Initial Commit
All checks were successful
Publish to Private NPM Registry / publish (push) Successful in 31s

This commit is contained in:
Alan Bridgeman 2026-02-17 19:48:41 -06:00
commit 6895111f69
13 changed files with 4107 additions and 0 deletions

158
tests/BaseKeystore.test.ts Normal file
View file

@ -0,0 +1,158 @@
import type { Request, Response, NextFunction } from 'express';
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
import type { BaseKeystore as BaseKeystoreType } from '../src/BaseKeystore.js';
// Mock the logging library
jest.unstable_mockModule('@BridgemanAccessible/ba-logging', () => ({
logMessage: jest.fn(),
LogLevel: {
DEBUG: 'DEBUG',
ERROR: 'ERROR'
}
}));
describe('BaseKeystore', () => {
/** Env management pattern (save/restore existing environment variables) */
const OLD_ENV = process.env;
/** Instance of the BaseKeystore class (needed to access static members like WELL_KNOWN_URI) */
let BaseKeystore: typeof BaseKeystoreType;
/** Instance of the concrete subclass for testing (Note, the typing gets kind of weird here because of loading mechanics - see `beforeEach` block) */
let keystoreInstance: InstanceType<typeof BaseKeystore> & { setInternalKeystore: (ks: any) => void };
/** Mock Express request object */
let mockReq: Partial<Request & { /* Make path NOT read-only */ path: string }>;
/** Mock Express response object */
let mockRes: Partial<Response>;
/** Mock Express next function */
let mockNext: NextFunction;
/** Mock Node-Jose keystore */
let mockJoseStore: any;
/** Mock logging module */
let mockLogging: any;
beforeEach(async () => {
jest.clearAllMocks();
// Make a copy of the existing environment variables to restore after tests
// (important for not affecting other tests that might run in the same process)
process.env = { ...OLD_ENV };
// Reset Process Env
process.env.DEBUG_INTERSERVICE_COMMS = 'false';
// Because of ESM stuff and mocking out dependencies,
// we need to load the BaseKeystore class asynchronously in the beforeEach block.
// Instead of at the top level of the test file.
const module = await import('../src/BaseKeystore.js');
BaseKeystore = module.BaseKeystore;
// Concrete implementation for testing abstract class
// Note, the definition needs to be done here in the beforeEach block after we load the BaseKeystore class,
// because it needs to extend the BaseKeystore class that we load here.
class TestKeystore extends BaseKeystore {
// Helper to manually set the internal keystore for testing
public setInternalKeystore(ks: any) {
this.keystore = ks;
}
}
keystoreInstance = new TestKeystore();
// Mock Express objects
mockReq = { path: '/' };
mockRes = {
status: jest.fn<(...args: any[]) => Response>().mockReturnThis(),
json: jest.fn<(...args: any[]) => Response>().mockReturnThis(),
};
mockNext = jest.fn();
// Mock Node-Jose Store
mockJoseStore = {
toJSON: jest.fn().mockReturnValue({ keys: [] }),
get: jest.fn()
};
mockLogging = await import('@BridgemanAccessible/ba-logging');
});
describe('getKeystore()', () => {
it('should throw error if keystore is null', () => {
expect(() => keystoreInstance.getKeystore()).toThrow('Keystore not ready');
});
it('should return the keystore if initialized', () => {
keystoreInstance.setInternalKeystore(mockJoseStore);
expect(keystoreInstance.getKeystore()).toBe(mockJoseStore);
});
});
describe('getKey()', () => {
it('should call get on the underlying node-jose store', () => {
keystoreInstance.setInternalKeystore(mockJoseStore);
keystoreInstance.getKey('key-id', { kty: 'RSA' });
expect(mockJoseStore.get).toHaveBeenCalledWith('key-id', { kty: 'RSA' });
});
});
describe('middleware()', () => {
it('should call next() immediately if path is not .well-known URI', () => {
mockReq.path = '/some/other/path';
keystoreInstance.middleware(mockReq as Request, mockRes as Response, mockNext);
expect(mockNext).toHaveBeenCalled();
expect(mockRes.status).not.toHaveBeenCalled();
});
it('should call next(Error) if accessing .well-known but keystore is not ready', () => {
mockReq.path = BaseKeystore.WELL_KNOWN_URI;
keystoreInstance.middleware(mockReq as Request, mockRes as Response, mockNext);
expect(mockNext).toHaveBeenCalledWith(expect.any(Error));
expect(mockNext).toHaveBeenCalledWith(expect.objectContaining({ message: 'Keystore not ready' }));
});
it('should return 200 and JSON public keys if ready', () => {
mockReq.path = BaseKeystore.WELL_KNOWN_URI;
keystoreInstance.setInternalKeystore(mockJoseStore);
keystoreInstance.middleware(mockReq as Request, mockRes as Response, mockNext);
expect(mockRes.status).toHaveBeenCalledWith(200);
expect(mockRes.json).toHaveBeenCalledWith({ keys: [] });
// Ensure private keys are NOT exported (toJSON should be called with false or no args)
expect(mockJoseStore.toJSON).toHaveBeenCalled();
expect(mockNext).not.toHaveBeenCalled(); // Should stop processing
});
it('should log debug messages when env var is set', () => {
process.env.DEBUG_INTERSERVICE_COMMS = 'true';
mockReq.path = BaseKeystore.WELL_KNOWN_URI;
keystoreInstance.setInternalKeystore(mockJoseStore);
keystoreInstance.middleware(mockReq as Request, mockRes as Response, mockNext);
expect(mockLogging.logMessage).toHaveBeenCalledTimes(2); // Start of middleware + JSON log
expect(mockLogging.logMessage).toHaveBeenCalledWith(expect.stringContaining('Keystore middleware called'), 'DEBUG');
});
it('should log error messages when env var is set and keystore fails', () => {
process.env.DEBUG_INTERSERVICE_COMMS = 'true';
mockReq.path = BaseKeystore.WELL_KNOWN_URI;
// Keystore is NOT set here
keystoreInstance.middleware(mockReq as Request, mockRes as Response, mockNext);
expect(mockLogging.logMessage).toHaveBeenCalledWith('Keystore not ready', 'ERROR');
});
});
afterAll(() => {
// Restore original environment variables after all tests are done
process.env = OLD_ENV;
})
});