GRO-2294: Route Optimization security hardening (geocode-batch limit cap + redact settings secret) (#193)
This commit was merged in pull request #193.
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { Hono } from "hono";
|
||||
|
||||
// ─── Mocks ──────────────────────────────────────────────────────────────────
|
||||
// GRO-2294: GET /api/admin/settings must not return the encrypted
|
||||
// googleMapsApiKey ciphertext, on either the existing-row or auto-create branch.
|
||||
|
||||
let selectRows: Record<string, unknown>[] = [];
|
||||
let insertReturning: Record<string, unknown>[] = [];
|
||||
|
||||
function makeChainable(data: unknown[]): unknown {
|
||||
const arr = [...data];
|
||||
const chain = new Proxy(arr, {
|
||||
get(target, prop) {
|
||||
if (prop === "where" || prop === "orderBy" || prop === "limit") {
|
||||
return () => chain;
|
||||
}
|
||||
// @ts-expect-error proxy passthrough
|
||||
return target[prop];
|
||||
},
|
||||
});
|
||||
return chain;
|
||||
}
|
||||
|
||||
vi.mock("@groombook/db", () => {
|
||||
const businessSettings = new Proxy(
|
||||
{ _name: "business_settings" },
|
||||
{ get: (_t, p) => (p === "_name" ? "business_settings" : { column: p }) }
|
||||
);
|
||||
return {
|
||||
getDb: () => ({
|
||||
select: () => ({ from: () => makeChainable(selectRows) }),
|
||||
insert: () => ({
|
||||
values: () => ({ returning: () => insertReturning }),
|
||||
}),
|
||||
}),
|
||||
businessSettings,
|
||||
eq: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../lib/s3.js", () => ({
|
||||
getPresignedUploadUrl: vi.fn(),
|
||||
deleteObject: vi.fn(),
|
||||
putObject: vi.fn(),
|
||||
getObject: vi.fn(),
|
||||
}));
|
||||
|
||||
const { settingsRouter } = await import("../routes/settings.js");
|
||||
|
||||
const app = new Hono();
|
||||
app.route("/settings", settingsRouter);
|
||||
|
||||
const FULL_ROW = {
|
||||
id: "settings-uuid-1",
|
||||
businessName: "GroomBook",
|
||||
primaryColor: "#4f8a6f",
|
||||
accentColor: "#8b7355",
|
||||
routeOptimizationProvider: "google",
|
||||
googleMapsApiKey: "ENCRYPTED::super-secret-ciphertext",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
describe("GET /settings — googleMapsApiKey redaction (GRO-2294)", () => {
|
||||
beforeEach(() => {
|
||||
selectRows = [];
|
||||
insertReturning = [];
|
||||
});
|
||||
|
||||
it("omits googleMapsApiKey from an existing settings row", async () => {
|
||||
selectRows = [{ ...FULL_ROW }];
|
||||
const res = await app.request("/settings", { method: "GET" });
|
||||
expect(res.status).toBe(200);
|
||||
const body = (await res.json()) as Record<string, unknown>;
|
||||
expect(body).not.toHaveProperty("googleMapsApiKey");
|
||||
// Non-secret fields are still returned.
|
||||
expect(body.businessName).toBe("GroomBook");
|
||||
expect(body.routeOptimizationProvider).toBe("google");
|
||||
});
|
||||
|
||||
it("omits googleMapsApiKey from the auto-create branch", async () => {
|
||||
selectRows = [];
|
||||
insertReturning = [{ ...FULL_ROW, id: "settings-uuid-new" }];
|
||||
const res = await app.request("/settings", { method: "GET" });
|
||||
expect(res.status).toBe(200);
|
||||
const body = (await res.json()) as Record<string, unknown>;
|
||||
expect(body).not.toHaveProperty("googleMapsApiKey");
|
||||
expect(body.id).toBe("settings-uuid-new");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user