Skip to content

Commit f5de846

Browse files
authored
Merge pull request #13 from DouglasNeuroInformatics/dev
a number of changes
2 parents 31f4934 + 84408f6 commit f5de846

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+2325
-1659
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,6 @@ dist
134134

135135
# local scripts for linking
136136
local/
137+
138+
# example build
139+
build/

example/app.test.ts

Lines changed: 34 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,79 @@
1-
import { randomBytes } from 'node:crypto';
2-
import type { IncomingMessage, Server, ServerResponse } from 'node:http';
1+
import { randomBytes } from 'crypto';
32

4-
import type { NestExpressApplication } from '@nestjs/platform-express';
5-
import request from 'supertest';
6-
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
3+
import { beforeEach } from 'vitest';
74

8-
import appContainer from './app.js';
5+
import { e2e } from '../src/testing/index.js';
96

10-
import type { BaseEnv } from '../src/schemas/env.schema.js';
7+
import type { EndToEndContext } from '../src/testing/index.js';
118
import type { CreateCatDto } from './cats/dto/create-cat.dto.js';
129

13-
const env = {
14-
API_PORT: '5500',
15-
DEBUG: 'false',
16-
MONGO_URI: 'mongodb://localhost:27017',
17-
NODE_ENV: 'test',
18-
SECRET_KEY: '2622d72669dd194b98cffd9098b0d04b',
19-
VERBOSE: 'false'
20-
} satisfies { [K in keyof BaseEnv]?: string };
21-
22-
describe('e2e (example)', () => {
23-
let app: NestExpressApplication;
24-
let server!: Server<typeof IncomingMessage, typeof ServerResponse>;
25-
26-
beforeAll(async () => {
27-
Object.entries(env).forEach(([key, value]) => {
28-
vi.stubEnv(key, value);
29-
});
30-
app = appContainer.getApplicationInstance();
31-
await app.init();
32-
server = app.getHttpServer();
33-
});
34-
35-
afterAll(async () => {
36-
if (app) {
37-
await app.close();
38-
app.flushLogs();
39-
}
40-
});
41-
42-
describe('/spec.json', () => {
43-
it('should configure the documentation', async () => {
44-
const response = await request(server).get('/spec.json');
10+
e2e((describe) => {
11+
describe('/spec.json', (it) => {
12+
it('should configure the documentation', async ({ api, expect }) => {
13+
const response = await api.get('/spec.json');
4514
expect(response.status).toBe(200);
4615
});
4716
});
4817

49-
describe('/auth/login', () => {
50-
it('should return status code 400 if the request body does not include login credentials', async () => {
51-
const response = await request(server).post('/v1/auth/login');
18+
describe('/auth/login', (it) => {
19+
it('should return status code 400 if the request body does not include credentials', async ({ api, expect }) => {
20+
const response = await api.post('/v1/auth/login');
5221
expect(response.status).toBe(400);
5322
});
54-
it('should return status code 400 if the request body does not include a username', async () => {
55-
const response = await request(server).post('/v1/auth/login').send({ username: 'admin' });
23+
it('should return status code 400 if the request body does not include a username', async ({ api, expect }) => {
24+
const response = await api.post('/v1/auth/login').send({ username: 'admin' });
5625
expect(response.status).toBe(400);
5726
});
58-
it('should return status code 400 if the request body does not include a password', async () => {
59-
const response = await request(server).post('/v1/auth/login').send({ password: 'password' });
27+
it('should return status code 400 if the request body does not include a password', async ({ api, expect }) => {
28+
const response = await api.post('/v1/auth/login').send({ password: 'password' });
6029
expect(response.status).toBe(400);
6130
});
62-
it('should return status code 400 if the request body includes a username and password, but are empty strings', async () => {
63-
const response = await request(server).post('/v1/auth/login').send({ password: '', username: '' });
31+
it('should return status code 400 if username and password are empty strings', async ({ api, expect }) => {
32+
const response = await api.post('/v1/auth/login').send({ password: '', username: '' });
6433
expect(response.status).toBe(400);
6534
});
66-
it('should return status code 400 if the request body includes a username and password, but password is a number', async () => {
67-
const response = await request(server).post('/v1/auth/login').send({ password: 123, username: 'admin' });
35+
it('should return status code 400 if password is a number', async ({ api, expect }) => {
36+
const response = await api.post('/v1/auth/login').send({ password: 123, username: 'admin' });
6837
expect(response.status).toBe(400);
6938
});
70-
it('should return status code 401 if the user does not exist', async () => {
71-
const response = await request(server).post('/v1/auth/login').send({ password: 'password', username: 'user' });
39+
it('should return status code 401 if the user does not exist', async ({ api, expect }) => {
40+
const response = await api.post('/v1/auth/login').send({ password: 'password', username: 'user' });
7241
expect(response.status).toBe(401);
7342
});
74-
it('should return status code 200 and an access token if the credentials are correct', async () => {
75-
const response = await request(server).post('/v1/auth/login').send({ password: 'password', username: 'admin' });
43+
it('should return status code 200 and an access token if the credentials are correct', async ({ api, expect }) => {
44+
const response = await api.post('/v1/auth/login').send({ password: 'password', username: 'admin' });
7645
expect(response.status).toBe(200);
7746
expect(response.body).toStrictEqual({
7847
accessToken: expect.stringMatching(/^[A-Za-z0-9-_]+\.([A-Za-z0-9-_]+)\.[A-Za-z0-9-_]+$/)
7948
});
8049
});
8150
});
8251

83-
describe('/cats', () => {
52+
describe('/cats', (it) => {
8453
let accessToken: string;
8554

86-
beforeAll(async () => {
87-
const response = await request(server).post('/v1/auth/login').send({ password: 'password', username: 'admin' });
55+
beforeEach<EndToEndContext>(async ({ api }) => {
56+
const response = await api.post('/v1/auth/login').send({ password: 'password', username: 'admin' });
8857
accessToken = response.body.accessToken;
8958
});
9059

91-
it('should return status code 401 if there is no access token provided', async () => {
92-
const response = await request(server).get('/v1/cats');
60+
it('should return status code 401 if there is no access token provided', async ({ api, expect }) => {
61+
const response = await api.get('/v1/cats');
9362
expect(response.status).toBe(401);
9463
});
9564

96-
it('should return status code 401 if there is an invalid access token provided', async () => {
97-
const response = await request(server)
98-
.get('/v1/cats')
99-
.set('Authorization', `Bearer ${randomBytes(12).toString('base64')}`);
65+
it('should return status code 401 if there is an invalid access token provided', async ({ api, expect }) => {
66+
const response = await api.get('/v1/cats').set('Authorization', `Bearer ${randomBytes(12).toString('base64')}`);
10067
expect(response.status).toBe(401);
10168
});
10269

103-
it('should allow a GET request', async () => {
104-
const response = await request(server).get('/v1/cats').set('Authorization', `Bearer ${accessToken}`);
70+
it('should allow a GET request', async ({ api, expect }) => {
71+
const response = await api.get('/v1/cats').set('Authorization', `Bearer ${accessToken}`);
10572
expect(response.status).toBe(200);
10673
});
10774

108-
it('should allow a POST request', async () => {
109-
const response = await request(server)
75+
it('should allow a POST request', async ({ api, expect }) => {
76+
const response = await api
11077
.post('/v1/cats')
11178
.set('Authorization', `Bearer ${accessToken}`)
11279
.send({

libnest.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
import * as path from 'node:path';
2+
13
import { defineUserConfig } from './src/user-config.js';
24

35
export default defineUserConfig({
6+
build: {
7+
outfile: path.resolve(import.meta.dirname, 'build/server.js')
8+
},
49
entry: () => import('./example/app.js')
510
});

package.json

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@douglasneuroinformatics/libnest",
33
"type": "module",
44
"version": "2.1.0",
5-
"packageManager": "pnpm@9.15.4",
5+
"packageManager": "pnpm@10.6.3",
66
"description": "Generic NestJS decorators, pipes, modules, and utilities used across DNP projects",
77
"author": "Joshua Unrau",
88
"license": "Apache-2.0",
@@ -29,7 +29,7 @@
2929
}
3030
},
3131
"bin": {
32-
"libnest": "./dist/cli/bin.js"
32+
"libnest": "./dist/cli/bin/libnest"
3333
},
3434
"files": [
3535
"dist"
@@ -38,86 +38,122 @@
3838
"node": "22.x"
3939
},
4040
"scripts": {
41-
"build": "rm -rf dist && tsc -b tsconfig.build.json",
41+
"build": "rm -rf dist && tsc -b tsconfig.build.json && cp -r src/cli/bin dist/cli/bin",
4242
"docs:build": "typedoc",
43-
"docs:dev": "concurrently \"typedoc --watch\" \"http-server docs -c-1\"",
44-
"example:dev": "env-cmd -f .env.example ./src/cli/bin.js dev -c libnest.config.ts",
43+
"docs:dev": "concurrently \"typedoc --watch\" \"pnpm docs:serve\"",
44+
"docs:serve": "http-server docs -c-1",
45+
"example:build": "rm -rf build && env-cmd -f .env.example ./src/cli/bin/libnest -c libnest.config.ts build",
46+
"example:dev": "env-cmd -f .env.example ./src/cli/bin/libnest dev -c libnest.config.ts",
47+
"example:start": "env-cmd -f .env.example node build/server.js",
4548
"format": "prettier --write src",
4649
"knip": "knip",
47-
"lint": "tsc && eslint --fix src",
50+
"lint": "tsc && eslint --fix example src",
4851
"prepare": "husky",
4952
"prisma:generate": "prisma generate --allow-no-models",
5053
"test": "vitest",
5154
"test:coverage": "vitest --coverage"
5255
},
5356
"peerDependencies": {
54-
"@nestjs/common": "^11.0.5",
55-
"@nestjs/core": "^11.0.5",
56-
"@nestjs/platform-express": "^11.0.5",
57-
"@nestjs/testing": "^11.0.5",
58-
"@prisma/client": "^6.2.1",
57+
"@nestjs/common": "^11.0.11",
58+
"@nestjs/core": "^11.0.11",
59+
"@nestjs/platform-express": "^11.0.11",
60+
"@nestjs/testing": "^11.0.11",
61+
"@prisma/client": "^6.5.0",
5962
"express": "^5.0.1",
60-
"mongodb": "^6.10.0",
63+
"mongodb": "^6.14.2",
6164
"neverthrow": "^8.2.0",
6265
"reflect-metadata": "~0.1.13",
63-
"rxjs": "^7.8.1",
66+
"rxjs": "^7.8.2",
6467
"zod": "^3.22.6"
6568
},
6669
"dependencies": {
6770
"@casl/ability": "^6.7.3",
6871
"@casl/prisma": "^1.5.1",
69-
"@douglasneuroinformatics/libjs": "^2.3.0",
72+
"@douglasneuroinformatics/libjs": "^2.4.0",
7073
"@nestjs/jwt": "^11.0.0",
7174
"@nestjs/passport": "^11.0.5",
72-
"@nestjs/swagger": "^11.0.3",
75+
"@nestjs/swagger": "^11.0.6",
7376
"@nestjs/throttler": "^6.4.0",
74-
"@swc-node/register": "^1.10.9",
75-
"@swc/core": "^1.11.7",
77+
"@swc-node/register": "^1.10.10",
78+
"@swc/core": "^1.11.10",
7679
"@swc/helpers": "^0.5.15",
7780
"chalk": "^5.4.1",
7881
"commander": "^13.1.0",
82+
"es-module-lexer": "^1.6.0",
83+
"esbuild": "^0.25.1",
7984
"passport": "^0.7.0",
8085
"passport-jwt": "^4.0.1",
8186
"serialize-error": "^12.0.0",
82-
"type-fest": "^4.33.0"
87+
"type-fest": "^4.37.0"
8388
},
8489
"devDependencies": {
85-
"@douglasneuroinformatics/eslint-config": "^5.3.1",
90+
"@douglasneuroinformatics/eslint-config": "^5.3.2",
8691
"@douglasneuroinformatics/prettier-config": "^0.0.2",
8792
"@douglasneuroinformatics/semantic-release": "^0.2.1",
88-
"@douglasneuroinformatics/tsconfig": "^1.0.2",
93+
"@douglasneuroinformatics/tsconfig": "^1.0.3",
8994
"@types/express": "^5.0.0",
90-
"@types/node": "^22.10.10",
95+
"@types/node": "^22.13.10",
9196
"@types/passport": "^1.0.17",
9297
"@types/passport-jwt": "^4.0.1",
9398
"@types/supertest": "^6.0.2",
94-
"@vitest/coverage-v8": "^3.0.4",
99+
"@vitest/coverage-v8": "^3.0.9",
95100
"concurrently": "^9.1.2",
96101
"env-cmd": "^10.1.0",
97-
"esbuild": "^0.24.2",
98-
"eslint": "9.19.0",
102+
"eslint": "9.22.0",
99103
"http-server": "^14.1.1",
100104
"husky": "^9.1.7",
101-
"knip": "^5.44.1",
102-
"prettier": "^3.4.2",
103-
"prisma": "^6.4.1",
105+
"knip": "^5.46.0",
106+
"prettier": "^3.5.3",
107+
"prisma": "^6.5.0",
104108
"supertest": "^7.0.0",
105-
"typedoc": "^0.27.6",
109+
"typedoc": "^0.27.0",
106110
"typedoc-material-theme": "^1.3.0",
107-
"typedoc-plugin-zod": "^1.3.1",
111+
"typedoc-plugin-zod": "^1.4.0",
108112
"typescript": "5.6.x",
109113
"unplugin-swc": "^1.5.1",
110-
"vitest": "^3.0.4"
114+
"vitest": "^3.0.9"
111115
},
112116
"commitlint": {
113117
"extends": [
114118
"@douglasneuroinformatics/semantic-release/commitlint-config"
115119
]
116120
},
121+
"knip": {
122+
"entry": [
123+
"example/app.ts",
124+
"src/index.ts",
125+
"src/user-config.ts",
126+
"src/testing/index.ts"
127+
],
128+
"ignoreDependencies": [
129+
"@douglasneuroinformatics/tsconfig",
130+
"@swc-node/register",
131+
"@swc/helpers"
132+
],
133+
"ignore": [
134+
"src/typings/globals.ts"
135+
],
136+
"project": [
137+
"{example,src}/**/*.{js,ts}"
138+
]
139+
},
117140
"prettier": "@douglasneuroinformatics/prettier-config",
118141
"release": {
119142
"extends": [
120143
"@douglasneuroinformatics/semantic-release"
121144
]
145+
},
146+
"pnpm": {
147+
"onlyBuiltDependencies": [
148+
"@nestjs/core",
149+
"@prisma/client",
150+
"@prisma/engines",
151+
"@swc/core",
152+
"esbuild",
153+
"prisma"
154+
],
155+
"ignoredBuiltDependencies": [
156+
"@scarf/scarf"
157+
]
122158
}
123159
}

0 commit comments

Comments
 (0)