feat(housekeeping): deliver people account workflows
This commit is contained in:
1 parent
7920d4f46c
commit
e1b31ff773
45 files changed
+4290
-1243
No files matched your search
@@ -1,5 +1,11 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { z } from "zod";
|
||||
|
||||
vi.mock("@/lib/auth", () => ({ invalidateLoginCache: vi.fn() }));
|
||||
vi.mock("@/lib/auth/password", () => ({ hashPassword: vi.fn() }));
|
||||
|
||||
import { COMMUNITY_COMMAND_IDS } from "../../domains/people/commands/community-commands";
|
||||
import { USER_COMMAND_IDS } from "../../domains/people/commands/user-commands";
|
||||
import { SYSTEM_COMMAND_IDS } from "../../domains/system/commands/system-commands";
|
||||
import { anyCapability, ok } from "../contracts";
|
||||
import {
|
||||
@@ -60,6 +66,12 @@ describe("housekeeping command bootstrap", () => {
|
||||
expect(
|
||||
SYSTEM_COMMAND_IDS.map((id) => getHousekeepingCommand(id)?.id),
|
||||
).toEqual(SYSTEM_COMMAND_IDS);
|
||||
expect(
|
||||
USER_COMMAND_IDS.map((id) => getHousekeepingCommand(id)?.id),
|
||||
).toEqual(USER_COMMAND_IDS);
|
||||
expect(
|
||||
COMMUNITY_COMMAND_IDS.map((id) => getHousekeepingCommand(id)?.id),
|
||||
).toEqual(COMMUNITY_COMMAND_IDS);
|
||||
expect(() =>
|
||||
registerHousekeepingCommand({
|
||||
id: "system.bootstrap.too-late",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import "server-only";
|
||||
|
||||
import { COMMUNITY_COMMANDS } from "../../domains/people/commands/community-commands";
|
||||
import { USER_COMMANDS } from "../../domains/people/commands/user-commands";
|
||||
import { SYSTEM_COMMANDS } from "../../domains/system/commands/system-commands";
|
||||
import type { HousekeepingCommand } from "./registry";
|
||||
import {
|
||||
@@ -36,6 +38,8 @@ export function registerHousekeepingCommands<
|
||||
}
|
||||
|
||||
const currentHousekeepingCommands = defineHousekeepingCommands(
|
||||
...USER_COMMANDS,
|
||||
...COMMUNITY_COMMANDS,
|
||||
...SYSTEM_COMMANDS,
|
||||
);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { join, posix } from "node:path";
|
||||
import { createElement, type ReactElement } from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { PEOPLE_PRIMARY_ROUTE_IDS } from "../domains/people/route-handlers";
|
||||
import { SYSTEM_ROUTE_IDS } from "../domains/system/routes";
|
||||
import { HOUSEKEEPING_MANIFESTS } from "../manifests";
|
||||
import { discoverLegacyPages } from "../migration/discover-legacy-pages";
|
||||
@@ -18,7 +19,82 @@ const SERVER_CAPABILITY_CONTEXT =
|
||||
"src/features/housekeeping/foundation/server-capability-context.ts";
|
||||
const PERMISSIONS_ADAPTER = "src/lib/permissions";
|
||||
const DOMAIN_MODULE_ROOT = "src/features/housekeeping/domains";
|
||||
const approvedSystemRuntimeImports = new Map<string, ReadonlySet<string>>([
|
||||
const approvedRuntimeImports = new Map<string, ReadonlySet<string>>([
|
||||
[
|
||||
"src/features/housekeeping/domains/people/commands/community-commands.ts",
|
||||
new Set(["src/features/housekeeping/domains/people/services/mutations"]),
|
||||
],
|
||||
[
|
||||
"src/features/housekeeping/domains/people/commands/user-commands.ts",
|
||||
new Set(["src/features/housekeeping/domains/people/services/mutations"]),
|
||||
],
|
||||
[
|
||||
"src/features/housekeeping/domains/people/pages/community.tsx",
|
||||
new Set([
|
||||
"src/features/housekeeping/domains/people/pages/page-state",
|
||||
"src/features/housekeeping/domains/people/queries/community",
|
||||
]),
|
||||
],
|
||||
[
|
||||
"src/features/housekeeping/domains/people/pages/multi-accounts.tsx",
|
||||
new Set([
|
||||
"src/features/housekeeping/domains/people/pages/page-state",
|
||||
"src/features/housekeeping/domains/people/queries/users",
|
||||
]),
|
||||
],
|
||||
[
|
||||
"src/features/housekeeping/domains/people/pages/staff.tsx",
|
||||
new Set([
|
||||
"src/features/housekeeping/domains/people/pages/page-state",
|
||||
"src/features/housekeeping/domains/people/queries/staff",
|
||||
]),
|
||||
],
|
||||
[
|
||||
"src/features/housekeeping/domains/people/pages/user-detail.tsx",
|
||||
new Set([
|
||||
"src/features/housekeeping/domains/people/pages/page-state",
|
||||
"src/features/housekeeping/domains/people/queries/users",
|
||||
]),
|
||||
],
|
||||
[
|
||||
"src/features/housekeeping/domains/people/pages/user-edit.tsx",
|
||||
new Set([
|
||||
"src/features/housekeeping/domains/people/pages/page-state",
|
||||
"src/features/housekeeping/domains/people/queries/users",
|
||||
]),
|
||||
],
|
||||
[
|
||||
"src/features/housekeeping/domains/people/pages/users.tsx",
|
||||
new Set([
|
||||
"src/features/housekeeping/domains/people/pages/page-state",
|
||||
"src/features/housekeeping/domains/people/queries/users",
|
||||
]),
|
||||
],
|
||||
[
|
||||
"src/features/housekeeping/domains/people/services/mutations.ts",
|
||||
new Set([
|
||||
"src/lib/auth",
|
||||
"src/lib/auth/password",
|
||||
"src/lib/db",
|
||||
"drizzle-orm",
|
||||
"mysql2",
|
||||
]),
|
||||
],
|
||||
[
|
||||
"src/features/housekeeping/domains/people/manifest.ts",
|
||||
new Set(["src/features/housekeeping/domains/people/routes"]),
|
||||
],
|
||||
[
|
||||
"src/features/housekeeping/domains/people/route-handlers.ts",
|
||||
new Set([
|
||||
"src/features/housekeeping/domains/people/pages/community",
|
||||
"src/features/housekeeping/domains/people/pages/multi-accounts",
|
||||
"src/features/housekeeping/domains/people/pages/staff",
|
||||
"src/features/housekeeping/domains/people/pages/user-detail",
|
||||
"src/features/housekeeping/domains/people/pages/user-edit",
|
||||
"src/features/housekeeping/domains/people/pages/users",
|
||||
]),
|
||||
],
|
||||
[
|
||||
"src/features/housekeeping/domains/system/commands/system-commands.ts",
|
||||
new Set(["src/features/housekeeping/domains/system/services/mutations"]),
|
||||
@@ -127,12 +203,17 @@ const approvedSystemRuntimeImports = new Map<string, ReadonlySet<string>>([
|
||||
[
|
||||
"src/features/housekeeping/foundation/commands/bootstrap.ts",
|
||||
new Set([
|
||||
"src/features/housekeeping/domains/people/commands/community-commands",
|
||||
"src/features/housekeeping/domains/people/commands/user-commands",
|
||||
"src/features/housekeeping/domains/system/commands/system-commands",
|
||||
]),
|
||||
],
|
||||
[
|
||||
"src/features/housekeeping/route-handlers.ts",
|
||||
new Set(["src/features/housekeeping/domains/system/route-handlers"]),
|
||||
new Set([
|
||||
"src/features/housekeeping/domains/people/route-handlers",
|
||||
"src/features/housekeeping/domains/system/route-handlers",
|
||||
]),
|
||||
],
|
||||
]);
|
||||
const forbiddenModuleRoots = [
|
||||
@@ -476,11 +557,11 @@ function isAllowedPermissionSetTypeImport(
|
||||
);
|
||||
}
|
||||
|
||||
function isApprovedSystemRuntimeImport(
|
||||
function isApprovedRuntimeImport(
|
||||
canonical: CanonicalModuleSpecifier,
|
||||
sourceFile: string,
|
||||
): boolean {
|
||||
const allowed = approvedSystemRuntimeImports.get(sourceFile);
|
||||
const allowed = approvedRuntimeImports.get(sourceFile);
|
||||
return (
|
||||
allowed !== undefined &&
|
||||
canonical.candidates.some((candidate) => allowed.has(candidate))
|
||||
@@ -499,7 +580,7 @@ function findHousekeepingImportBoundaryViolations(
|
||||
if (canonical.violation) violations.push(canonical.violation);
|
||||
if (isAllowedPermissionSetTypeImport(access, canonical, sourceFile))
|
||||
continue;
|
||||
if (isApprovedSystemRuntimeImport(canonical, sourceFile)) continue;
|
||||
if (isApprovedRuntimeImport(canonical, sourceFile)) continue;
|
||||
const forbiddenPath = canonical.candidates.find((candidate) =>
|
||||
isForbiddenModulePath(candidate, sourceFile),
|
||||
);
|
||||
@@ -528,7 +609,13 @@ describe("housekeeping runtime import boundary", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("allows only the approved System vertical runtime edges", () => {
|
||||
it("allows only approved domain vertical runtime edges", () => {
|
||||
expect(
|
||||
findHousekeepingImportBoundaryViolations(
|
||||
'import { peopleMutationService } from "../services/mutations";',
|
||||
"src/features/housekeeping/domains/people/commands/user-commands.ts",
|
||||
),
|
||||
).toEqual([]);
|
||||
expect(
|
||||
findHousekeepingImportBoundaryViolations(
|
||||
'import { systemMutationService } from "../services/mutations";',
|
||||
@@ -718,7 +805,7 @@ describe("housekeeping foundation completion contracts", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("creates the real six-domain registry with only the System routes enabled", () => {
|
||||
it("creates the real six-domain registry with People primary and System routes enabled", () => {
|
||||
const registry = createHousekeepingRegistry(HOUSEKEEPING_MANIFESTS);
|
||||
|
||||
expect(registry.domains.map((domain) => domain.id)).toEqual([
|
||||
@@ -731,9 +818,14 @@ describe("housekeeping foundation completion contracts", () => {
|
||||
]);
|
||||
expect(
|
||||
registry.domains
|
||||
.filter((domain) => domain.id !== "system")
|
||||
.filter((domain) => !["people", "system"].includes(domain.id))
|
||||
.every((domain) => domain.routes.length === 0),
|
||||
).toBe(true);
|
||||
expect(
|
||||
registry.domains
|
||||
.find((domain) => domain.id === "people")
|
||||
?.routes.map((route) => route.id),
|
||||
).toEqual(PEOPLE_PRIMARY_ROUTE_IDS);
|
||||
expect(
|
||||
registry.domains
|
||||
.find((domain) => domain.id === "system")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import { PEOPLE_PRIMARY_ROUTES } from "../domains/people/routes";
|
||||
import { SYSTEM_ROUTES } from "../domains/system/routes";
|
||||
import { HOUSEKEEPING_MANIFESTS } from "../manifests";
|
||||
import { HOUSEKEEPING_MIGRATION_MATRIX } from "../migration/matrix";
|
||||
@@ -355,7 +356,11 @@ describe("housekeeping registry", () => {
|
||||
descriptionKey: expected.descriptionKey,
|
||||
});
|
||||
expect(actual.routes).toEqual(
|
||||
expected.id === "system" ? SYSTEM_ROUTES : [],
|
||||
expected.id === "people"
|
||||
? PEOPLE_PRIMARY_ROUTES
|
||||
: expected.id === "system"
|
||||
? SYSTEM_ROUTES
|
||||
: [],
|
||||
);
|
||||
expect(actual.searchProviders).toEqual([]);
|
||||
expect(actual.inboxSources).toEqual([]);
|
||||
|
||||
@@ -74,12 +74,12 @@ function adminContext(
|
||||
};
|
||||
}
|
||||
|
||||
async function invokeRequestConsumers() {
|
||||
async function invokeRequestConsumers(expectedPageError = "NEXT_NOT_FOUND") {
|
||||
const shell = await AdminNextDomainLayout({
|
||||
children: null,
|
||||
params: Promise.resolve({ domain: "operations" }),
|
||||
});
|
||||
await expect(AdminNextPage()).rejects.toThrow("NEXT_NOT_FOUND");
|
||||
await expect(AdminNextPage()).rejects.toThrow(expectedPageError);
|
||||
const commandContext = await requireHousekeepingCapability(
|
||||
anyCapability("admin.dashboard"),
|
||||
);
|
||||
@@ -102,7 +102,9 @@ describe("getHousekeepingCapabilityContext", () => {
|
||||
});
|
||||
|
||||
it("isolates real shell, page, and command preflight consumers between logical requests", async () => {
|
||||
const first = await invokeRequestConsumers();
|
||||
const first = await invokeRequestConsumers(
|
||||
"NEXT_REDIRECT:/ase-next/people/users",
|
||||
);
|
||||
|
||||
expect(first.shell).toBeDefined();
|
||||
expect(first.commandContext.actor).toEqual({
|
||||
|
||||
Reference in new issue
Block a user