115 lines
3.5 KiB
TypeScript
115 lines
3.5 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import { PERMS } from "@/lib/permission-slugs";
|
|
import type { HousekeepingCapabilityContext } from "../../../foundation/contracts";
|
|
import { ECONOMY_ROUTE_IDS } from "../routes";
|
|
import { createEconomyQuery, type EconomyQueryData } from "./catalog";
|
|
import { serializeEconomyAmount } from "./commerce";
|
|
import { serializeEconomyValue } from "./value";
|
|
|
|
function context(granted: readonly string[]): HousekeepingCapabilityContext {
|
|
const permissions = new Set(granted);
|
|
return {
|
|
actor: { id: 42, username: "operator", rank: 7 },
|
|
isSuperAdmin: false,
|
|
has: (slug) => permissions.has(slug),
|
|
hasAny: (...slugs) => slugs.some((slug) => permissions.has(slug)),
|
|
hasAll: (...slugs) => slugs.every((slug) => permissions.has(slug)),
|
|
};
|
|
}
|
|
|
|
describe("Economy query adapters", () => {
|
|
it("fails closed before invoking an unauthorized adapter", async () => {
|
|
const load = vi.fn();
|
|
const query = createEconomyQuery({ load });
|
|
const result = await query.run(context([]), {
|
|
routeId: "economy.catalog.overview",
|
|
});
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: { code: "FORBIDDEN" },
|
|
});
|
|
expect(load).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("normalizes stable pagination and preserves BIGINT identifiers", async () => {
|
|
const load = vi.fn(
|
|
async (): Promise<EconomyQueryData> => ({
|
|
kind: "history",
|
|
items: [
|
|
{
|
|
id: "18446744073709551615",
|
|
title: "Completed order",
|
|
amount: "EUR 12.50",
|
|
href: "/ase/economy/history/transactions",
|
|
},
|
|
],
|
|
total: 501,
|
|
partialDependencies: [],
|
|
}),
|
|
);
|
|
const query = createEconomyQuery({ load });
|
|
const result = await query.run(context([PERMS.SHOP_VIEW]), {
|
|
routeId: "economy.history.transactions",
|
|
list: { search: " paid ", pageSize: 999, offset: 999_999 },
|
|
});
|
|
expect(load).toHaveBeenCalledWith({
|
|
routeId: "economy.history.transactions",
|
|
params: {},
|
|
list: { search: "paid", pageSize: 100, offset: 100_000 },
|
|
});
|
|
expect(result).toMatchObject({
|
|
ok: true,
|
|
data: { items: [{ id: "18446744073709551615" }], total: 501 },
|
|
});
|
|
});
|
|
|
|
it("serializes money and rare values without locale-dependent rounding", () => {
|
|
expect(serializeEconomyAmount(12.5, "eur")).toBe("EUR 12.50");
|
|
expect(serializeEconomyAmount("9007199254740993", "usd")).toBe(
|
|
"USD 9007199254740993",
|
|
);
|
|
expect(serializeEconomyValue("1250.50", "diamonds")).toEqual({
|
|
value: "1250.50",
|
|
currency: "diamonds",
|
|
});
|
|
});
|
|
|
|
it("rejects fabricated partial dependencies and unsafe hrefs", async () => {
|
|
const query = createEconomyQuery({
|
|
load: async () => ({
|
|
kind: "catalog",
|
|
items: [
|
|
{ id: "1", title: "Unsafe", href: "/ase/economy/%2e%2e/system" },
|
|
],
|
|
total: 1,
|
|
partialDependencies: ["imaginary"],
|
|
}),
|
|
});
|
|
expect(
|
|
await query.run(context([PERMS.CATALOG_VIEW]), {
|
|
routeId: "economy.catalog.overview",
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "DEPENDENCY_UNAVAILABLE" },
|
|
});
|
|
});
|
|
|
|
it("has an authorized adapter path for every Economy route", async () => {
|
|
const load = vi.fn(async (input) => ({
|
|
kind: input.routeId.split(".")[1] as EconomyQueryData["kind"],
|
|
items: [],
|
|
total: 0,
|
|
partialDependencies: [],
|
|
}));
|
|
const query = createEconomyQuery({ load });
|
|
for (const routeId of ECONOMY_ROUTE_IDS) {
|
|
const result = await query.run(context(Object.values(PERMS)), {
|
|
routeId,
|
|
});
|
|
expect(result.ok, routeId).toBe(true);
|
|
}
|
|
expect(load).toHaveBeenCalledTimes(ECONOMY_ROUTE_IDS.length);
|
|
});
|
|
});
|