forked from bai/curriculum-project-hub
6990082247
三处引用旧工程名/旧资源路径的地方一并更新,它们必须同时改 —— 少改一处 就是静默故障,而不是构建期报错: 1. 部署脚本(deploy_platform.sh / deploy_fleet_release.sh):npm ci 的 prefix、rsync 排除项、构建产物存在性检查从 database-admin 换成 filelib-web。最后一项是真门禁:static.ts 缺产物时只 warn 不注册路由, 漏改会让 /app 与 /database 静默 404 —— 恰是 database-admin 长期处于 禁用状态的原因。 2. silo 限流豁免:资源路径随 appDir 改名而变(/database/_app/* 已不存在, 现为 /_filelib/*);/app/* 此前不在豁免列表,它现在也是 SPA 外壳, 客户端路由无法预先枚举。 注:/database/* 是整体豁免,filelib 的 JSON API 也绕过限流预算。这是 迁移前就有的行为,原样保留,但覆盖面因多了 /app/* 而变宽。 3. 回归测试:把 registerStaticSpa 与 registerDatabaseSpa 挂到同一个 Fastify 实例,断言 ready() 不因重复路由抛错 —— appDir 若用回默认的 _app,这里会红(ADR-0029 的承重约束)。另断言 /app 与 /database/dashboard/users 返回同一份字节(SPA 回退不读请求)、body 含 /_filelib/。构建产物缺失时不 skip 而是直接失败:那说明该先跑 filelib-web 的 build,不是测试不适用。
49 lines
2.4 KiB
TypeScript
49 lines
2.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
isSiloHttpRateLimitExempt,
|
|
SiloFixedWindowRateLimiter,
|
|
} from "../../src/deployment/siloRateLimit.js";
|
|
|
|
describe("Silo fixed-window rate limiter", () => {
|
|
it("returns an explicit retry interval and resets at the next window", () => {
|
|
const limiter = new SiloFixedWindowRateLimiter(2, 60_000, 1_000);
|
|
expect(limiter.consume(1_000)).toEqual({ allowed: true });
|
|
expect(limiter.consume(2_000)).toEqual({ allowed: true });
|
|
expect(limiter.consume(3_000)).toEqual({ allowed: false, retryAfterSeconds: 58 });
|
|
expect(limiter.consume(61_000)).toEqual({ allowed: true });
|
|
});
|
|
});
|
|
|
|
describe("isSiloHttpRateLimitExempt", () => {
|
|
it("exempts healthz, SPA static assets, and the admin HTML shell", () => {
|
|
expect(isSiloHttpRateLimitExempt("/api/healthz")).toBe(true);
|
|
expect(isSiloHttpRateLimitExempt("/_app/version.json")).toBe(true);
|
|
expect(isSiloHttpRateLimitExempt("/_app/immutable/chunks/foo.js?v=1")).toBe(true);
|
|
expect(isSiloHttpRateLimitExempt("/favicon.ico")).toBe(true);
|
|
expect(isSiloHttpRateLimitExempt("/favicon.svg")).toBe(true);
|
|
expect(isSiloHttpRateLimitExempt("/robots.txt")).toBe(true);
|
|
expect(isSiloHttpRateLimitExempt("/admin")).toBe(true);
|
|
expect(isSiloHttpRateLimitExempt("/admin/members")).toBe(true);
|
|
expect(isSiloHttpRateLimitExempt("/admin/org/para-26071100/members")).toBe(true);
|
|
});
|
|
|
|
it("exempts the filelib-web SPA assets and both of its mount prefixes", () => {
|
|
// appDir 改名为 `_filelib`(见 src/database/static.ts):同一份产物服务
|
|
// 老师端 /app 与管理后台 /database。
|
|
expect(isSiloHttpRateLimitExempt("/_filelib/version.json")).toBe(true);
|
|
expect(isSiloHttpRateLimitExempt("/_filelib/immutable/chunks/foo.js?v=1")).toBe(true);
|
|
expect(isSiloHttpRateLimitExempt("/app")).toBe(true);
|
|
expect(isSiloHttpRateLimitExempt("/app/dev-login-teacher")).toBe(true);
|
|
expect(isSiloHttpRateLimitExempt("/database")).toBe(true);
|
|
expect(isSiloHttpRateLimitExempt("/database/dashboard/users")).toBe(true);
|
|
});
|
|
|
|
it("still rate-limits APIs and auth", () => {
|
|
expect(isSiloHttpRateLimitExempt("/api/me")).toBe(false);
|
|
expect(isSiloHttpRateLimitExempt("/api/org/x/members")).toBe(false);
|
|
expect(isSiloHttpRateLimitExempt("/auth/feishu/x")).toBe(false);
|
|
expect(isSiloHttpRateLimitExempt("/auth/feishu/callback?code=1")).toBe(false);
|
|
expect(isSiloHttpRateLimitExempt("/")).toBe(false);
|
|
});
|
|
});
|