docs: 基本完成初版数据库设计

This commit is contained in:
R524809
2026-10-15 10:09:34 +08:00
parent 63a6993276
commit e6ded00a4c
41 changed files with 2597 additions and 180 deletions
+306
View File
@@ -0,0 +1,306 @@
import 'reflect-metadata';
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { Body, Controller, Get, Post } from '@nestjs/common';
import type { NestExpressApplication } from '@nestjs/platform-express';
import { Test } from '@nestjs/testing';
import { JwtService } from '@nestjs/jwt';
import type { OpenAPIObject } from '@nestjs/swagger';
import { IsString, MaxLength } from 'class-validator';
import { AppModule } from '../src/app.module';
import { configureApplication } from '../src/app.setup';
import { createApplication } from '../src/app.factory';
import { AppLogger } from '../src/logging/app-logger';
import { ACCOUNT_READER, type Account } from '../src/auth/account-reader';
import { CurrentUser, Public, Roles } from '../src/auth/auth.decorators';
import { JwtTokenService } from '../src/auth/jwt-token.service';
import type { AuthenticatedUser } from '../src/common/http-request';
import { testConfig } from './test-config';
class EchoDto {
@IsString()
@MaxLength(100)
message!: string;
}
// 只在测试模块注册,生产不增加探测身份或签发 token 的路由。
@Controller('probe')
class ProbeController {
@Get('me')
me(@CurrentUser() user: AuthenticatedUser) {
return user;
}
@Get('admin')
@Roles('admin')
admin() {
return { ok: true };
}
@Public()
@Post('echo')
echo(@Body() body: EchoDto) {
return body;
}
@Public()
@Get('failure')
failure() {
throw new Error('private-internal-error');
}
}
test('HTTP 中间件、校验、日志、Swagger 与 JWT 全局守卫', async (context) => {
const logs: string[] = [];
const logger = new AppLogger('info', (line) => logs.push(line));
const config = {
...testConfig,
bodyLimitBytes: 1024,
corsOrigins: ['http://localhost:3001'],
};
// 用可变账户快照模拟禁用、角色和凭证失效,不依赖真实用户或开发数据库。
const accounts = new Map<string, Account>([
[
'UserA',
{ id: 'UserA', authVersion: 1, role: 'user', status: 'active' },
],
[
'AdminA',
{ id: 'AdminA', authVersion: 1, role: 'admin', status: 'active' },
],
]);
const module = await Test.createTestingModule({
imports: [AppModule.forRoot(config)],
controllers: [ProbeController],
})
.overrideProvider(ACCOUNT_READER)
.useValue({ findById: async (id: string) => accounts.get(id) ?? null })
.compile();
const app = module.createNestApplication<NestExpressApplication>({
bodyParser: false,
logger,
});
context.after(() => app.close());
configureApplication(app, config, logger);
await app.listen(0, '127.0.0.1');
const origin = await app.getUrl();
const tokens = app.get(JwtTokenService);
const jwt = app.get(JwtService);
const userToken = await tokens.signAccessToken('UserA', 1);
const request = (path: string, options?: RequestInit) =>
fetch(`${origin}${path}`, {
...options,
signal: AbortSignal.timeout(5000),
});
const health = await request('/api/v1/health?code=private-login-code', {
headers: {
'x-request-id': 'request_test_123',
Origin: 'http://localhost:3001',
},
});
assert.equal(health.status, 200);
assert.equal(health.headers.get('x-request-id'), 'request_test_123');
assert.equal(
health.headers.get('access-control-allow-origin'),
'http://localhost:3001',
);
assert.equal(health.headers.get('x-content-type-options'), 'nosniff');
assert.equal(health.headers.get('x-powered-by'), null);
const deniedOrigin = await request('/api/v1/health', {
headers: { Origin: 'https://untrusted.example' },
});
assert.equal(deniedOrigin.headers.get('access-control-allow-origin'), null);
const missingToken = await request('/api/v1/probe/me');
assert.equal(missingToken.status, 401);
const errorBody = (await missingToken.json()) as {
code: number;
requestId: string;
};
assert.equal(errorBody.code, 401);
assert.equal(errorBody.requestId, missingToken.headers.get('x-request-id'));
const authenticated = await request('/api/v1/probe/me', {
headers: { Authorization: `Bearer ${userToken}` },
});
assert.equal(authenticated.status, 200);
assert.deepEqual(await authenticated.json(), {
userId: 'UserA',
authVersion: 1,
role: 'user',
});
assert.equal(
(
await request('/api/v1/probe/admin', {
headers: { Authorization: `Bearer ${userToken}` },
})
).status,
403,
);
assert.equal(
(
await request('/api/v1/probe/admin', {
headers: {
Authorization: `Bearer ${await tokens.signAccessToken('AdminA', 1)}`,
},
})
).status,
200,
);
// 用同一 JWT 库构造异常凭证,验证算法、签发方、受众和字段校验均会生效。
const sign = (claims: object, overrides: object = {}) =>
jwt.signAsync(claims, {
secret: config.jwtSecret,
algorithm: 'HS256',
issuer: config.jwtIssuer,
audience: config.jwtAudience,
expiresIn: 60,
...overrides,
});
for (const token of [
'invalid-token',
await tokens.signAccessToken('UserA', 0),
await tokens.signAccessToken('DeletedUser', 1),
await sign({ sub: 'UserA', authVersion: 1 }, { expiresIn: -1 }),
await sign({ sub: 'UserA', authVersion: 1 }, { audience: 'other' }),
await sign({ sub: 'UserA', authVersion: 1 }, { issuer: 'other' }),
await sign({ sub: 'UserA', authVersion: 1 }, { algorithm: 'HS384' }),
await sign(
{ sub: 'UserA', authVersion: 1 },
{ secret: 'wrong-secret' },
),
await sign({ sub: 'UserA', authVersion: '1' }),
await jwt.signAsync(
{ sub: 'UserA', authVersion: 1 },
{
secret: config.jwtSecret,
issuer: config.jwtIssuer,
audience: config.jwtAudience,
},
),
])
assert.equal(
(
await request('/api/v1/probe/me', {
headers: { Authorization: `Bearer ${token}` },
})
).status,
401,
);
// 即使签名正确,token 自报 admin 也不能覆盖服务端账户中的 user 角色。
const forgedRole = await sign({
sub: 'UserA',
authVersion: 1,
role: 'admin',
});
assert.equal(
(
await request('/api/v1/probe/admin', {
headers: { Authorization: `Bearer ${forgedRole}` },
})
).status,
403,
);
accounts.get('UserA')!.status = 'disabled';
assert.equal(
(
await request('/api/v1/probe/me', {
headers: { Authorization: `Bearer ${userToken}` },
})
).status,
401,
);
const echo = (body: string, contentType = 'application/json') =>
request('/api/v1/probe/echo', {
method: 'POST',
headers: { 'Content-Type': contentType },
body,
});
assert.equal((await echo('{"message":"ok"}')).status, 201);
assert.equal(
(await echo('message=ok', 'application/x-www-form-urlencoded')).status,
201,
);
assert.equal((await echo('{"message":12}')).status, 400);
assert.equal((await echo('{"message":"ok","extra":true}')).status, 400);
const malformed = await echo('{broken-json');
assert.equal(malformed.status, 400);
assert.ok(malformed.headers.get('x-request-id'));
assert.equal(
(await echo(JSON.stringify({ message: 'x'.repeat(2000) }))).status,
413,
);
const failure = await request('/api/v1/probe/failure');
assert.equal(failure.status, 500);
assert.ok(!(await failure.text()).includes('private-internal-error'));
assert.equal((await request('/api-docs')).status, 200);
const openapi = (await (
await request('/api-docs-json')
).json()) as OpenAPIObject;
assert.ok(openapi.paths['/api/v1/health']);
assert.deepEqual(openapi.paths['/api/v1/health']?.get?.security, []);
assert.equal(
(
openapi.components?.securitySchemes?.['access-token'] as {
scheme: string;
}
).scheme,
'bearer',
);
// 检查完整请求链日志,确认解析失败仍有日志,且查询参数、异常原文和 token 未泄漏。
const requestLogs = logs.filter(
(line) => JSON.parse(line).message === 'http_request_completed',
);
assert.ok(
requestLogs.some(
(line) => JSON.parse(line).context.status_code === 413,
),
);
assert.ok(!logs.join('\n').includes('private-login-code'));
assert.ok(!logs.join('\n').includes('private-internal-error'));
assert.ok(!logs.join('\n').includes(userToken));
});
test('生产默认不暴露 Swagger;账户查询未接入时不放行有效 JWT', async (context) => {
const config = {
...testConfig,
nodeEnv: 'production' as const,
swaggerEnabled: false,
};
const module = await Test.createTestingModule({
imports: [AppModule.forRoot(config)],
controllers: [ProbeController],
}).compile();
const logger = new AppLogger('error', () => {});
const app = module.createNestApplication<NestExpressApplication>({
bodyParser: false,
logger,
});
context.after(() => app.close());
configureApplication(app, config, logger);
await app.listen(0, '127.0.0.1');
const origin = await app.getUrl();
for (const path of ['/api-docs', '/api-docs-json'])
assert.equal((await fetch(`${origin}${path}`)).status, 404);
const token = await app.get(JwtTokenService).signAccessToken('UserA', 1);
assert.equal(
(
await fetch(`${origin}/api/v1/probe/me`, {
headers: { Authorization: `Bearer ${token}` },
})
).status,
503,
);
});
test('导入应用工厂不会监听固定端口', async (context) => {
const app = await createApplication(
testConfig,
new AppLogger('error', () => {}),
);
context.after(() => app.close());
assert.equal(app.getHttpServer().listening, false);
});
+7 -8
View File
@@ -1,19 +1,18 @@
import 'reflect-metadata';
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { Test } from '@nestjs/testing';
import { AppModule } from '../src/app.module';
import { configureApplication } from '../src/app.setup';
import { createApplication } from '../src/app.factory';
import { AppLogger } from '../src/logging/app-logger';
import { testConfig } from './test-config';
test('服务可以启动并通过带版本前缀的 HTTP 健康检查', async (context) => {
const testingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
const app = testingModule.createNestApplication({ logger: false });
const app = await createApplication(
testConfig,
new AppLogger('error', () => {}),
);
context.after(async () => {
await app.close();
});
configureApplication(app);
await app.listen(0, '127.0.0.1');
const origin = await app.getUrl();
+28
View File
@@ -0,0 +1,28 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { AppLogger } from '../src/logging/app-logger';
test('日志为单行 JSON,递归脱敏并安全处理循环对象与 bigint', () => {
const output: string[] = [];
const logger = new AppLogger('info', (line) => output.push(line));
const nested: Record<string, unknown> = {
password: 'private-password',
amount: 9n,
};
nested.self = nested;
logger.log('test_event', {
authorization: 'Bearer private-token',
nested,
body: { code: 'login-secret' },
});
logger.debug('hidden');
assert.equal(output.length, 1);
const log = JSON.parse(output[0]!);
assert.equal(log.context.authorization, '[REDACTED]');
assert.equal(log.context.nested.password, '[REDACTED]');
assert.equal(log.context.nested.self, '[Circular]');
assert.equal(log.context.nested.amount, '9');
assert.equal(log.context.body, '[REDACTED]');
assert.ok(!output[0]!.includes('private-'));
assert.ok(!output[0]!.includes('login-secret'));
});
+57
View File
@@ -0,0 +1,57 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { getServerConfig } from '../src/config/server-config';
const env = { JWT_SECRET: 'test-configuration-secret-12345678901234567890' };
test('启动配置必须提供有效 JWT 密钥,不使用共享默认密钥', () => {
for (const secret of [
undefined,
'',
'short',
'change-me-123456789012345678901234567890',
]) {
assert.throws(
() =>
getServerConfig(
secret === undefined ? {} : { JWT_SECRET: secret },
),
/JWT_SECRET/,
);
}
});
test('Swagger 开发默认开启,生产默认关闭,支持显式开关', () => {
assert.equal(getServerConfig(env).swaggerEnabled, true);
assert.equal(
getServerConfig({ ...env, NODE_ENV: 'production' }).swaggerEnabled,
false,
);
assert.equal(
getServerConfig({ ...env, SWAGGER_ENABLED: 'false' }).swaggerEnabled,
false,
);
});
test('拒绝非法 body、JWT 有效期、日志级别、布尔开关和跨域配置', () => {
for (const entry of [
{ API_BODY_LIMIT_BYTES: '0' },
{ API_BODY_LIMIT_BYTES: '1e6' },
{ JWT_EXPIRES_IN_SECONDS: '0' },
{ SWAGGER_ENABLED: 'yes' },
{ LOG_LEVEL: 'all' },
{ NODE_ENV: 'staging' },
{ JWT_ISSUER: ' ' },
{ CORS_ORIGINS: '*' },
{ CORS_ORIGINS: 'https://example.com/path' },
{ TRUSTED_PROXY_CIDRS: 'true' },
])
assert.throws(() => getServerConfig({ ...env, ...entry }));
assert.deepEqual(
getServerConfig({
...env,
CORS_ORIGINS: 'https://example.com,http://localhost:3001',
}).corsOrigins,
['https://example.com', 'http://localhost:3001'],
);
});
+7
View File
@@ -0,0 +1,7 @@
import { getServerConfig } from '../src/config/server-config';
/** 测试独立配置,不读取本地 .env;固定测试密钥不得用于开发或生产。 */
export const testConfig = getServerConfig({
NODE_ENV: 'test',
JWT_SECRET: 'test-only-secret-123456789012345678901234567890',
});