feat(housekeeping): add global command deck search
CI / check (pull_request) Successful in 38s
CI / release (pull_request) Skipped
CI / deploy (pull_request) Skipped

This commit is contained in:
Simo committed 2026-08-30 15:59:48 +02:00
1 parent 2e95a106e0
commit bcb0ca977f
18 files changed
+1353 -23

No files matched your search

+54
View File
@@ -0,0 +1,54 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { HousekeepingCapabilityContext } from "@/features/housekeeping/foundation/contracts";
import { PERMS } from "@/lib/permission-slugs";
const { getContextMock, searchMock } = vi.hoisted(() => ({
getContextMock: vi.fn(),
searchMock: vi.fn(),
}));
vi.mock("@/features/housekeeping/foundation/server-capability-context", () => ({
getHousekeepingCapabilityContext: getContextMock,
}));
vi.mock("@/features/housekeeping/foundation/search/search-service", () => ({
searchHousekeeping: searchMock,
}));
import { executeHousekeepingSearch } from "./housekeeping-search";
const context: HousekeepingCapabilityContext = {
actor: { id: 42, username: "operator", rank: 7 },
isSuperAdmin: false,
has: (slug) => slug === PERMS.USERS_VIEW,
hasAny: (...slugs) => slugs.includes(PERMS.USERS_VIEW),
hasAll: (...slugs) => slugs.every((slug) => slug === PERMS.USERS_VIEW),
};
describe("housekeeping search action", () => {
beforeEach(() => {
getContextMock.mockReset().mockResolvedValue(context);
searchMock.mockReset().mockResolvedValue({
navigation: [],
commands: [],
entities: [],
errors: [],
correlationId: "action-search",
});
});
it("binds search to the fresh server capability context", async () => {
const result = await executeHousekeepingSearch(" users ");
expect(searchMock).toHaveBeenCalledWith(" users ", context);
expect(result.correlationId).toBe("action-search");
});
it("rejects forged non-string terms without invoking dependencies", async () => {
const result = await executeHousekeepingSearch({ term: "users" });
expect(getContextMock).not.toHaveBeenCalled();
expect(searchMock).not.toHaveBeenCalled();
expect(result).toMatchObject({
errors: [{ providerId: "search", code: "VALIDATION" }],
});
});
});
+31
View File
@@ -0,0 +1,31 @@
"use server";
import type { HousekeepingErrorCode } from "@/features/housekeeping/foundation/contracts";
import { createCorrelationId } from "@/features/housekeeping/foundation/correlation";
import {
type HousekeepingSearchResponse,
searchHousekeeping,
} from "@/features/housekeeping/foundation/search/search-service";
import { getHousekeepingCapabilityContext } from "@/features/housekeeping/foundation/server-capability-context";
function failedSearch(code: HousekeepingErrorCode): HousekeepingSearchResponse {
return {
navigation: [],
commands: [],
entities: [],
errors: [{ providerId: "search", code }],
correlationId: createCorrelationId(),
};
}
export async function executeHousekeepingSearch(
term: unknown,
): Promise<HousekeepingSearchResponse> {
if (typeof term !== "string") return failedSearch("VALIDATION");
try {
const context = await getHousekeepingCapabilityContext();
return await searchHousekeeping(term, context);
} catch {
return failedSearch("INTERNAL");
}
}