- Add .npmrc with scoped Gitea npm registry for @mosaicstack packages - Create MosaicTelemetryModule (global, lifecycle-aware) at apps/api/src/mosaic-telemetry/ - Create MosaicTelemetryService wrapping TelemetryClient with convenience methods: trackTaskCompletion, getPrediction, refreshPredictions, eventBuilder - Create mosaic-telemetry.config.ts for env var integration via NestJS ConfigService - Register MosaicTelemetryModule in AppModule - Add 32 unit tests covering module init, service methods, disabled mode, dry-run mode, and lifecycle management Refs #369 Co-Authored-By: Claude Opus 4.6 <[email protected]>
213 lines
5.6 KiB
TypeScript
213 lines
5.6 KiB
TypeScript
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<typeof import("@mosaicstack/telemetry-client")>();
|
|
|
|
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<void> {
|
|
this._isRunning = false;
|
|
}
|
|
|
|
track(_event: unknown): void {
|
|
// no-op
|
|
}
|
|
|
|
getPrediction(_query: unknown): unknown {
|
|
return null;
|
|
}
|
|
|
|
async refreshPredictions(_queries: unknown): Promise<void> {
|
|
// 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>(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>(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>(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>(MosaicTelemetryService);
|
|
expect(service.isEnabled).toBe(true);
|
|
|
|
await expect(module.close()).resolves.not.toThrow();
|
|
});
|
|
});
|
|
});
|