Files
template-app/modules/crm/__tests__/action-plan.test.ts
T
2026-11-14 09:56:45 +00:00

100 lines
4.4 KiB
TypeScript

// Owning pure/adapter consistency controls, not a Root approval or live service.
import { createHash } from "node:crypto"
import { canonicalActionJson } from "@conusai-kit/business"
import { installAppActionTransport } from "@conusai-kit/business/app-actions"
import { Container, type ProjectScope } from "@conusai-kit/core"
import { describe, expect, it, vi } from "vitest"
import { crmAppActionTargets } from "../adapters/app-actions.js"
import { crmServiceOf } from "../adapters/service.js"
import { partyCreate } from "../contract/index.js"
import { contactActionData, contactEncodedRequest, partyActionData, partyEncodedRequest } from "../core/action-plan.js"
import { CRM_SERVICE, type CrmService } from "../core/ports.js"
const binding = "11111111-1111-4111-8111-111111111111"
const scope: ProjectScope = {
level: "project",
orgId: "22222222-2222-4222-8222-222222222222",
workspaceId: "33333333-3333-4333-8333-333333333333",
projectId: "44444444-4444-4444-8444-444444444444",
actor: { userId: "55555555-5555-4555-8555-555555555555" },
}
const requestId = "66666666-6666-4666-8666-666666666666"
describe("actual CRM action plan is shared with its owning effect", () => {
it("preserves derived member/project scope and exact normalizations in the request", () => {
const value = partyActionData(scope, binding, {
kind: "person",
displayName: " Synthetic ",
legalName: " ",
country: "LT",
linkSelf: true,
requestId,
})
const plan = partyEncodedRequest("platform", value)
expect(plan.body.data.display_name).toBe("Synthetic")
expect(plan.body.data.legal_name).toBeUndefined()
expect(plan.body.data.identifiers?.[0]?.value).toBe(
`conusai://app-partitions/${binding}/members/${scope.actor.userId}`,
)
expect(plan.body.idempotency_key).toBe(`app-project:${scope.projectId}:${requestId}`)
expect(plan.arguments.body).toEqual(plan.body)
})
it("does not mask or renew the actual contact effective day across midnight", () => {
const input = {
partyId: "party",
kind: "email" as const,
value: " synthetic@example.test ",
purposes: ["billing" as const],
isPrimary: true,
requestId,
}
const before = contactEncodedRequest(
"platform",
contactActionData(scope, input, new Date("2026-11-05T23:59:59.999Z")),
)
const after = contactEncodedRequest("platform", contactActionData(scope, input, new Date("2026-11-06T00:00:00Z")))
expect(before.body.data.effective.from).toBe("2026-11-05")
expect(after.body.data.effective.from).toBe("2026-11-06")
expect(canonicalActionJson(before) === canonicalActionJson(after)).toBe(false)
expect(before.arguments.id).toBe("party")
})
it("refuses custom effect ports before inspection or guarded dispatch can call them", () => {
const container = new Container()
const custom = { createParty: vi.fn(), addContact: vi.fn() } as unknown as CrmService
container.value(CRM_SERVICE, custom)
const target = crmAppActionTargets.find((candidate) => candidate.procedure === partyCreate)
if (!target) throw new Error("missing declared CRM action target")
expect(() => target.encode({}, { scope, container, now: new Date() })).toThrow()
const request = partyEncodedRequest(
"platform",
partyActionData(scope, binding, { kind: "organization", displayName: "Synthetic", country: "LT", requestId }),
)
// Synthetic lower transport restriction ONLY, never a redeemed Root lease.
installAppActionTransport(
container,
{
bindingId: binding,
environmentUuid: "77777777-7777-4777-8777-777777777777",
appTenantId: scope.orgId,
actorId: scope.actor.userId,
orgSlug: "platform",
platformProjectId: 9,
},
{
action_id: requestId,
dispatch_lease_id: requestId,
source_id: requestId,
source_epoch: "1",
observation_sha256: "a".repeat(64),
encoded_request_sha256: createHash("sha256").update(canonicalActionJson(request)).digest("hex"),
expires_at_micros: String(BigInt(Date.now()) * 1000n + 20_000_000n),
execution_context: "synthetic_non_authoritative_context_".repeat(2),
},
request,
async () => {},
)
expect(() => crmServiceOf(container, "effect")).toThrow()
expect(custom.createParty).not.toHaveBeenCalled()
expect(custom.addContact).not.toHaveBeenCalled()
})
})