Skip to content

Commit 2e16cdf

Browse files
committed
add test:custom-api template
1 parent eedd966 commit 2e16cdf

12 files changed

+370
-2
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"type": "service_account",
3+
"project_id": "",
4+
"private_key_id": "",
5+
"private_key": "",
6+
"client_email": "",
7+
"client_id": "",
8+
"auth_uri": "",
9+
"token_uri": "",
10+
"auth_provider_x509_cert_url": "",
11+
"client_x509_cert_url": "",
12+
"universe_domain": ""
13+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
jest.mock('@waylaidwanderer/fetch-event-source', () => ({
2+
fetchEventSource: jest
3+
.fn()
4+
.mockImplementation((url, { onopen, onmessage, onclose, onerror, error }) => {
5+
// Simulating the onopen event
6+
onopen && onopen({ status: 200 });
7+
8+
// Simulating a few onmessage events
9+
onmessage &&
10+
onmessage({ data: JSON.stringify({ message: 'First message' }), event: 'message' });
11+
onmessage &&
12+
onmessage({ data: JSON.stringify({ message: 'Second message' }), event: 'message' });
13+
onmessage &&
14+
onmessage({ data: JSON.stringify({ message: 'Third message' }), event: 'message' });
15+
16+
// Simulate the onclose event
17+
onclose && onclose();
18+
19+
if (error) {
20+
// Simulate the onerror event
21+
onerror && onerror({ status: 500 });
22+
}
23+
24+
// Return a Promise that resolves to simulate async behavior
25+
return Promise.resolve();
26+
}),
27+
}));

api/custom/test/__mocks__/logger.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
jest.mock('winston', () => {
2+
const mockFormatFunction = jest.fn((fn) => {
3+
return (info) => {
4+
if (!info) return {};
5+
return typeof fn === 'function' ? fn(info) : info;
6+
};
7+
});
8+
9+
mockFormatFunction.colorize = jest.fn();
10+
mockFormatFunction.combine = jest.fn();
11+
mockFormatFunction.label = jest.fn();
12+
mockFormatFunction.timestamp = jest.fn();
13+
mockFormatFunction.printf = jest.fn();
14+
mockFormatFunction.errors = jest.fn();
15+
mockFormatFunction.splat = jest.fn();
16+
mockFormatFunction.json = jest.fn();
17+
18+
const winstonMock = {
19+
format: mockFormatFunction,
20+
createLogger: jest.fn().mockReturnValue({
21+
info: jest.fn(),
22+
warn: jest.fn(),
23+
debug: jest.fn(),
24+
error: jest.fn(),
25+
}),
26+
transports: {
27+
Console: jest.fn(),
28+
DailyRotateFile: jest.fn(),
29+
File: jest.fn(),
30+
},
31+
addColors: jest.fn(),
32+
};
33+
winstonMock.default = winstonMock;
34+
Object.defineProperty(winstonMock, 'addColors', { value: jest.fn(), writable: true, configurable: true });
35+
Object.defineProperty(winstonMock.default, 'addColors', { value: jest.fn(), writable: true, configurable: true });
36+
return winstonMock;
37+
});
38+
39+
jest.mock('winston-daily-rotate-file', () => {
40+
return jest.fn().mockImplementation(() => {
41+
return {
42+
level: 'error',
43+
filename: '../logs/error-%DATE%.log',
44+
datePattern: 'YYYY-MM-DD',
45+
zippedArchive: true,
46+
maxSize: '20m',
47+
maxFiles: '14d',
48+
format: 'format',
49+
};
50+
});
51+
});
52+
53+
jest.mock('~/config', () => {
54+
return {
55+
logger: {
56+
info: jest.fn(),
57+
warn: jest.fn(),
58+
debug: jest.fn(),
59+
error: jest.fn(),
60+
},
61+
};
62+
});
63+
64+
jest.mock('~/config/parsers', () => {
65+
return {
66+
redactMessage: jest.fn(),
67+
redactFormat: jest.fn(),
68+
debugTraverse: jest.fn(),
69+
jsonTruncateFormat: jest.fn(),
70+
};
71+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// api/test/__mocks__/openid-client-passport.js
2+
const Strategy = jest.fn().mockImplementation((options, verify) => {
3+
return { name: 'mocked-openid-passport-strategy', options, verify };
4+
});
5+
6+
module.exports = { Strategy };
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// api/test/__mocks__/openid-client.js
2+
module.exports = {
3+
Issuer: {
4+
discover: jest.fn().mockResolvedValue({
5+
Client: jest.fn().mockImplementation(() => ({
6+
authorizationUrl: jest.fn().mockReturnValue('mock_auth_url'),
7+
callback: jest.fn().mockResolvedValue({
8+
access_token: 'mock_access_token',
9+
id_token: 'mock_id_token',
10+
claims: () => ({
11+
sub: 'mock_sub',
12+
13+
}),
14+
}),
15+
userinfo: jest.fn().mockResolvedValue({
16+
sub: 'mock_sub',
17+
18+
}),
19+
})),
20+
}),
21+
},
22+
Strategy: jest.fn().mockImplementation((options, verify) => {
23+
// Store verify to call it if needed, or just mock the strategy behavior
24+
return { name: 'openid-mock-strategy' };
25+
}),
26+
custom: {
27+
setHttpOptionsDefaults: jest.fn(),
28+
},
29+
// Add any other exports from openid-client that are used directly
30+
// For example, if your code uses `client.Issuer.discover`, then mock `Issuer`
31+
// If it uses `new Strategy()`, then mock `Strategy`
32+
// Based on openidStrategy.js, it uses:
33+
// const client = require('openid-client'); -> client.discovery, client.fetchUserInfo, client.genericGrantRequest
34+
// const { Strategy: OpenIDStrategy } = require('openid-client/passport');
35+
// So the mock needs to cover these.
36+
// The provided mock in openidStrategy.spec.js is a good reference.
37+
38+
// Simpler mock based on the spec file:
39+
discovery: jest.fn().mockResolvedValue({
40+
clientId: 'fake_client_id',
41+
clientSecret: 'fake_client_secret',
42+
issuer: 'https://fake-issuer.com',
43+
Client: jest.fn().mockImplementation(() => ({
44+
authorizationUrl: jest.fn().mockReturnValue('mock_auth_url'),
45+
callback: jest.fn().mockResolvedValue({
46+
access_token: 'mock_access_token',
47+
id_token: 'mock_id_token',
48+
claims: () => ({
49+
sub: 'mock_sub',
50+
51+
}),
52+
}),
53+
userinfo: jest.fn().mockResolvedValue({
54+
sub: 'mock_sub',
55+
56+
}),
57+
grant: jest.fn().mockResolvedValue({ access_token: 'mock_grant_token' }), // For genericGrantRequest
58+
})),
59+
}),
60+
fetchUserInfo: jest.fn().mockResolvedValue({
61+
preferred_username: 'preferred_username',
62+
}),
63+
genericGrantRequest: jest
64+
.fn()
65+
.mockResolvedValue({ access_token: 'mock_grant_access_token', expires_in: 3600 }),
66+
customFetch: Symbol('customFetch'),
67+
};

api/custom/test/__mocks__/winston.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const mockFormatFunction = jest.fn((fn) => {
2+
return (info) => {
3+
if (!info) return {};
4+
return typeof fn === 'function' ? fn(info) : info;
5+
};
6+
});
7+
mockFormatFunction.colorize = jest.fn();
8+
mockFormatFunction.combine = jest.fn();
9+
mockFormatFunction.label = jest.fn();
10+
mockFormatFunction.timestamp = jest.fn();
11+
mockFormatFunction.printf = jest.fn();
12+
mockFormatFunction.errors = jest.fn();
13+
mockFormatFunction.splat = jest.fn();
14+
mockFormatFunction.json = jest.fn();
15+
16+
const winstonMock = {
17+
format: mockFormatFunction,
18+
createLogger: jest.fn().mockReturnValue({
19+
info: jest.fn(),
20+
warn: jest.fn(),
21+
debug: jest.fn(),
22+
error: jest.fn(),
23+
}),
24+
transports: {
25+
Console: jest.fn(),
26+
DailyRotateFile: jest.fn(),
27+
File: jest.fn(),
28+
},
29+
addColors: jest.fn(),
30+
};
31+
winstonMock.default = winstonMock;
32+
Object.defineProperty(winstonMock, 'addColors', { value: jest.fn(), writable: true, configurable: true });
33+
Object.defineProperty(winstonMock.default, 'addColors', { value: jest.fn(), writable: true, configurable: true });
34+
module.exports = winstonMock;
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
const request = require('supertest');
2+
const app = require('@server/index'); // Assuming your app is exported from here
3+
4+
// Mockery for JWT authentication and other potential middleware
5+
jest.mock('@server/middleware/requireJwtAuth', () => (req, res, next) => {
6+
req.user = { id: 'testUserId' }; // Mock a user object
7+
next();
8+
});
9+
10+
jest.mock('@server/middleware/checkBan', () => (req, res, next) => {
11+
next();
12+
});
13+
14+
// Mock any other middleware that might interfere with basic route testing
15+
// For example, if you have permission checks, mock them to allow access for these tests.
16+
17+
describe('Custom API Routes', () => {
18+
let server;
19+
20+
beforeAll((done) => {
21+
server = app.listen(done);
22+
});
23+
24+
afterAll((done) => {
25+
server.close(done);
26+
});
27+
28+
// Test for routes.customUser - Assuming a GET endpoint like /api/user/custom
29+
describe('GET /api/user/custom', () => {
30+
it('should respond with 200 for customUser route', async () => {
31+
const response = await request(server).get('/api/user/custom');
32+
// Replace 404 with the expected status code if the endpoint is different or requires specific conditions
33+
// For a simple check, we might expect it to exist, or be a placeholder returning 200/404 initially.
34+
// This depends on how 'routes.customUser' is actually implemented.
35+
// If it's a POST, change .get to .post and add .send({})
36+
expect(response.statusCode).not.toBe(500); // Example: Check it doesn't error out
37+
});
38+
});
39+
40+
// Test for routes.promptsUtil - Assuming a GET endpoint like /api/prompts/utils
41+
describe('GET /api/prompts/utils', () => {
42+
it('should respond with 200 for promptsUtil route', async () => {
43+
const response = await request(server).get('/api/prompts/utils');
44+
expect(response.statusCode).not.toBe(500);
45+
});
46+
});
47+
48+
// Test for routes.school - Assuming a GET endpoint like /api/school
49+
describe('GET /api/school', () => {
50+
it('should respond with 200 for school route', async () => {
51+
const response = await request(server).get('/api/school');
52+
expect(response.statusCode).not.toBe(500);
53+
});
54+
});
55+
56+
// Test for routes.coupon - Assuming a GET endpoint like /api/coupon
57+
describe('GET /api/coupon', () => {
58+
it('should respond with 200 for coupon route', async () => {
59+
const response = await request(server).get('/api/coupon');
60+
expect(response.statusCode).not.toBe(500);
61+
});
62+
});
63+
64+
// Test for routes.customBalance - Assuming a GET endpoint like /api/custom-balance
65+
describe('GET /api/custom-balance', () => {
66+
it('should respond with 200 for customBalance route', async () => {
67+
const response = await request(server).get('/api/custom-balance');
68+
expect(response.statusCode).not.toBe(500);
69+
});
70+
});
71+
72+
// Test for routes.admin - Assuming a GET endpoint like /api/admin
73+
describe('GET /api/admin', () => {
74+
it('should respond with 200 for admin route', async () => {
75+
const response = await request(server).get('/api/admin');
76+
expect(response.statusCode).not.toBe(500);
77+
});
78+
});
79+
80+
// Test for routes.customPresets - Assuming a GET endpoint like /api/custom-presets
81+
describe('GET /api/custom-presets', () => {
82+
it('should respond with 200 for customPresets route', async () => {
83+
const response = await request(server).get('/api/custom-presets');
84+
expect(response.statusCode).not.toBe(500);
85+
});
86+
});
87+
});

api/custom/test/jestSetup.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// See .env.test.example for an example of the '.env.test' file.
2+
require('dotenv').config({ path: './test/.env.test' });
3+
4+
jest.mock('winston');
5+
6+
process.env.MONGO_URI = 'mongodb://127.0.0.1:27017/dummy-uri';
7+
process.env.BAN_VIOLATIONS = 'true';
8+
process.env.BAN_DURATION = '7200000';
9+
process.env.BAN_INTERVAL = '20';
10+
process.env.CI = 'true';
11+
process.env.JWT_SECRET = 'test';
12+
process.env.JWT_REFRESH_SECRET = 'test';
13+
process.env.CREDS_KEY = 'test';
14+
process.env.CREDS_IV = 'test';

api/jest.custom.config.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
module.exports = {
2+
testEnvironment: 'node',
3+
clearMocks: true,
4+
resetMocks: true,
5+
resetModules: true,
6+
roots: ['<rootDir>/custom/test'],
7+
coverageDirectory: 'coverage',
8+
setupFiles: [
9+
'./custom/test/jestSetup.js',
10+
'./custom/test/__mocks__/logger.js',
11+
'./custom/test/__mocks__/fetchEventSource.js',
12+
],
13+
moduleNameMapper: {
14+
'^~/config$': '<rootDir>/config',
15+
'^~/config/(.*)$': '<rootDir>/config/$1',
16+
'^~/db$': '<rootDir>/db',
17+
'^~/db/(.*)$': '<rootDir>/db/$1',
18+
'^@server/(.*)$': '<rootDir>/server/$1',
19+
'^~/server/(.*)$': '<rootDir>/server/$1',
20+
'^~/(.*)$': '<rootDir>/$1',
21+
'~/data/auth.json': '<rootDir>/custom/__mocks__/auth.mock.json',
22+
'^openid-client/passport$': '<rootDir>/custom/test/__mocks__/openid-client-passport.js', // Mock for the passport strategy part
23+
'^openid-client$': '<rootDir>/custom/test/__mocks__/openid-client.js',
24+
'^winston$': '<rootDir>/custom/test/__mocks__/logger.js',
25+
'^winston-daily-rotate-file$': '<rootDir>/custom/test/__mocks__/logger.js',
26+
},
27+
transformIgnorePatterns: ['/node_modules/(?!(openid-client|oauth4webapi|jose)/).*/'],
28+
testMatch: ['**/?(*.)+(custom.spec).[jt]s?(x)'], // Only run files ending in .custom.spec.js or .custom.spec.ts
29+
testPathIgnorePatterns: [
30+
'<rootDir>/test/',
31+
'<rootDir>/../test/',
32+
'<rootDir>/../api/test/',
33+
],
34+
coveragePathIgnorePatterns: [
35+
'<rootDir>/test/',
36+
'<rootDir>/../test/',
37+
'<rootDir>/../api/test/',
38+
],
39+
};

api/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"test": "cross-env NODE_ENV=test jest",
99
"b:test": "NODE_ENV=test bun jest",
1010
"test:ci": "jest --ci",
11+
"test:custom": "jest --config jest.custom.config.js --detectOpenHandles",
1112
"add-balance": "node ./add-balance.js",
1213
"list-balances": "node ./list-balances.js",
1314
"user-stats": "node ./user-stats.js",

0 commit comments

Comments
 (0)