0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-23 20:33:16 -05:00
logto/packages/core/src/middleware/koa-audit-log.test.ts
2022-12-26 12:12:19 +00:00

192 lines
5.7 KiB
TypeScript

import type { LogKey } from '@logto/schemas';
import { LogResult } from '@logto/schemas';
import { pickDefault, createMockUtils } from '@logto/shared/esm';
import i18next from 'i18next';
import RequestError from '#src/errors/RequestError/index.js';
import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
import type { WithLogContext, LogPayload } from './koa-audit-log.js';
const { jest } = import.meta;
const { mockEsm } = createMockUtils(jest);
const { insertLog } = mockEsm('#src/queries/log.js', () => ({
insertLog: jest.fn(),
}));
const nanoIdMock = 'mockId';
mockEsm('@logto/core-kit', () => ({
generateStandardId: () => nanoIdMock,
}));
const koaLog = await pickDefault(import('./koa-audit-log.js'));
describe('koaAuditLog middleware', () => {
const logKey: LogKey = 'Interaction.SignIn.Identifier.VerificationCode.Submit';
const mockPayload: LogPayload = {
userId: 'foo',
username: 'Bar',
};
const ip = '192.168.0.1';
const userAgent =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36';
afterEach(() => {
jest.clearAllMocks();
});
it('should insert a success log when next() does not throw an error', async () => {
// @ts-expect-error for testing
const ctx: WithLogContext<ReturnType<typeof createContextWithRouteParameters>> = {
...createContextWithRouteParameters({ headers: { 'user-agent': userAgent } }),
};
ctx.request.ip = ip;
const additionalMockPayload: LogPayload = { foo: 'bar' };
const next = async () => {
const log = ctx.createLog(logKey);
log.append(mockPayload);
log.append(additionalMockPayload);
};
await koaLog()(ctx, next);
expect(insertLog).toBeCalledWith({
id: nanoIdMock,
key: logKey,
payload: {
...mockPayload,
...additionalMockPayload,
key: logKey,
result: LogResult.Success,
ip,
userAgent,
},
});
});
it('should insert multiple success logs when needed', async () => {
// @ts-expect-error for testing
const ctx: WithLogContext<ReturnType<typeof createContextWithRouteParameters>> = {
...createContextWithRouteParameters({ headers: { 'user-agent': userAgent } }),
};
ctx.request.ip = ip;
const additionalMockPayload: LogPayload = { foo: 'bar' };
const next = async () => {
const log = ctx.createLog(logKey);
log.append(mockPayload);
log.append(additionalMockPayload);
const log2 = ctx.createLog(logKey);
log2.append(mockPayload);
};
await koaLog()(ctx, next);
const basePayload = {
...mockPayload,
key: logKey,
result: LogResult.Success,
ip,
userAgent,
};
expect(insertLog).toHaveBeenCalledWith({
id: nanoIdMock,
key: logKey,
payload: basePayload,
});
expect(insertLog).toHaveBeenCalledWith({
id: nanoIdMock,
key: logKey,
payload: {
...basePayload,
...additionalMockPayload,
},
});
});
it('should not log when there is no log type', async () => {
// @ts-expect-error for testing
const ctx: WithLogContext<ReturnType<typeof createContextWithRouteParameters>> = {
...createContextWithRouteParameters({ headers: { 'user-agent': userAgent } }),
};
ctx.request.ip = ip;
// eslint-disable-next-line unicorn/consistent-function-scoping, @typescript-eslint/no-empty-function
const next = async () => {};
await koaLog()(ctx, next);
expect(insertLog).not.toBeCalled();
});
describe('should insert an error log with the error message when next() throws an error', () => {
it('should log with error message when next throws a normal Error', async () => {
// @ts-expect-error for testing
const ctx: WithLogContext<ReturnType<typeof createContextWithRouteParameters>> = {
...createContextWithRouteParameters({ headers: { 'user-agent': userAgent } }),
};
ctx.request.ip = ip;
const message = 'Normal error';
const error = new Error(message);
const next = async () => {
const log = ctx.createLog(logKey);
log.append(mockPayload);
throw error;
};
await expect(koaLog()(ctx, next)).rejects.toMatchError(error);
expect(insertLog).toBeCalledWith({
id: nanoIdMock,
key: logKey,
payload: {
...mockPayload,
key: logKey,
result: LogResult.Error,
error: { message: `Error: ${message}` },
ip,
userAgent,
},
});
});
it('should update all logs with error result when next() throws a RequestError', async () => {
// @ts-expect-error for testing
const ctx: WithLogContext<ReturnType<typeof createContextWithRouteParameters>> = {
...createContextWithRouteParameters({ headers: { 'user-agent': userAgent } }),
};
ctx.request.ip = ip;
const message = 'Error message';
jest.spyOn(i18next, 't').mockReturnValueOnce(message); // Used in
const code = 'connector.general';
const data = { foo: 'bar', num: 123 };
const error = new RequestError(code, data);
const next = async () => {
const log = ctx.createLog(logKey);
log.append(mockPayload);
const log2 = ctx.createLog(logKey);
log2.append(mockPayload);
throw error;
};
await expect(koaLog()(ctx, next)).rejects.toMatchError(error);
expect(insertLog).toHaveBeenCalledTimes(2);
expect(insertLog).toBeCalledWith({
id: nanoIdMock,
key: logKey,
payload: {
...mockPayload,
key: logKey,
result: LogResult.Error,
error: { message, code, data },
ip,
userAgent,
},
});
});
});
});