142 lines
6.2 KiB
TypeScript
142 lines
6.2 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const resolveSecretsMock = vi.fn(async () => undefined);
|
|
const createServiceAppMock = vi.fn();
|
|
const registerOptionalJwtContextMock = vi.fn(async () => undefined);
|
|
const startServiceMock = vi.fn(async () => undefined);
|
|
const loadProductCacheMock = vi.fn(async () => undefined);
|
|
const initCosmosIfNeededMock = vi.fn(async () => undefined);
|
|
const verifyTokenMock = vi.fn(async () => ({ sub: 'user_1', productId: 'lysnrai' }));
|
|
const seedDefaultFlagsMock = vi.fn(async () => undefined);
|
|
const runPendingMigrationsMock = vi.fn(async () => undefined);
|
|
const registerDiagnosticsSubscribersMock = vi.fn();
|
|
const startTriggerEvaluationJobMock = vi.fn();
|
|
|
|
const appMock = {
|
|
register: vi.fn(async () => undefined),
|
|
addHook: vi.fn(),
|
|
};
|
|
|
|
vi.mock('@bytelyst/config', () => ({
|
|
LYSNR_SECRETS: {
|
|
COSMOS_KEY: 'cosmos-key',
|
|
COSMOS_ENDPOINT: 'cosmos-endpoint',
|
|
JWT_SECRET: 'jwt-secret',
|
|
STRIPE_SECRET_KEY: 'stripe-secret',
|
|
STRIPE_WEBHOOK_SECRET: 'stripe-webhook',
|
|
AZURE_BLOB_CONNECTION_STRING: 'blob-conn',
|
|
AZURE_BLOB_ACCOUNT_KEY: 'blob-key',
|
|
},
|
|
resolveSecrets: resolveSecretsMock,
|
|
loadProductIdentity: () => ({
|
|
productId: 'lysnrai',
|
|
displayName: 'LysnrAI',
|
|
licensePrefix: 'LYSNR',
|
|
}),
|
|
}));
|
|
|
|
vi.mock('@bytelyst/fastify-core', () => ({
|
|
createServiceApp: createServiceAppMock,
|
|
registerOptionalJwtContext: registerOptionalJwtContextMock,
|
|
startService: startServiceMock,
|
|
}));
|
|
|
|
vi.mock('./modules/products/routes.js', () => ({ productRoutes: vi.fn() }));
|
|
vi.mock('./modules/products/cache.js', () => ({ loadProductCache: loadProductCacheMock }));
|
|
vi.mock('./modules/auth/routes.js', () => ({ authRoutes: vi.fn() }));
|
|
vi.mock('./modules/agent-runtime/routes.js', () => ({ agentRuntimeRoutes: vi.fn() }));
|
|
vi.mock('./modules/audit/routes.js', () => ({ auditRoutes: vi.fn() }));
|
|
vi.mock('./modules/notifications/routes.js', () => ({ notificationRoutes: vi.fn() }));
|
|
vi.mock('./modules/timeline/routes.js', () => ({ timelineRoutes: vi.fn() }));
|
|
vi.mock('./modules/flags/routes.js', () => ({ flagRoutes: vi.fn() }));
|
|
vi.mock('./modules/ratelimit/routes.js', () => ({ rateLimitRoutes: vi.fn() }));
|
|
vi.mock('./modules/blob/routes.js', () => ({ blobRoutes: vi.fn() }));
|
|
vi.mock('./modules/invitations/routes.js', () => ({ invitationRoutes: vi.fn() }));
|
|
vi.mock('./modules/referrals/routes.js', () => ({ referralRoutes: vi.fn() }));
|
|
vi.mock('./modules/promos/routes.js', () => ({ promoRoutes: vi.fn() }));
|
|
vi.mock('./modules/subscriptions/routes.js', () => ({ subscriptionRoutes: vi.fn() }));
|
|
vi.mock('./modules/usage/routes.js', () => ({ usageRoutes: vi.fn() }));
|
|
vi.mock('./modules/plans/routes.js', () => ({ planRoutes: vi.fn() }));
|
|
vi.mock('./modules/licenses/routes.js', () => ({ licenseRoutes: vi.fn() }));
|
|
vi.mock('./modules/stripe/routes.js', () => ({ stripeRoutes: vi.fn() }));
|
|
vi.mock('./modules/settings/routes.js', () => ({ settingsRoutes: vi.fn() }));
|
|
vi.mock('./modules/items/routes.js', () => ({ itemRoutes: vi.fn() }));
|
|
vi.mock('./modules/comments/routes.js', () => ({ commentRoutes: vi.fn() }));
|
|
vi.mock('./modules/votes/routes.js', () => ({ voteRoutes: vi.fn() }));
|
|
vi.mock('./modules/public/routes.js', () => ({ publicRoutes: vi.fn() }));
|
|
vi.mock('./modules/tokens/routes.js', () => ({ tokenRoutes: vi.fn() }));
|
|
vi.mock('./modules/themes/routes.js', () => ({ themeRoutes: vi.fn() }));
|
|
vi.mock('./lib/cosmos-init.js', () => ({ initCosmosIfNeeded: initCosmosIfNeededMock }));
|
|
vi.mock('./lib/config.js', () => ({ config: { CORS_ORIGIN: '*', PORT: 4003, HOST: '0.0.0.0' } }));
|
|
vi.mock('./modules/auth/jwt.js', () => ({ verifyToken: verifyTokenMock }));
|
|
vi.mock('./modules/flags/seed.js', () => ({ seedDefaultFlags: seedDefaultFlagsMock }));
|
|
vi.mock('./migrations/runner.js', () => ({ runPendingMigrations: runPendingMigrationsMock }));
|
|
vi.mock('./modules/diagnostics/subscribers.js', () => ({
|
|
registerDiagnosticsSubscribers: registerDiagnosticsSubscribersMock,
|
|
}));
|
|
vi.mock('./modules/diagnostics/trigger-job.js', () => ({
|
|
startTriggerEvaluationJob: startTriggerEvaluationJobMock,
|
|
}));
|
|
|
|
describe('server bootstrap', () => {
|
|
beforeEach(() => {
|
|
vi.resetModules();
|
|
vi.clearAllMocks();
|
|
createServiceAppMock.mockResolvedValue(appMock);
|
|
appMock.register.mockReset();
|
|
appMock.register.mockResolvedValue(undefined);
|
|
appMock.addHook.mockReset();
|
|
});
|
|
|
|
it('initializes secrets, app, routes, and starts service', async () => {
|
|
await import('./server.js');
|
|
|
|
expect(resolveSecretsMock).toHaveBeenCalledOnce();
|
|
expect(initCosmosIfNeededMock).toHaveBeenCalledOnce();
|
|
expect(loadProductCacheMock).toHaveBeenCalledOnce();
|
|
expect(createServiceAppMock).toHaveBeenCalledOnce();
|
|
expect(registerOptionalJwtContextMock).toHaveBeenCalledOnce();
|
|
expect(runPendingMigrationsMock).toHaveBeenCalledOnce();
|
|
expect(seedDefaultFlagsMock).toHaveBeenCalledOnce();
|
|
expect(registerDiagnosticsSubscribersMock).toHaveBeenCalledOnce();
|
|
expect(startTriggerEvaluationJobMock).toHaveBeenCalledOnce();
|
|
expect(appMock.register).toHaveBeenCalled();
|
|
expect(appMock.register.mock.calls.length).toBeGreaterThan(15);
|
|
expect(startServiceMock).toHaveBeenCalledWith(appMock, { port: 4003, host: '0.0.0.0' });
|
|
}, 15000);
|
|
|
|
it('registers optional jwt parsing with the shared fastify-core helper', async () => {
|
|
await import('./server.js');
|
|
|
|
const helperCall = registerOptionalJwtContextMock.mock.calls[0] as unknown as [
|
|
unknown,
|
|
{
|
|
verifyToken: (token: string) => Promise<unknown>;
|
|
},
|
|
];
|
|
const options = helperCall[1];
|
|
expect(options).toBeDefined();
|
|
expect(options.verifyToken).toBeTypeOf('function');
|
|
await expect(options.verifyToken('token_abc')).resolves.toEqual({
|
|
sub: 'user_1',
|
|
productId: 'lysnrai',
|
|
});
|
|
expect(verifyTokenMock).toHaveBeenCalledWith('token_abc');
|
|
});
|
|
|
|
it('passes through verifyToken rejections to the shared helper callback', async () => {
|
|
verifyTokenMock.mockRejectedValueOnce(new Error('invalid token'));
|
|
await import('./server.js');
|
|
|
|
const helperCall = registerOptionalJwtContextMock.mock.calls[0] as unknown as [
|
|
unknown,
|
|
{
|
|
verifyToken: (token: string) => Promise<unknown>;
|
|
},
|
|
];
|
|
const options = helperCall[1];
|
|
|
|
await expect(options.verifyToken('broken')).rejects.toThrow('invalid token');
|
|
});
|
|
});
|