Browse Source

test: optional auth HTTP integration; docs and db:generate script

Made-with: Cursor
feat/auth-user
npmrun 6 days ago
parent
commit
ba28a7c321
  1. 9
      README.md
  2. 2
      package.json
  3. 57
      test/integration/auth.http.test.ts

9
README.md

@ -9,4 +9,11 @@
## 开发与部署 ## 开发与部署
用 Linux 开发与部署,包管理器采用 bun@1.3.11。数据库为 **postgres**(通过 `DATABASE_URL` 连接;本地可参考 `.env.example` 复制为 `.env.local`)。部署时可直接打包 `.output` 目录,在服务器环境执行迁移命令,省时省力。 用 Linux 开发与部署,包管理器采用 bun@1.3.11。数据库为 **postgres**(通过 `DATABASE_URL` 连接;本地可参考 `.env.example` 复制为 `.env.local`)。部署时可直接打包 `.output` 目录,在服务器环境执行迁移命令,省时省力。
会话与限流依赖 **Redis**(`REDIS_URL`,见 `.env.example`)。认证相关设计见 `docs/superpowers/specs/2026-04-12-auth-user-design.md`,实现任务见 `docs/superpowers/plans/2026-04-12-auth-user.md`
- 迁移:`bun run db:migrate`(需可用 `DATABASE_URL`)。
- 生成迁移:在仓库根执行 `bun run db:generate -- --name <tag>`(`--` 之后参数传给 `drizzle-kit generate`)。
- 单元测试:`bun run test`。
- 可选 HTTP 集成:终端 A `bun run dev`(需 `DATABASE_URL`、`REDIS_URL`),终端 B `TEST_INTEGRATION=1 NODE_ENV=test bun run test:integration`

2
package.json

@ -12,7 +12,7 @@
"cp:db": "cp build-files/run.sh .output/run.sh && cp .env.example .output/.env && cp build-files/migrate.js .output/server/migrate.js", "cp:db": "cp build-files/run.sh .output/run.sh && cp .env.example .output/.env && cp build-files/migrate.js .output/server/migrate.js",
"migrate:test": "sh scripts/migrate-test.sh", "migrate:test": "sh scripts/migrate-test.sh",
"db:migrate": "bun --elide-lines=0 --filter drizzle-pkg migrate", "db:migrate": "bun --elide-lines=0 --filter drizzle-pkg migrate",
"db:generate": "bun --elide-lines=0 --filter drizzle-pkg generate --name", "db:generate": "bun --elide-lines=0 --filter drizzle-pkg run generate --",
"db:seed": "bun --elide-lines=0 --filter drizzle-pkg seed", "db:seed": "bun --elide-lines=0 --filter drizzle-pkg seed",
"generate": "nuxt generate", "generate": "nuxt generate",
"preview": "nuxt preview", "preview": "nuxt preview",

57
test/integration/auth.http.test.ts

@ -0,0 +1,57 @@
import { describe, it, expect } from "bun:test";
const BASE = process.env.TEST_BASE_URL ?? "http://127.0.0.1:3000";
function parseCookie(res: Response): string {
const raw = res.headers.get("set-cookie");
if (!raw) return "";
return raw.split(";")[0] ?? "";
}
(process.env.TEST_INTEGRATION ? describe : describe.skip)("auth over HTTP", () => {
it("register → me → verify → patch", async () => {
const email = `u${Date.now()}@example.com`;
const reg = await fetch(`${BASE}/api/auth/register`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
email,
password: "password123",
name: "T",
age: 30,
}),
});
expect(reg.status).toBe(200);
const regJson = (await reg.json()) as {
user: { emailVerified: boolean };
_testTokens?: { verify?: string };
};
expect(regJson.user.emailVerified).toBe(false);
const cookie = parseCookie(reg);
expect(cookie.length).toBeGreaterThan(5);
const me1 = await fetch(`${BASE}/api/me`, { headers: { cookie } });
expect(me1.status).toBe(200);
const patch1 = await fetch(`${BASE}/api/me`, {
method: "PATCH",
headers: { cookie, "content-type": "application/json" },
body: JSON.stringify({ name: "T2" }),
});
expect(patch1.status).toBe(403);
const verify = await fetch(`${BASE}/api/auth/verify-email`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ token: regJson._testTokens?.verify }),
});
expect(verify.status).toBe(200);
const patch2 = await fetch(`${BASE}/api/me`, {
method: "PATCH",
headers: { cookie, "content-type": "application/json" },
body: JSON.stringify({ name: "T2" }),
});
expect(patch2.status).toBe(200);
});
});
Loading…
Cancel
Save