From 2bfb5f295399ca69a2c31815e0f0d368584825ba Mon Sep 17 00:00:00 2001 From: suyx Date: Tue, 4 Aug 2026 15:39:46 +0800 Subject: [PATCH] test: prove Amap monthly egress hard stop --- tests/api/wp7-04-amap-release-gate.test.ts | 85 ++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 tests/api/wp7-04-amap-release-gate.test.ts diff --git a/tests/api/wp7-04-amap-release-gate.test.ts b/tests/api/wp7-04-amap-release-gate.test.ts new file mode 100644 index 0000000..affc4fd --- /dev/null +++ b/tests/api/wp7-04-amap-release-gate.test.ts @@ -0,0 +1,85 @@ +import { randomUUID } from "node:crypto"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +import { afterEach, describe, expect, it } from "vitest"; + +import { MockAmapAdapter } from "../../apps/api/src/amap-adapter.js"; +import { createApp } from "../../apps/api/src/app.js"; +import { RegistrationService } from "../../apps/api/src/registration.js"; +import { MockResendAdapter } from "../../apps/api/src/resend-adapter.js"; + +const roots: string[] = []; +const registrations: RegistrationService[] = []; + +afterEach(() => { + for (const registration of registrations.splice(0)) registration.close(); + for (const root of roots.splice(0)) rmSync(root, { force: true, recursive: true }); +}); + +function createRegistration() { + const now = Date.parse("2026-08-04T09:00:00.000Z"); + const root = mkdtempSync(join(tmpdir(), "dada-wp7-04-amap-")); + roots.push(root); + const registration = new RegistrationService({ + challengePepper: Buffer.alloc(32, 0xa1), + clock: () => now, + currentPrivacyNoticeVersion: "p0a-registration-notice-v1", + databasePath: join(root, "dada.sqlite3"), + invitePepper: Buffer.alloc(32, 0xa2), + resend: new MockResendAdapter(), + sessionPepper: Buffer.alloc(32, 0xa3), + }); + registrations.push(registration); + + const userId = randomUUID(); + registration.database.prepare(` + INSERT INTO users (user_id, normalized_email, role, status, counts_toward_stage_limit, registration_id, created_at) + VALUES (?, ?, 'user', 'active', 1, ?, ?) + `).run(userId, `${userId}@example.invalid`, randomUUID(), now); + registration.database.prepare("INSERT INTO user_profiles (user_id, creator_name, social_id) VALUES (?, 'Release Gate User', '@release_gate')").run(userId); + registration.database.prepare("INSERT INTO credit_accounts (user_id, available_balance, reserved_balance, updated_at) VALUES (?, 10, 0, ?)").run(userId, now); + return { registration, session: registration.issueAuthenticatedSession(userId, "user") }; +} + +describe("TDD-WP7-EXT-003 Amap release hard stop", () => { + it("admits the simulated 1000th request and blocks the 1001st before provider egress", async () => { + const { registration, session } = createRegistration(); + registration.database.prepare(` + UPDATE external_service_usage + SET used_count = 999, service_status = 'active', pause_reason = NULL + WHERE service_id = 'amap_web_service' AND period_type = 'monthly' + `).run(); + + const amap = new MockAmapAdapter(); + const app = await createApp({ amap, browserGate: false, networkBoundary: { allowTestPort: true }, registration }); + const headers = { + cookie: `dada_session=${session.sessionToken}`, + host: "127.0.0.1:43121", + origin: "http://127.0.0.1:43121", + "x-csrf-token": registration.issueUserCsrfToken(session.sessionToken), + }; + + const thousandth = await app.inject({ + headers, + method: "POST", + payload: { latitude: 0, longitude: 0 }, + url: "/api/v1/location/reverse-geocode", + }); + const thousandAndFirst = await app.inject({ + headers, + method: "POST", + payload: { latitude: 0, longitude: 0 }, + url: "/api/v1/location/reverse-geocode", + }); + + expect(thousandth.statusCode).toBe(200); + expect(thousandAndFirst.statusCode).toBe(503); + expect(amap.calls).toHaveLength(1); + expect(registration.serviceUsage.read("amap_web_service")).toEqual([ + expect.objectContaining({ hardLimit: 1_000, status: "paused_quota", usedCount: 1_000 }), + ]); + await app.close(); + }); +});