diff --git a/api/src/__test__/core/utils/misc/get-key-file.test.ts b/api/src/__test__/core/utils/misc/get-key-file.test.ts index 12698aed3..e7f56dcc8 100644 --- a/api/src/__test__/core/utils/misc/get-key-file.test.ts +++ b/api/src/__test__/core/utils/misc/get-key-file.test.ts @@ -1,11 +1,12 @@ -import { expect, test } from 'vitest'; +import { expect, test, vi } from 'vitest'; import { store } from '@app/store/index.js'; import { FileLoadStatus, StateFileKey } from '@app/store/types.js'; -import '@app/core/utils/misc/get-key-file.js'; import '@app/store/modules/emhttp.js'; +vi.mock('fs/promises'); + test('Before loading key returns null', async () => { const { getKeyFile } = await import('@app/core/utils/misc/get-key-file.js'); const { status } = store.getState().registration; @@ -48,21 +49,70 @@ test('Returns empty key if key location is empty', async () => { await expect(getKeyFile()).resolves.toBe(''); }); -test( - 'Returns decoded key file if key location exists', - async () => { - const { getKeyFile } = await import('@app/core/utils/misc/get-key-file.js'); - const { loadStateFiles } = await import('@app/store/modules/emhttp.js'); - const { loadRegistrationKey } = await import('@app/store/modules/registration.js'); - // Load state files into store - await store.dispatch(loadStateFiles()); - await store.dispatch(loadRegistrationKey()); - // Check if store has state files loaded - const { status } = store.getState().registration; - expect(status).toBe(FileLoadStatus.LOADED); - await expect(getKeyFile()).resolves.toMatchInlineSnapshot( - '"hVs1tLjvC9FiiQsIwIQ7G1KszAcexf0IneThhnmf22SB0dGs5WzRkqMiSMmt2DtR5HOXFUD32YyxuzGeUXmky3zKpSu6xhZNKVg5atGM1OfvkzHBMldI3SeBLuUFSgejLbpNUMdTrbk64JJdbzle4O8wiQgkIpAMIGxeYLwLBD4zHBcfyzq40QnxG--HcX6j25eE0xqa2zWj-j0b0rCAXahJV2a3ySCbPzr1MvfPRTVb0rr7KJ-25R592hYrz4H7Sc1B3p0lr6QUxHE6o7bcYrWKDRtIVoZ8SMPpd1_0gzYIcl5GsDFzFumTXUh8NEnl0Q8hwW1YE-tRc6Y_rrvd7w"' - ); - }, - { timeout: 10000 } -); +test('Returns empty string when key file does not exist (ENOENT)', async () => { + const { readFile } = await import('fs/promises'); + + // Mock readFile to throw ENOENT error + const readFileMock = vi.mocked(readFile); + readFileMock.mockRejectedValueOnce( + Object.assign(new Error('ENOENT: no such file or directory'), { code: 'ENOENT' }) + ); + + // Clear the module cache and re-import to get fresh module with mock + vi.resetModules(); + const { getKeyFile } = await import('@app/core/utils/misc/get-key-file.js'); + const { updateEmhttpState } = await import('@app/store/modules/emhttp.js'); + const { store: freshStore } = await import('@app/store/index.js'); + + // Set key file location to a non-existent file + freshStore.dispatch( + updateEmhttpState({ + field: StateFileKey.var, + state: { + regFile: '/boot/config/Pro.key', + }, + }) + ); + + // Should return empty string when file doesn't exist + await expect(getKeyFile()).resolves.toBe(''); + + // Clear mock + readFileMock.mockReset(); + vi.resetModules(); +}); + +test('Returns decoded key file if key location exists', async () => { + const { readFile } = await import('fs/promises'); + + // Mock a valid key file content + const mockKeyContent = + 'hVs1tLjvC9FiiQsIwIQ7G1KszAcexf0IneThhnmf22SB0dGs5WzRkqMiSMmt2DtR5HOXFUD32YyxuzGeUXmky3zKpSu6xhZNKVg5atGM1OfvkzHBMldI3SeBLuUFSgejLbpNUMdTrbk64JJdbzle4O8wiQgkIpAMIGxeYLwLBD4zHBcfyzq40QnxG--HcX6j25eE0xqa2zWj-j0b0rCAXahJV2a3ySCbPzr1MvfPRTVb0rr7KJ-25R592hYrz4H7Sc1B3p0lr6QUxHE6o7bcYrWKDRtIVoZ8SMPpd1_0gzYIcl5GsDFzFumTXUh8NEnl0Q8hwW1YE-tRc6Y_rrvd7w=='; + const binaryContent = Buffer.from(mockKeyContent, 'base64').toString('binary'); + + const readFileMock = vi.mocked(readFile); + readFileMock.mockResolvedValue(binaryContent); + + // Clear the module cache and re-import to get fresh module with mock + vi.resetModules(); + const { getKeyFile } = await import('@app/core/utils/misc/get-key-file.js'); + const { loadStateFiles } = await import('@app/store/modules/emhttp.js'); + const { loadRegistrationKey } = await import('@app/store/modules/registration.js'); + const { store: freshStore } = await import('@app/store/index.js'); + + // Load state files into store + await freshStore.dispatch(loadStateFiles()); + await freshStore.dispatch(loadRegistrationKey()); + // Check if store has state files loaded + const { status } = freshStore.getState().registration; + expect(status).toBe(FileLoadStatus.LOADED); + + const result = await getKeyFile(); + expect(result).toBe( + 'hVs1tLjvC9FiiQsIwIQ7G1KszAcexf0IneThhnmf22SB0dGs5WzRkqMiSMmt2DtR5HOXFUD32YyxuzGeUXmky3zKpSu6xhZNKVg5atGM1OfvkzHBMldI3SeBLuUFSgejLbpNUMdTrbk64JJdbzle4O8wiQgkIpAMIGxeYLwLBD4zHBcfyzq40QnxG--HcX6j25eE0xqa2zWj-j0b0rCAXahJV2a3ySCbPzr1MvfPRTVb0rr7KJ-25R592hYrz4H7Sc1B3p0lr6QUxHE6o7bcYrWKDRtIVoZ8SMPpd1_0gzYIcl5GsDFzFumTXUh8NEnl0Q8hwW1YE-tRc6Y_rrvd7w' + ); + + // Clear mock + readFileMock.mockReset(); + vi.resetModules(); +}, 10000); diff --git a/api/src/core/utils/misc/get-key-file.ts b/api/src/core/utils/misc/get-key-file.ts index 55cd139c9..941f29521 100644 --- a/api/src/core/utils/misc/get-key-file.ts +++ b/api/src/core/utils/misc/get-key-file.ts @@ -16,11 +16,22 @@ export const getKeyFile = async function (appStore: RootState = store.getState() const keyFileName = basename(emhttp.var?.regFile); const registrationKeyFilePath = join(paths['keyfile-base'], keyFileName); - const keyFile = await readFile(registrationKeyFilePath, 'binary'); - return Buffer.from(keyFile, 'binary') - .toString('base64') - .trim() - .replace(/\+/g, '-') - .replace(/\//g, '_') - .replace(/=/g, ''); + + try { + const keyFile = await readFile(registrationKeyFilePath, 'binary'); + return Buffer.from(keyFile, 'binary') + .toString('base64') + .trim() + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=/g, ''); + } catch (error) { + // Handle ENOENT error when Pro.key file doesn't exist + if (error instanceof Error && 'code' in error && error.code === 'ENOENT') { + // Return empty string when key file is missing (ENOKEYFILE state) + return ''; + } + // Re-throw other errors + throw error; + } };