feat(#170): Implement mosaic-bridge module for Discord
Created the mosaic-bridge module to enable Discord integration for chat-based control of Mosaic Stack. This module provides the foundation for receiving commands via Discord and forwarding them to the stitcher for job orchestration. Key Features: - Discord bot connection and authentication - Command parsing (@mosaic fix, status, cancel, verbose, quiet, help) - Thread management for job updates - Chat provider interface for future platform extensibility - Noise management (low/medium/high verbosity levels) Implementation Details: - Created IChatProvider interface for platform abstraction - Implemented DiscordService with Discord.js - Basic command parsing (detailed parsing in #171) - Thread creation for job-specific updates - Configuration via environment variables Commands Supported: - @mosaic fix <issue> - Start job for issue - @mosaic status <job> - Get job status (placeholder) - @mosaic cancel <job> - Cancel running job (placeholder) - @mosaic verbose <job> - Stream full logs (placeholder) - @mosaic quiet - Reduce notifications (placeholder) - @mosaic help - Show available commands Testing: - 23/23 tests passing (TDD approach) - Unit tests for Discord service - Module integration tests - 100% coverage of critical paths Quality Gates: - Typecheck: PASSED - Lint: PASSED - Build: PASSED - Tests: PASSED (23/23) Environment Variables: - DISCORD_BOT_TOKEN - Bot authentication token - DISCORD_GUILD_ID - Server/Guild ID (optional) - DISCORD_CONTROL_CHANNEL_ID - Channel for commands Files Created: - apps/api/src/bridge/bridge.module.ts - apps/api/src/bridge/discord/discord.service.ts - apps/api/src/bridge/interfaces/chat-provider.interface.ts - apps/api/src/bridge/index.ts - Full test coverage Dependencies Added: - discord.js@latest Next Steps: - Issue #171: Implement detailed command parsing - Issue #172: Add Herald integration for job updates - Future: Add Slack, Matrix support via IChatProvider Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
96
apps/api/src/bridge/bridge.module.spec.ts
Normal file
96
apps/api/src/bridge/bridge.module.spec.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { BridgeModule } from "./bridge.module";
|
||||
import { DiscordService } from "./discord/discord.service";
|
||||
import { StitcherService } from "../stitcher/stitcher.service";
|
||||
import { PrismaService } from "../prisma/prisma.service";
|
||||
import { BullMqService } from "../bullmq/bullmq.service";
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
|
||||
// Mock discord.js
|
||||
const mockReadyCallbacks: Array<() => void> = [];
|
||||
const mockClient = {
|
||||
login: vi.fn().mockImplementation(async () => {
|
||||
mockReadyCallbacks.forEach((cb) => cb());
|
||||
return Promise.resolve();
|
||||
}),
|
||||
destroy: vi.fn().mockResolvedValue(undefined),
|
||||
on: vi.fn(),
|
||||
once: vi.fn().mockImplementation((event: string, callback: () => void) => {
|
||||
if (event === "ready") {
|
||||
mockReadyCallbacks.push(callback);
|
||||
}
|
||||
}),
|
||||
user: { tag: "TestBot#1234" },
|
||||
channels: {
|
||||
fetch: vi.fn(),
|
||||
},
|
||||
guilds: {
|
||||
fetch: vi.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
vi.mock("discord.js", () => {
|
||||
return {
|
||||
Client: class MockClient {
|
||||
login = mockClient.login;
|
||||
destroy = mockClient.destroy;
|
||||
on = mockClient.on;
|
||||
once = mockClient.once;
|
||||
user = mockClient.user;
|
||||
channels = mockClient.channels;
|
||||
guilds = mockClient.guilds;
|
||||
},
|
||||
Events: {
|
||||
ClientReady: "ready",
|
||||
MessageCreate: "messageCreate",
|
||||
Error: "error",
|
||||
},
|
||||
GatewayIntentBits: {
|
||||
Guilds: 1 << 0,
|
||||
GuildMessages: 1 << 9,
|
||||
MessageContent: 1 << 15,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe("BridgeModule", () => {
|
||||
let module: TestingModule;
|
||||
|
||||
beforeEach(async () => {
|
||||
// Set environment variables
|
||||
process.env.DISCORD_BOT_TOKEN = "test-token";
|
||||
process.env.DISCORD_GUILD_ID = "test-guild-id";
|
||||
process.env.DISCORD_CONTROL_CHANNEL_ID = "test-channel-id";
|
||||
|
||||
// Clear ready callbacks
|
||||
mockReadyCallbacks.length = 0;
|
||||
|
||||
module = await Test.createTestingModule({
|
||||
imports: [BridgeModule],
|
||||
})
|
||||
.overrideProvider(PrismaService)
|
||||
.useValue({})
|
||||
.overrideProvider(BullMqService)
|
||||
.useValue({})
|
||||
.compile();
|
||||
|
||||
// Clear all mocks
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should be defined", () => {
|
||||
expect(module).toBeDefined();
|
||||
});
|
||||
|
||||
it("should provide DiscordService", () => {
|
||||
const discordService = module.get<DiscordService>(DiscordService);
|
||||
expect(discordService).toBeDefined();
|
||||
expect(discordService).toBeInstanceOf(DiscordService);
|
||||
});
|
||||
|
||||
it("should provide StitcherService", () => {
|
||||
const stitcherService = module.get<StitcherService>(StitcherService);
|
||||
expect(stitcherService).toBeDefined();
|
||||
expect(stitcherService).toBeInstanceOf(StitcherService);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user