163 lines
4.5 KiB
TypeScript
163 lines
4.5 KiB
TypeScript
import { dayOf, requestKey } from "../core/action-plan.js"
|
|
// The crm use cases over a recording port: what each asks the service, with no client and no network.
|
|
|
|
import { actorRef } from "@conusai-kit/business"
|
|
import type { ProjectScope } from "@conusai-kit/core"
|
|
import { describe, expect, it } from "vitest"
|
|
import type { PartyView } from "../contract/index.js"
|
|
import { addContact, createParty, listParties } from "../core/crm.js"
|
|
import { type CrmService, crmReadsOf } from "../core/ports.js"
|
|
|
|
const scope: ProjectScope = {
|
|
level: "project",
|
|
actor: { userId: "01900000-0000-7000-8000-000000000002" },
|
|
orgId: "o1",
|
|
workspaceId: "w1",
|
|
projectId: "p1",
|
|
}
|
|
|
|
const party: PartyView = {
|
|
partyId: "x",
|
|
kind: "person",
|
|
displayName: "Ada",
|
|
legalName: null,
|
|
country: "LT",
|
|
status: "active",
|
|
linked: false,
|
|
}
|
|
|
|
function recording() {
|
|
const asked: Array<[string, unknown]> = []
|
|
const service: CrmService = {
|
|
bindingId: "01900000-0000-7000-8000-000000000001",
|
|
listParties: async (query) => {
|
|
asked.push(["listParties", query])
|
|
return { rows: [] }
|
|
},
|
|
getParty: async () => party,
|
|
listContacts: async () => ({ rows: [] }),
|
|
createParty: async (input) => {
|
|
asked.push(["createParty", input])
|
|
return party
|
|
},
|
|
addContact: async (input) => {
|
|
asked.push(["addContact", input])
|
|
return {
|
|
contactId: "c",
|
|
kind: input.kind,
|
|
value: input.value,
|
|
purposes: input.purposes,
|
|
isPrimary: input.isPrimary,
|
|
erased: false,
|
|
}
|
|
},
|
|
}
|
|
return { service, asked }
|
|
}
|
|
|
|
describe("the crm use cases", () => {
|
|
it("list parties with a trimmed search and the module's page size", async () => {
|
|
const { service, asked } = recording()
|
|
await listParties(service, { q: " acme " })
|
|
expect(asked).toEqual([["listParties", { q: "acme", limit: 50 }]])
|
|
})
|
|
|
|
it("create a party with the project's request key and no identifier unless asked", async () => {
|
|
const { service, asked } = recording()
|
|
await createParty(service, scope, {
|
|
kind: "organization",
|
|
displayName: " Acme ",
|
|
legalName: " ",
|
|
country: "LT",
|
|
requestId: "r1",
|
|
})
|
|
expect(asked).toEqual([
|
|
[
|
|
"createParty",
|
|
{ kind: "organization", displayName: "Acme", country: "LT", identifiers: [], key: requestKey(scope, "r1") },
|
|
],
|
|
])
|
|
})
|
|
|
|
it("link a person to the member with the member's subject reference, and only a person, and only a member", async () => {
|
|
const { service, asked } = recording()
|
|
await createParty(service, scope, {
|
|
kind: "person",
|
|
displayName: "Ada",
|
|
country: "LT",
|
|
linkSelf: true,
|
|
requestId: "r2",
|
|
})
|
|
expect(asked[0]?.[1]).toMatchObject({
|
|
identifiers: [
|
|
{
|
|
scheme: "conusai.subject",
|
|
value: actorRef("01900000-0000-7000-8000-000000000001", "01900000-0000-7000-8000-000000000002"),
|
|
},
|
|
],
|
|
})
|
|
await expect(
|
|
createParty(service, scope, {
|
|
kind: "organization",
|
|
displayName: "Acme",
|
|
country: "LT",
|
|
linkSelf: true,
|
|
requestId: "r3",
|
|
}),
|
|
).rejects.toMatchObject({ problem: { status: 403 } })
|
|
const system: ProjectScope = { ...scope, actor: { userId: "system:sweep", system: true } }
|
|
await expect(
|
|
createParty(service, system, {
|
|
kind: "person",
|
|
displayName: "Ada",
|
|
country: "LT",
|
|
linkSelf: true,
|
|
requestId: "r4",
|
|
}),
|
|
).rejects.toMatchObject({ problem: { status: 403 } })
|
|
expect(asked).toHaveLength(1)
|
|
})
|
|
|
|
it("add a contact point from today, each purpose once", async () => {
|
|
const { service, asked } = recording()
|
|
const now = new Date("2026-11-02T23:30:00.000Z")
|
|
await addContact(
|
|
service,
|
|
scope,
|
|
{
|
|
partyId: "x",
|
|
kind: "phone",
|
|
value: " +370 600 00000 ",
|
|
purposes: ["billing", "billing"],
|
|
isPrimary: false,
|
|
requestId: "r5",
|
|
},
|
|
now,
|
|
)
|
|
expect(asked).toEqual([
|
|
[
|
|
"addContact",
|
|
{
|
|
partyId: "x",
|
|
kind: "phone",
|
|
value: "+370 600 00000",
|
|
purposes: ["billing"],
|
|
isPrimary: false,
|
|
effectiveFrom: "2026-11-02",
|
|
key: requestKey(scope, "r5"),
|
|
},
|
|
],
|
|
])
|
|
expect(dayOf(now)).toBe("2026-11-02")
|
|
})
|
|
|
|
it("hand a read handler a view without a single write", () => {
|
|
expect(Object.keys(crmReadsOf(recording().service)).sort()).toEqual([
|
|
"bindingId",
|
|
"getParty",
|
|
"listContacts",
|
|
"listParties",
|
|
])
|
|
})
|
|
})
|