Skip to content

Commit a113dec

Browse files
committed
fix: don't run tests over HTTP, run natively without spawning HTTP server
1 parent 07f5c64 commit a113dec

10 files changed

Lines changed: 153 additions & 17 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ plugins/*
33
!plugins/README.md
44
adapters/*
55
!adapters/README.md
6-
background-jobs-dbs
6+
background-jobs-dbs
7+
tests/jest_tests/db/

tests/README

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
To run tests:
2-
1) Run `task start_app`
3-
2) Run `task run_tests`
2+
1) Run `task run_tests`

tests/Taskfile.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
version: '3'
22
tasks:
33
start_app:
4-
desc: "Start the application server for testing"
4+
desc: "Start the application server manually for debugging"
55
cmds:
66
- "cd application && pnpm migrate:local && pnpm install && pnpm start"
77

@@ -13,4 +13,5 @@ tasks:
1313
run_tests:
1414
desc: "Run unit tests"
1515
cmds:
16+
- "cd application && pnpm install"
1617
- "cd jest_tests && pnpm install && pnpm test"

tests/application/index.ts

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import usersResource from "./resources/adminuser.js";
44
import { fileURLToPath } from 'url';
55
import path from 'path';
66
import { Filters } from 'adminforth';
7-
import { logger } from 'adminforth';
87
import cars_SQLITE_resource from './resources/cars_sl_allow_create.js';
98

109
import cars_sl_dont_allow_create from './resources/cars_sl_dont_allow_create.js';
@@ -23,6 +22,15 @@ import carsDescriptionImage from '../../dev-demo/resources/cars_description_imag
2322
import passkeysResource from '../../dev-demo/resources/passkeys.js';
2423

2524
const ADMIN_BASE_URL = '';
25+
const appFilePath = fileURLToPath(import.meta.url);
26+
const appDir = path.dirname(appFilePath);
27+
const sqliteDbPath = path.join(appDir, '.db.sqlite');
28+
const customComponentsDir = path.join(appDir, 'custom');
29+
30+
process.env.ADMINFORTH_SECRET ??= '123';
31+
process.env.NODE_ENV ??= 'test';
32+
process.env.SQLITE_URL ??= `sqlite://${sqliteDbPath}`;
33+
process.env.SQLITE_FILE_URL ??= `file:${sqliteDbPath}`;
2634

2735
export const admin = new AdminForth({
2836
baseUrl: ADMIN_BASE_URL,
@@ -41,6 +49,7 @@ export const admin = new AdminForth({
4149
},
4250
},
4351
customization: {
52+
customComponentsDir,
4453
brandName: "application",
4554
title: "application",
4655
favicon: '@@/assets/favicon.png',
@@ -105,7 +114,7 @@ const port = 3333;
105114

106115
admin.express.serve(app);
107116

108-
admin.discoverDatabases().then(async () => {
117+
const appReady = admin.discoverDatabases().then(async () => {
109118
if (await admin.resource('adminuser').count() === 0) {
110119
await admin.resource('adminuser').create({
111120
email: 'adminforth',
@@ -115,6 +124,35 @@ admin.discoverDatabases().then(async () => {
115124
}
116125
});
117126

118-
admin.express.listen(port, () => {});
127+
async function closeApplication() {
128+
await Promise.all(Object.values(admin.connectors).map(async (connector: any) => {
129+
if (typeof connector?.close === 'function') {
130+
await connector.close();
131+
}
132+
}));
133+
134+
if (admin.express.server?.listening) {
135+
await new Promise<void>((resolve, reject) => {
136+
admin.express.server.close((error) => {
137+
if (error) {
138+
reject(error);
139+
return;
140+
}
141+
resolve();
142+
});
143+
});
144+
}
145+
}
146+
147+
const isMainModule = process.argv[1] ? path.resolve(process.argv[1]) === appFilePath : false;
148+
149+
if (isMainModule) {
150+
appReady.then(() => {
151+
admin.express.listen(port, () => {});
152+
}).catch((error) => {
153+
console.error(error);
154+
process.exitCode = 1;
155+
});
156+
}
119157

120-
export { app };
158+
export { app, appReady, closeApplication };

tests/jest_tests/CRUD_sqlite.test.ts

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import request from 'supertest';
2+
import { agent, app, closeApplication } from './testApp';
23

3-
const agent = request.agent('localhost:3333');
44
let authCookie: string;
55

6+
afterAll(async () => {
7+
await closeApplication();
8+
});
9+
610
describe('POST /create_record', () => {
711
const requestBody: any ={
812
"resourceId": "cars_sl",
@@ -31,13 +35,17 @@ describe('POST /create_record', () => {
3135
password: 'adminforth',
3236
});
3337
expect(res.status).toEqual(200);
34-
authCookie = res.headers['set-cookie']?.[0];
38+
authCookie = res.headers['set-cookie']?.[0]?.split(';')[0];
3539
expect(authCookie).toContain('adminforth_');
3640
});
3741

38-
it('throw an error, that unauthorized access is blocked after login', async () => {
39-
const res = await agent
40-
.post('/adminapi/v1/create_record');
42+
it('blocks create_record for unauthenticated requests', async () => {
43+
const res = await request(app)
44+
.post('/adminapi/v1/create_record')
45+
.send({
46+
...requestBody,
47+
resourceId: 'cars_sl',
48+
});
4149
expect(res.status).toEqual(401);
4250
});
4351

@@ -298,7 +306,7 @@ describe('POST /update_record', () => {
298306
password: 'adminforth',
299307
});
300308
expect(res.status).toEqual(200);
301-
authCookie = res.headers['set-cookie']?.[0];
309+
authCookie = res.headers['set-cookie']?.[0]?.split(';')[0];
302310
expect(authCookie).toContain('adminforth_');
303311
});
304312

@@ -377,6 +385,11 @@ describe('POST /update_record', () => {
377385
.post('/adminapi/v1/update_record')
378386
.send({
379387
resourceId: 'non_existent_resource',
388+
recordId: '21345667',
389+
meta: {},
390+
record: {
391+
model: 'Abobus2',
392+
},
380393
});
381394
expect(res.status).toEqual(200);
382395
expect(res.body.error).toBe("Resource 'non_existent_resource' not found");
@@ -676,6 +689,31 @@ describe('POST /get_resource_data', () => {
676689
});
677690
});
678691

692+
it('returns list data for boolean filters without mutating the count path input', async () => {
693+
const res = await agent
694+
.set('Cookie', authCookie)
695+
.post('/adminapi/v1/get_resource_data')
696+
.send({
697+
resourceId: 'cars_sl',
698+
source: 'list',
699+
limit: 1,
700+
offset: 0,
701+
sort: [{ field: 'price', direction: 'desc' }],
702+
filters: [
703+
{ field: 'id', operator: 'eq', value: createdRecordId },
704+
{ field: 'listed', operator: 'eq', value: true },
705+
],
706+
});
707+
708+
expect(res.status).toEqual(200);
709+
expect(res.body.error).toBeUndefined();
710+
expect(res.body.total).toBeGreaterThanOrEqual(1);
711+
expect(res.body.data[0]).toMatchObject({
712+
id: createdRecordId,
713+
listed: true,
714+
});
715+
});
716+
679717
describe('POST /get_resource', () => {
680718
beforeAll(async () => {
681719
const res = await agent
@@ -685,7 +723,7 @@ describe('POST /get_resource_data', () => {
685723
password: 'adminforth',
686724
});
687725
expect(res.status).toEqual(200);
688-
authCookie = res.headers['set-cookie']?.[0];
726+
authCookie = res.headers['set-cookie']?.[0]?.split(';')[0];
689727
expect(authCookie).toContain('adminforth_');
690728
});
691729

@@ -846,6 +884,7 @@ describe('POST /delete_record', () => {
846884
.post('/adminapi/v1/delete_record')
847885
.send({
848886
resourceId: 'non_existent_resource',
887+
primaryKey: 'non_existent_id',
849888
});
850889
expect(res.status).toEqual(200);
851890
expect(res.body.error).toBe("Resource 'non_existent_resource' not found");

tests/jest_tests/jest.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ export default {
22
preset: 'ts-jest/presets/default-esm',
33
testEnvironment: 'node',
44
extensionsToTreatAsEsm: ['.ts'],
5+
resolver: './resolver.cjs',
56
transform: {
6-
'^.+\\.ts$': ['ts-jest', { useESM: true }],
7+
'^.+\\.ts$': ['ts-jest', { useESM: true, diagnostics: false }],
78
},
89

910
transformIgnorePatterns: [

tests/jest_tests/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"main": "index.js",
66
"type": "module",
77
"scripts": {
8-
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
8+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --runInBand",
99
"test:watch": "jest --watchAll"
1010
},
1111
"keywords": [],

tests/jest_tests/resolver.cjs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const fs = require('node:fs');
2+
const path = require('node:path');
3+
4+
module.exports = (request, options) => {
5+
const isRelativeJavaScriptImport = (request.startsWith('./') || request.startsWith('../')) && request.endsWith('.js');
6+
7+
if (isRelativeJavaScriptImport) {
8+
const requestedPath = path.resolve(options.basedir, request);
9+
10+
if (!fs.existsSync(requestedPath)) {
11+
const typescriptPath = requestedPath.slice(0, -3) + '.ts';
12+
if (fs.existsSync(typescriptPath)) {
13+
return options.defaultResolver(typescriptPath, options);
14+
}
15+
}
16+
}
17+
18+
return options.defaultResolver(request, options);
19+
};

tests/jest_tests/testApp.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { execFile } from 'node:child_process';
2+
import fs from 'node:fs/promises';
3+
import path from 'node:path';
4+
import { fileURLToPath } from 'node:url';
5+
import { promisify } from 'node:util';
6+
import request from 'supertest';
7+
8+
const execFileAsync = promisify(execFile);
9+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
10+
const applicationDir = path.resolve(currentDir, '../application');
11+
const sqliteDbPath = path.join(applicationDir, '.db.sqlite');
12+
13+
const testEnv = {
14+
...process.env,
15+
ADMINFORTH_SECRET: process.env.ADMINFORTH_SECRET ?? '123',
16+
NODE_ENV: process.env.NODE_ENV ?? 'test',
17+
SQLITE_URL: process.env.SQLITE_URL ?? `sqlite://${sqliteDbPath}`,
18+
SQLITE_FILE_URL: process.env.SQLITE_FILE_URL ?? `file:${sqliteDbPath}`,
19+
};
20+
21+
process.env.ADMINFORTH_SECRET = testEnv.ADMINFORTH_SECRET;
22+
process.env.NODE_ENV = testEnv.NODE_ENV;
23+
process.env.SQLITE_URL = testEnv.SQLITE_URL;
24+
process.env.SQLITE_FILE_URL = testEnv.SQLITE_FILE_URL;
25+
26+
await fs.rm(sqliteDbPath, { force: true });
27+
await execFileAsync('pnpm', ['migrate:local'], {
28+
cwd: applicationDir,
29+
env: testEnv,
30+
});
31+
32+
const { app, appReady, closeApplication } = await import('../application/index');
33+
await appReady;
34+
35+
const agent = request.agent(app);
36+
37+
export { agent, app, closeApplication };

tests/jest_tests/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"moduleResolution": "node",
66
"esModuleInterop": true,
77
"allowSyntheticDefaultImports": true,
8+
"isolatedModules": true,
89
"types": ["jest", "node"],
910
"strict": true
1011
}

0 commit comments

Comments
 (0)