import { describe, it, expect, vi, beforeEach } from "vitest"; import { Test, TestingModule } from "@nestjs/testing"; import { ConfigModule } from "@nestjs/config"; import { MosaicTelemetryModule } from "./mosaic-telemetry.module"; import { MosaicTelemetryService } from "./mosaic-telemetry.service"; // Mock the telemetry client to avoid real HTTP calls vi.mock("@mosaicstack/telemetry-client", async (importOriginal) => { const actual = await importOriginal(); class MockTelemetryClient { private _isRunning = false; constructor(_config: unknown) { // no-op } get eventBuilder() { return { build: vi.fn().mockReturnValue({ event_id: "test-event-id" }) }; } start(): void { this._isRunning = true; } async stop(): Promise { this._isRunning = false; } track(_event: unknown): void { // no-op } getPrediction(_query: unknown): unknown { return null; } async refreshPredictions(_queries: unknown): Promise { // no-op } get queueSize(): number { return 0; } get isRunning(): boolean { return this._isRunning; } } return { ...actual, TelemetryClient: MockTelemetryClient, }; }); describe("MosaicTelemetryModule", () => { let module: TestingModule; beforeEach(() => { vi.clearAllMocks(); }); describe("module initialization", () => { it("should compile the module successfully", async () => { module = await Test.createTestingModule({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: [], load: [ () => ({ MOSAIC_TELEMETRY_ENABLED: "false", }), ], }), MosaicTelemetryModule, ], }).compile(); expect(module).toBeDefined(); await module.close(); }); it("should provide MosaicTelemetryService", async () => { module = await Test.createTestingModule({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: [], load: [ () => ({ MOSAIC_TELEMETRY_ENABLED: "false", }), ], }), MosaicTelemetryModule, ], }).compile(); const service = module.get(MosaicTelemetryService); expect(service).toBeDefined(); expect(service).toBeInstanceOf(MosaicTelemetryService); await module.close(); }); it("should export MosaicTelemetryService for injection in other modules", async () => { module = await Test.createTestingModule({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: [], load: [ () => ({ MOSAIC_TELEMETRY_ENABLED: "false", }), ], }), MosaicTelemetryModule, ], }).compile(); const service = module.get(MosaicTelemetryService); expect(service).toBeDefined(); await module.close(); }); }); describe("lifecycle integration", () => { it("should initialize service on module init when enabled", async () => { module = await Test.createTestingModule({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: [], load: [ () => ({ MOSAIC_TELEMETRY_ENABLED: "true", MOSAIC_TELEMETRY_SERVER_URL: "https://tel.test.local", MOSAIC_TELEMETRY_API_KEY: "a".repeat(64), MOSAIC_TELEMETRY_INSTANCE_ID: "550e8400-e29b-41d4-a716-446655440000", MOSAIC_TELEMETRY_DRY_RUN: "false", }), ], }), MosaicTelemetryModule, ], }).compile(); await module.init(); const service = module.get(MosaicTelemetryService); expect(service.isEnabled).toBe(true); await module.close(); }); it("should not start client when disabled via env", async () => { module = await Test.createTestingModule({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: [], load: [ () => ({ MOSAIC_TELEMETRY_ENABLED: "false", }), ], }), MosaicTelemetryModule, ], }).compile(); await module.init(); const service = module.get(MosaicTelemetryService); expect(service.isEnabled).toBe(false); await module.close(); }); it("should cleanly shut down on module destroy", async () => { module = await Test.createTestingModule({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: [], load: [ () => ({ MOSAIC_TELEMETRY_ENABLED: "true", MOSAIC_TELEMETRY_SERVER_URL: "https://tel.test.local", MOSAIC_TELEMETRY_API_KEY: "a".repeat(64), MOSAIC_TELEMETRY_INSTANCE_ID: "550e8400-e29b-41d4-a716-446655440000", MOSAIC_TELEMETRY_DRY_RUN: "false", }), ], }), MosaicTelemetryModule, ], }).compile(); await module.init(); const service = module.get(MosaicTelemetryService); expect(service.isEnabled).toBe(true); await expect(module.close()).resolves.not.toThrow(); }); }); });