Add transfer workflow components and utilities: 1. Transfer types and core functions (src/lib/transfers/index.ts): - Type-safe transfer request and decision creation - Appeal, cancel, and resolve core functions - Server action conventions with error handling 2. Transfer UI components (src/components/frontend/transfers/): - TransferActionDialog.tsx: Handle approval/rejection appeals - TransferApprovalCard.tsx: Display approval decisions - TransferNoteDialog.tsx: Add notes for transfers/appeals - TransferRequestCard.tsx: Show transfer request details - TransferResolutionCard.tsx: Display superuser resolutions - TransfersView.tsx: Main transfers list page - transferTypes.ts: Shared TypeScript types 3. Notification types (src/lib/transfers/notificationTypes.ts): - Define notification type constants for transfer events - Include TRANSFER_NOTIFICATION_TYPES for bridge filtering 4. Integration tests (tests/int/): - transfer-requests.int.spec.ts: Test bot request polling - transfer-bot-delivery.int.spec.ts: Test Discord notification delivery These components and utilities complete the transfer workflow implementation including approvals, appeals, and delivery.
98 lines
2.5 KiB
TypeScript
98 lines
2.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
TRANSFER_DELIVERY_KEYS,
|
|
deliveredKeys,
|
|
findLeaderSide,
|
|
isLeaderDeliveryDue,
|
|
transferDecisionRow,
|
|
} from "@/bot/lib/transferMessaging";
|
|
import type { AssignmentTransfer } from "@/payload-types";
|
|
|
|
const transfer = (overrides: Partial<AssignmentTransfer> = {}): AssignmentTransfer => ({
|
|
id: 42,
|
|
requester: 10,
|
|
fromAssignment: 20,
|
|
toAssignment: 30,
|
|
status: "pending",
|
|
fromLeaderDecision: "pending",
|
|
toLeaderDecision: "pending",
|
|
updatedAt: "2026-08-31T00:00:00.000Z",
|
|
createdAt: "2026-08-31T00:00:00.000Z",
|
|
...overrides,
|
|
});
|
|
|
|
describe("assignment-transfer Discord helpers", () => {
|
|
it("requires a fresh leader DM when leadership changes", () => {
|
|
// Given
|
|
const request = transfer({
|
|
discordDeliveredKeys: [TRANSFER_DELIVERY_KEYS.requestFrom],
|
|
sourceNotifiedToUserId: 100,
|
|
});
|
|
|
|
// When
|
|
const due = isLeaderDeliveryDue(request, "from", 101);
|
|
|
|
// Then
|
|
expect(due).toBe(true);
|
|
});
|
|
|
|
it("skips a leader DM only when its key and recipient both match", () => {
|
|
// Given
|
|
const request = transfer({
|
|
discordDeliveredKeys: [TRANSFER_DELIVERY_KEYS.requestTo],
|
|
destinationNotifiedToUserId: 200,
|
|
});
|
|
|
|
// When
|
|
const due = isLeaderDeliveryDue(request, "to", 200);
|
|
|
|
// Then
|
|
expect(due).toBe(false);
|
|
});
|
|
|
|
it("chooses the actor's still-pending current-leader side", () => {
|
|
// Given
|
|
const request = transfer({ fromLeaderDecision: "approved" });
|
|
|
|
// When
|
|
const check = findLeaderSide(request, 100, 200, 200);
|
|
|
|
// Then
|
|
expect(check).toEqual({ outcome: "authorized", side: "to" });
|
|
});
|
|
|
|
it("marks stale leaders as unauthorized", () => {
|
|
// Given
|
|
const request = transfer();
|
|
|
|
// When
|
|
const check = findLeaderSide(request, 101, 200, 100);
|
|
|
|
// Then
|
|
expect(check).toEqual({ outcome: "not_leader" });
|
|
});
|
|
|
|
it("renders the transfer custom-id contract", () => {
|
|
// Given
|
|
const row = transferDecisionRow(42);
|
|
|
|
// When
|
|
const customIds = row.toJSON().components.map((component) =>
|
|
"custom_id" in component ? component.custom_id : null,
|
|
);
|
|
|
|
// Then
|
|
expect(customIds).toEqual(["ptf-xfer:42:approve", "ptf-xfer:42:reject"]);
|
|
});
|
|
|
|
it("narrows malformed delivery-ledger JSON to an empty list", () => {
|
|
// Given
|
|
const request = transfer({ discordDeliveredKeys: { invalid: true } });
|
|
|
|
// When
|
|
const keys = deliveredKeys(request);
|
|
|
|
// Then
|
|
expect(keys).toEqual([]);
|
|
});
|
|
});
|