ba-auth_keystore_file/tests/FileKeystore.test.ts
Alan Bridgeman 98be42cb99
All checks were successful
Publish to Private NPM Registry / publish (push) Successful in 34s
Updated the dependency (because forgot to before)
2026-02-18 15:31:23 -06:00

147 lines
No EOL
5.8 KiB
TypeScript

import path from 'path';
import NodeJose from 'node-jose';
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
import type { FileKeystore } from '../src/FileKeystore.js'; // Import TYPE only (safe in ESM)
// We use unstable_mockModule because standard jest.mock struggles to intercept
// core Node modules (like fs) in strict ESM mode.
jest.unstable_mockModule('fs', () => ({
// We must mock the 'default' export because your code uses: import fs from 'fs'
default: {
existsSync: jest.fn(),
mkdirSync: jest.fn(),
writeFileSync: jest.fn(),
readFileSync: jest.fn(),
},
// We also mock named exports purely for safety, though 'default' is the key one here
existsSync: jest.fn(),
mkdirSync: jest.fn(),
writeFileSync: jest.fn(),
readFileSync: jest.fn(),
}));
// Mock dependency
jest.unstable_mockModule('@BridgemanAccessible/ba-auth_keystore', () => ({
BaseKeystore: class {},
Keystore: {
addKeystoreType: jest.fn(),
},
}));
describe('FileKeystore', () => {
// Variables to hold our dynamically imported modules
let FileKeystoreClass: typeof FileKeystore;
let mockFs: any;
const TEST_DIR = '.test-cert';
const TEST_FILE = 'test-keystore.json';
const FULL_PATH = path.join(TEST_DIR, TEST_FILE);
beforeEach(async () => {
jest.clearAllMocks();
// 3. DYNAMICALLY IMPORT THE MODULES
// This is the magic. We import AFTER the mock is defined.
// This ensures 'fs' inside FileKeystore.ts is actually our mock.
const fsModule = await import('fs');
mockFs = fsModule.default; // Access the default export we mocked above
// Import your class under test
// Note: You might need to add '.js' to the path if your resolving is strict
const module = await import('../src/FileKeystore.js');
FileKeystoreClass = module.FileKeystore;
});
test('should create a NEW keystore if the file does not exist', async () => {
// SETUP:
// 1. Pretend the file does NOT exist
mockFs.existsSync.mockReturnValue(false);
// 2. Mock writeFileSync to do nothing (just verify it was called)
mockFs.writeFileSync.mockImplementation(() => {});
// 3. Mock mkdirSync
mockFs.mkdirSync.mockImplementation(() => undefined);
// EXECUTE
const keystoreInstance = await FileKeystoreClass.create(TEST_FILE, TEST_DIR);
// VERIFY
// 1. Verify it checked for file existence
expect(mockFs.existsSync).toHaveBeenCalledWith(FULL_PATH);
// 2. Verify it checked for directory existence (and created it)
expect(mockFs.mkdirSync).toHaveBeenCalledWith(TEST_DIR);
// 3. Verify it wrote the new keystore to disk
// We expect the first argument to be the path, and the second to be a JSON string
expect(mockFs.writeFileSync).toHaveBeenCalledWith(
FULL_PATH,
expect.stringContaining('"keys":') // Simple check to ensure it looks like JSON
);
// 4. Verify the instance path is set correctly
expect(keystoreInstance.getKeystoreFilePath()).toBe(FULL_PATH);
// 5. Verify internal keystore state (node-jose should have generated keys)
// @ts-ignore - accessing public property 'keystore' which might be protected in your real usage,
// but implied public in the provided snippet logic
const keys = keystoreInstance.keystore.all();
expect(keys.length).toBeGreaterThanOrEqual(1);
});
test('should LOAD an existing keystore if the file exists', async () => {
// SETUP:
// 1. Create a real temporary keystore using node-jose to mimic a file on disk
const realKeystore = await NodeJose.JWK.createKeyStore().generate('RSA', 2048);
const keystoreJson = JSON.stringify(realKeystore.keystore.toJSON(true));
// 2. Pretend the file DOES exist
mockFs.existsSync.mockReturnValue(true);
// 3. Pretend readFileSync returns our JSON string
mockFs.readFileSync.mockReturnValue(keystoreJson);
// EXECUTE
const keystoreInstance = await FileKeystoreClass.create(TEST_FILE, TEST_DIR);
// VERIFY
// 1. It should check existence
expect(mockFs.existsSync).toHaveBeenCalledWith(FULL_PATH);
// 2. It should READ the file
expect(mockFs.readFileSync).toHaveBeenCalledWith(FULL_PATH, 'utf-8');
// 3. CRITICAL: It should NOT overwrite the file
expect(mockFs.writeFileSync).not.toHaveBeenCalled();
// 4. CRITICAL: It should NOT try to create the directory again
// (The code checks !existsSync(file), so it goes into the else block)
expect(mockFs.mkdirSync).not.toHaveBeenCalled();
// 5. Verify the loaded keys match what we "mocked" on disk
// @ts-ignore
const loadedKeys = keystoreInstance.keystore.all();
expect(loadedKeys.length).toBe(1);
});
test('should create directory only if it is missing', async () => {
// Scenario: File missing, Directory also missing
mockFs.existsSync
.mockReturnValueOnce(false) // First check: File exists? No.
.mockReturnValueOnce(false); // Second check: Dir exists? No.
await FileKeystoreClass.create(TEST_FILE, TEST_DIR);
expect(mockFs.mkdirSync).toHaveBeenCalledWith(TEST_DIR);
});
test('should NOT create directory if it already exists', async () => {
// Scenario: File missing, Directory exists
mockFs.existsSync
.mockReturnValueOnce(false) // First check: File exists? No.
.mockReturnValueOnce(true); // Second check: Dir exists? Yes.
await FileKeystoreClass.create(TEST_FILE, TEST_DIR);
expect(mockFs.mkdirSync).not.toHaveBeenCalled();
});
});