100 lines
3.2 KiB
TypeScript
100 lines
3.2 KiB
TypeScript
import { beforeEach, describe, expect, expectTypeOf, it, vi } from "vitest";
|
|
|
|
vi.mock("@/features/housekeeping/foundation/server-capability-context", () => ({
|
|
getHousekeepingCapabilityContext: vi.fn(),
|
|
}));
|
|
|
|
const preferenceRepository = vi.hoisted(() => ({
|
|
read: vi.fn(),
|
|
upsert: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@/lib/housekeeping-preferences-repository", () => ({
|
|
housekeepingPreferencesRepository: preferenceRepository,
|
|
}));
|
|
|
|
import { defaultHousekeepingPreferences } from "@/features/housekeeping/foundation/preferences/schema";
|
|
import { getHousekeepingCapabilityContext } from "@/features/housekeeping/foundation/server-capability-context";
|
|
import {
|
|
loadHousekeepingPreferences,
|
|
saveHousekeepingPreferences,
|
|
} from "./housekeeping-preferences";
|
|
|
|
const allowedContext = {
|
|
actor: { id: 42, username: "operator", rank: 0 },
|
|
isSuperAdmin: false,
|
|
has: () => true,
|
|
hasAny: () => true,
|
|
hasAll: () => true,
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.mocked(getHousekeepingCapabilityContext).mockResolvedValue(allowedContext);
|
|
preferenceRepository.read.mockResolvedValue(defaultHousekeepingPreferences());
|
|
});
|
|
|
|
describe("housekeeping preference actions", () => {
|
|
it("does not expose dependency or user identity parameters to callers", () => {
|
|
expect(loadHousekeepingPreferences).toHaveLength(0);
|
|
expect(saveHousekeepingPreferences).toHaveLength(1);
|
|
});
|
|
|
|
it("publishes exact action signatures without caller-controlled dependencies", () => {
|
|
expectTypeOf<
|
|
Parameters<typeof loadHousekeepingPreferences>
|
|
>().toEqualTypeOf<[]>();
|
|
expectTypeOf<
|
|
Parameters<typeof saveHousekeepingPreferences>
|
|
>().toEqualTypeOf<[input: unknown]>();
|
|
});
|
|
|
|
it("derives the read owner from the server capability context", async () => {
|
|
const result = await loadHousekeepingPreferences();
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(preferenceRepository.read).toHaveBeenCalledWith(42);
|
|
});
|
|
|
|
it("rejects a denied operator without reading or writing preferences", async () => {
|
|
vi.mocked(getHousekeepingCapabilityContext).mockResolvedValueOnce({
|
|
...allowedContext,
|
|
hasAny: () => false,
|
|
});
|
|
|
|
const result = await saveHousekeepingPreferences(
|
|
defaultHousekeepingPreferences(),
|
|
);
|
|
|
|
expect(result).toMatchObject({ ok: false, error: { code: "FORBIDDEN" } });
|
|
expect(preferenceRepository.read).not.toHaveBeenCalled();
|
|
expect(preferenceRepository.upsert).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("reconciles input before persisting or returning it", async () => {
|
|
const value = {
|
|
...defaultHousekeepingPreferences(),
|
|
pinnedRouteIds: ["removed.route"],
|
|
pinnedCommandIds: ["removed.command"],
|
|
};
|
|
|
|
const result = await saveHousekeepingPreferences(value);
|
|
|
|
expect(result).toMatchObject({
|
|
ok: true,
|
|
data: { pinnedRouteIds: [], pinnedCommandIds: [] },
|
|
});
|
|
expect(preferenceRepository.upsert).toHaveBeenCalledWith(
|
|
42,
|
|
expect.objectContaining({ pinnedRouteIds: [], pinnedCommandIds: [] }),
|
|
);
|
|
});
|
|
|
|
it("returns a validation result before an invalid payload reaches persistence", async () => {
|
|
const result = await saveHousekeepingPreferences({ schemaVersion: 2 });
|
|
|
|
expect(result).toMatchObject({ ok: false, error: { code: "VALIDATION" } });
|
|
expect(preferenceRepository.upsert).not.toHaveBeenCalled();
|
|
});
|
|
});
|