141 lines
3.8 KiB
TypeScript
141 lines
3.8 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
threadList: vi.fn(),
|
|
threadDetail: vi.fn(),
|
|
contacts: vi.fn(),
|
|
deals: vi.fn(),
|
|
policy: vi.fn(),
|
|
scheduled: vi.fn(),
|
|
bulkRuns: vi.fn(),
|
|
reminders: vi.fn(),
|
|
mailboxes: vi.fn(),
|
|
audit: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@hivegrid/db/admin-sales-core.server", () => ({
|
|
writeAdminSalesAudit: mocks.audit,
|
|
}));
|
|
vi.mock("~/utils/db.server", () => ({
|
|
db: {
|
|
adminEmailThread: {
|
|
findMany: mocks.threadList,
|
|
findFirst: mocks.threadDetail,
|
|
},
|
|
adminContact: { findMany: mocks.contacts },
|
|
adminDeal: { findMany: mocks.deals },
|
|
adminEmailOutboundPolicy: { findUnique: mocks.policy },
|
|
adminScheduledEmail: { findMany: mocks.scheduled },
|
|
adminBulkEmailRun: { findMany: mocks.bulkRuns },
|
|
adminNoReplyReminder: { findMany: mocks.reminders },
|
|
adminMailbox: { findMany: mocks.mailboxes },
|
|
},
|
|
}));
|
|
|
|
import {
|
|
readAdminEmailInbox,
|
|
readAdminEmailScheduled,
|
|
} from "./admin-email-workspace.server";
|
|
|
|
describe("admin email team scope", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mocks.threadList.mockResolvedValue([]);
|
|
mocks.contacts.mockResolvedValue([]);
|
|
mocks.deals.mockResolvedValue([]);
|
|
mocks.policy.mockResolvedValue(null);
|
|
mocks.scheduled.mockResolvedValue([]);
|
|
mocks.bulkRuns.mockResolvedValue([]);
|
|
mocks.reminders.mockResolvedValue([]);
|
|
mocks.mailboxes.mockResolvedValue([]);
|
|
mocks.threadDetail.mockResolvedValue({
|
|
id: "thread-b",
|
|
adminUserId: "sales-b",
|
|
normalizedSubject: "Team-Anfrage",
|
|
messages: [],
|
|
});
|
|
});
|
|
|
|
it("reads a selected team thread only with read-all and audits actor, owner and target", async () => {
|
|
const actor = vi.fn((reason: string) => ({
|
|
adminUserId: "manager",
|
|
email: "manager@example.test",
|
|
reason,
|
|
}));
|
|
|
|
await readAdminEmailInbox(
|
|
{
|
|
adminUserId: "manager",
|
|
canReadAllCommunication: true,
|
|
actor,
|
|
} as never,
|
|
"thread-b"
|
|
);
|
|
|
|
expect(mocks.threadList).toHaveBeenCalledWith(
|
|
expect.objectContaining({ where: { archivedAt: null } })
|
|
);
|
|
expect(mocks.threadDetail).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
where: { id: "thread-b", archivedAt: null },
|
|
})
|
|
);
|
|
expect(mocks.audit).toHaveBeenCalledWith(
|
|
expect.anything(),
|
|
expect.objectContaining({
|
|
action: "admin.sales.email.foreignThread.read",
|
|
targetType: "AdminEmailThread",
|
|
targetId: "thread-b",
|
|
after: {
|
|
dataOwnerAdminUserId: "sales-b",
|
|
accessKind: "read",
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
it("audits every foreign scheduled communication target exposed to the manager", async () => {
|
|
mocks.scheduled.mockResolvedValue([
|
|
{
|
|
id: "scheduled-b",
|
|
adminUserId: "sales-b",
|
|
subject: "Follow-up",
|
|
toAddresses: ["buyer@example.test"],
|
|
scheduledFor: new Date("2026-07-26T10:00:00.000Z"),
|
|
status: "queued",
|
|
noReplyAfterDays: null,
|
|
lastErrorMessageKey: null,
|
|
},
|
|
]);
|
|
|
|
await readAdminEmailScheduled({
|
|
adminUserId: "manager",
|
|
canReadAllCommunication: true,
|
|
actor: (reason: string) => ({
|
|
adminUserId: "manager",
|
|
email: "manager@example.test",
|
|
reason,
|
|
}),
|
|
} as never);
|
|
|
|
expect(mocks.audit).toHaveBeenCalledWith(
|
|
expect.anything(),
|
|
expect.objectContaining({
|
|
action: "admin.sales.email.foreignScheduled.read",
|
|
targetType: "AdminScheduledEmail",
|
|
targetId: "scheduled-b",
|
|
after: {
|
|
foreignTargets: [
|
|
{
|
|
targetType: "AdminScheduledEmail",
|
|
targetId: "scheduled-b",
|
|
dataOwnerAdminUserId: "sales-b",
|
|
},
|
|
],
|
|
accessKind: "read",
|
|
},
|
|
})
|
|
);
|
|
});
|
|
});
|