feat(#382): Herald Service: broadcast to all active chat providers
Some checks failed
ci/woodpecker/push/api Pipeline failed

- Replace direct DiscordService injection with CHAT_PROVIDERS array
- Herald broadcasts to ALL active chat providers (Discord, Matrix, future)
- Graceful error handling — one provider failure doesn't block others
- Skips disconnected providers automatically
- Tests verify multi-provider broadcasting behavior
- Fix lint: remove unnecessary conditional in matrix.service.ts

Refs #382
This commit is contained in:
2026-02-15 02:25:55 -06:00
parent 4a9ecab4dd
commit ad24720616
8 changed files with 859 additions and 424 deletions

View File

@@ -35,6 +35,7 @@ describe("MatrixRoomService", () => {
const mockPrismaService = {
workspace: {
findUnique: vi.fn(),
findFirst: vi.fn(),
update: vi.fn(),
},
};
@@ -162,6 +163,30 @@ describe("MatrixRoomService", () => {
});
});
describe("getWorkspaceForRoom", () => {
it("should return the workspace ID for a mapped room", async () => {
mockPrismaService.workspace.findFirst.mockResolvedValue({
id: "workspace-uuid-1",
});
const workspaceId = await service.getWorkspaceForRoom("!mapped-room:example.com");
expect(workspaceId).toBe("workspace-uuid-1");
expect(mockPrismaService.workspace.findFirst).toHaveBeenCalledWith({
where: { matrixRoomId: "!mapped-room:example.com" },
select: { id: true },
});
});
it("should return null for an unmapped room", async () => {
mockPrismaService.workspace.findFirst.mockResolvedValue(null);
const workspaceId = await service.getWorkspaceForRoom("!unknown-room:example.com");
expect(workspaceId).toBeNull();
});
});
describe("linkWorkspaceToRoom", () => {
it("should store the room mapping in the workspace", async () => {
await service.linkWorkspaceToRoom("workspace-uuid-1", "!existing-room:example.com");