import { afterEach, describe, expect, it } from "vitest"; import { mkdtempSync, rmSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { createApp } from "../../apps/api/src/app.js"; import { MockAmapAdapter } from "../../apps/api/src/amap-adapter.js"; import { RegistrationService } from "../../apps/api/src/registration.js"; import { MockResendAdapter } from "../../apps/api/src/resend-adapter.js"; const resources: Array<{ close: () => Promise | void }> = []; const roots: string[] = []; afterEach(async () => { for (const resource of resources.splice(0).reverse()) await resource.close(); for (const root of roots.splice(0)) rmSync(root, { force: true, recursive: true }); }); function registrationFixture() { const directory = mkdtempSync(join(tmpdir(), "dada-wp4-04-")); roots.push(directory); const registration = new RegistrationService({ challengePepper: Buffer.alloc(32, 2), currentPrivacyNoticeVersion: "2026-07-24", databasePath: join(directory, "dada.sqlite3"), invitePepper: Buffer.alloc(32, 3), resend: new MockResendAdapter(), sessionPepper: Buffer.alloc(32, 4), }); resources.push({ close: () => registration.close() }); const userId = "00000000-0000-4000-8000-000000000831"; registration.database.prepare("INSERT INTO users (user_id, normalized_email, role, status, counts_toward_stage_limit, registration_id, created_at) VALUES (?, 'location@example.invalid', 'user', 'active', 1, ?, ?)").run(userId, crypto.randomUUID(), Date.now()); registration.database.prepare("INSERT INTO user_profiles (user_id, creator_name, social_id) VALUES (?, 'Location User', '@location')").run(userId); registration.database.prepare("INSERT INTO credit_accounts (user_id, available_balance, reserved_balance, updated_at) VALUES (?, 10, 0, ?)").run(userId, Date.now()); const session = registration.issueAuthenticatedSession(userId, "user"); return { registration, session }; } describe("TASK-WP4-04 location adapter API", () => { it("requires mutation auth and forwards only coordinates to the local mock adapter", async () => { const fixture = registrationFixture(); const amap = new MockAmapAdapter(); const app = await createApp({ amap, browserGate: false, registration: fixture.registration }); resources.push({ close: () => app.close() }); const csrf = fixture.registration.issueUserCsrfToken(fixture.session.sessionToken); const response = await app.inject({ headers: { cookie: `dada_session=${fixture.session.sessionToken}`, host: "127.0.0.1:43121", origin: "http://127.0.0.1:43121", "x-csrf-token": csrf }, method: "POST", payload: { latitude: 27.9943, longitude: 120.6994 }, url: "/api/v1/location/reverse-geocode", }); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ formatted_value: "模拟地点 27.9943, 120.6994", service_mode: "mock", status: "resolved" }); expect(amap.calls).toEqual([{ latitude: 27.9943, longitude: 120.6994 }]); const anonymous = await app.inject({ headers: { host: "127.0.0.1:43121", origin: "http://127.0.0.1:43121", "x-csrf-token": "anonymous-csrf-fixture-000000000000000000000000" }, method: "POST", payload: { latitude: 27.9943, longitude: 120.6994 }, url: "/api/v1/location/reverse-geocode" }); expect(anonymous.statusCode).toBe(401); expect(amap.calls).toHaveLength(1); }); });