主要变更: - 添加完整的项目结构和模块(admin、articles、comments、users、session、oauth2、email、moderation、analytics、jobs 等) - 实现系统初始化 API(/init/status 和 /init/run) - 重写部署流程:迁移到 package.json scripts,删除 Makefile - 优化部署脚本:deploy.sh、healthcheck.sh、backup.sh、restore.sh、verify-env.sh - 更新 README.md:简化文档,整合部署指南 - 优化 AGENTS.md:精简到约 150 行,包含完整的代码规范和命令速查 - 配置 Docker Compose 自动化部署(prisma migrate deploy + seed) - 生成 OAuth2 RSA 密钥对支持 - 添加环境变量验证和数据库备份恢复功能
131 lines
4.1 KiB
TypeScript
131 lines
4.1 KiB
TypeScript
import { Test, TestingModule } from '@nestjs/testing';
|
|
import request from 'supertest';
|
|
import { AppModule } from '../../app.module';
|
|
import { INestApplication } from '@nestjs/common';
|
|
|
|
describe('OAuth2 + Session (e2e)', () => {
|
|
let app: INestApplication;
|
|
let sessionCookie: string | undefined;
|
|
|
|
beforeAll(async () => {
|
|
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
imports: [AppModule],
|
|
}).compile();
|
|
app = moduleFixture.createNestApplication();
|
|
await app.init();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await app.close();
|
|
});
|
|
|
|
it('/login (POST) - success', async () => {
|
|
const response = await request(app.getHttpServer())
|
|
.post('/login')
|
|
.send({ emailOrUsername: 'admin@example.com', password: 'password123' })
|
|
.expect(200);
|
|
expect(response.body).toEqual({ ok: true, user: expect.any(Object) });
|
|
const setCookie = response.headers['set-cookie'] as string[];
|
|
if (setCookie && setCookie.length > 0) {
|
|
sessionCookie = setCookie[0].split(';')[0];
|
|
}
|
|
expect(sessionCookie).toBeDefined();
|
|
});
|
|
|
|
it('/me (GET) - with session', async () => {
|
|
expect(sessionCookie).toBeDefined();
|
|
await request(app.getHttpServer())
|
|
.get('/me')
|
|
.set('Cookie', sessionCookie!)
|
|
.expect(200);
|
|
});
|
|
|
|
it('/oauth2/authorize - not logged in, redirect to login', async () => {
|
|
const query = new URLSearchParams({
|
|
response_type: 'code',
|
|
client_id: 'web-client',
|
|
redirect_uri: 'http://localhost:3000/auth/callback',
|
|
scope: 'read write',
|
|
state: 'teststate',
|
|
}).toString();
|
|
const response = await request(app.getHttpServer())
|
|
.get(`/oauth2/authorize?${query}`)
|
|
.expect(302);
|
|
expect(response.headers.location).toContain('/login?next=');
|
|
});
|
|
|
|
it('/oauth2/authorize - after login, redirect with code', async () => {
|
|
// 假设 sessionCookie 已存在
|
|
expect(sessionCookie).toBeDefined();
|
|
const query = new URLSearchParams({
|
|
response_type: 'code',
|
|
client_id: 'web-client',
|
|
redirect_uri: 'http://localhost:3000/auth/callback',
|
|
scope: 'read write',
|
|
state: 'teststate',
|
|
}).toString();
|
|
const response = await request(app.getHttpServer())
|
|
.get(`/oauth2/authorize?${query}`)
|
|
.set('Cookie', sessionCookie!)
|
|
.expect(302);
|
|
const location = response.headers.location;
|
|
expect(location).toContain('http://localhost:3000/auth/callback?code=');
|
|
expect(location).toContain('state=teststate');
|
|
});
|
|
|
|
it('/oauth2/token - authorization_code grant', async () => {
|
|
// 先从 authorize 获取 code
|
|
const query = new URLSearchParams({
|
|
response_type: 'code',
|
|
client_id: 'web-client',
|
|
redirect_uri: 'http://localhost:3000/auth/callback',
|
|
scope: 'read write',
|
|
state: 'teststate',
|
|
}).toString();
|
|
const authResponse = await request(app.getHttpServer())
|
|
.get(`/oauth2/authorize?${query}`)
|
|
.set('Cookie', sessionCookie!);
|
|
const location = authResponse.headers.location;
|
|
const code = new URL(location).searchParams.get('code');
|
|
expect(code).toBeDefined();
|
|
|
|
// 用 code 换 token
|
|
const tokenResponse = await request(app.getHttpServer())
|
|
.post('/oauth2/token')
|
|
.type('form')
|
|
.send({
|
|
grant_type: 'authorization_code',
|
|
code,
|
|
redirect_uri: 'http://localhost:3000/auth/callback',
|
|
client_id: 'web-client',
|
|
client_secret: 'change_me',
|
|
})
|
|
.expect(200);
|
|
expect(tokenResponse.body).toEqual({
|
|
access_token: expect.any(String),
|
|
token_type: 'Bearer',
|
|
expires_in: expect.any(Number),
|
|
refresh_token: expect.any(String),
|
|
scope: 'read write',
|
|
});
|
|
});
|
|
|
|
it('/oauth2/token - client_credentials grant', async () => {
|
|
const response = await request(app.getHttpServer())
|
|
.post('/oauth2/token')
|
|
.type('form')
|
|
.send({
|
|
grant_type: 'client_credentials',
|
|
client_id: 'web-client',
|
|
client_secret: 'change_me',
|
|
})
|
|
.expect(200);
|
|
expect(response.body).toEqual({
|
|
access_token: expect.any(String),
|
|
token_type: 'Bearer',
|
|
expires_in: expect.any(Number),
|
|
scope: 'read write',
|
|
});
|
|
});
|
|
});
|