Files
claude-task-master/tests/unit/scripts/modules/telemetry-submission.test.js
Eyal Toledano 685365270d feat: integrate Supabase authenticated users
- Updated init.js, ai-services-unified.js, user-management.js, telemetry-submission.js, and .taskmasterconfig to support Supabase authentication flow and authenticated gateway calls
2025-06-04 18:53:28 -04:00

402 lines
12 KiB
JavaScript

/**
* Unit Tests for Telemetry Submission Service - Task 90.2
* Tests the secure telemetry submission with gateway integration
*/
import { jest } from "@jest/globals";
// Mock config-manager before importing submitTelemetryData
jest.unstable_mockModule(
"../../../../scripts/modules/config-manager.js",
() => ({
getConfig: jest.fn(),
getDebugFlag: jest.fn(() => false),
getLogLevel: jest.fn(() => "info"),
getMainProvider: jest.fn(() => "openai"),
getMainModelId: jest.fn(() => "gpt-4"),
getResearchProvider: jest.fn(() => "openai"),
getResearchModelId: jest.fn(() => "gpt-4"),
getFallbackProvider: jest.fn(() => "openai"),
getFallbackModelId: jest.fn(() => "gpt-3.5-turbo"),
getParametersForRole: jest.fn(() => ({
maxTokens: 4000,
temperature: 0.7,
})),
getUserId: jest.fn(() => "test-user-id"),
MODEL_MAP: {},
getBaseUrlForRole: jest.fn(() => null),
isApiKeySet: jest.fn(() => true),
getOllamaBaseURL: jest.fn(() => "http://localhost:11434/api"),
getAzureBaseURL: jest.fn(() => null),
getVertexProjectId: jest.fn(() => null),
getVertexLocation: jest.fn(() => null),
getDefaultSubtasks: jest.fn(() => 5),
getProjectName: jest.fn(() => "Test Project"),
getDefaultPriority: jest.fn(() => "medium"),
getDefaultNumTasks: jest.fn(() => 10),
getTelemetryEnabled: jest.fn(() => true),
})
);
// Mock fetch globally
global.fetch = jest.fn();
// Import after mocking
const { submitTelemetryData, registerUserWithGateway } = await import(
"../../../../scripts/modules/telemetry-submission.js"
);
const { getConfig } = await import(
"../../../../scripts/modules/config-manager.js"
);
describe("Telemetry Submission Service", () => {
beforeEach(() => {
jest.clearAllMocks();
global.fetch.mockClear();
});
describe("should send telemetry data to remote database endpoint", () => {
it("should successfully submit telemetry data to hardcoded gateway endpoint", async () => {
// Mock successful config with proper structure
getConfig.mockReturnValue({
account: {
userId: "test-user-id",
email: "test@example.com",
},
});
// Mock environment variables for telemetry config
process.env.TASKMASTER_API_KEY = "test-api-key";
// Mock successful response
global.fetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ id: "telemetry-123" }),
});
const telemetryData = {
timestamp: new Date().toISOString(),
userId: "test-user-id",
commandName: "test-command",
modelUsed: "claude-3-sonnet",
totalCost: 0.001,
currency: "USD",
commandArgs: { secret: "should-be-sent" },
fullOutput: { debug: "should-be-sent" },
};
const result = await submitTelemetryData(telemetryData);
expect(result.success).toBe(true);
expect(result.id).toBe("telemetry-123");
expect(global.fetch).toHaveBeenCalledWith(
"http://localhost:4444/api/v1/telemetry", // Hardcoded endpoint
expect.objectContaining({
method: "POST",
headers: {
"Content-Type": "application/json",
"x-taskmaster-service-id": "98fb3198-2dfc-42d1-af53-07b99e4f3bde",
Authorization: "Bearer test-api-key",
"X-User-Email": "test@example.com",
},
body: expect.stringContaining('"commandName":"test-command"'),
})
);
// Verify sensitive data IS included in submission to gateway
const sentData = JSON.parse(global.fetch.mock.calls[0][1].body);
expect(sentData.commandArgs).toEqual({ secret: "should-be-sent" });
expect(sentData.fullOutput).toEqual({ debug: "should-be-sent" });
// Clean up
delete process.env.TASKMASTER_API_KEY;
});
it("should implement retry logic for failed requests", async () => {
getConfig.mockReturnValue({
account: {
userId: "test-user-id",
email: "test@example.com",
},
});
// Mock environment variables
process.env.TASKMASTER_API_KEY = "test-api-key";
// Mock 3 network failures then final HTTP error
global.fetch
.mockRejectedValueOnce(new Error("Network error"))
.mockRejectedValueOnce(new Error("Network error"))
.mockRejectedValueOnce(new Error("Network error"));
const telemetryData = {
timestamp: new Date().toISOString(),
userId: "test-user-id",
commandName: "test-command",
totalCost: 0.001,
currency: "USD",
};
const result = await submitTelemetryData(telemetryData);
expect(result.success).toBe(false);
expect(result.error).toContain("Network error");
expect(global.fetch).toHaveBeenCalledTimes(3);
// Clean up
delete process.env.TASKMASTER_API_KEY;
}, 10000);
it("should handle failures gracefully without blocking execution", async () => {
getConfig.mockReturnValue({
account: {
userId: "test-user-id",
email: "test@example.com",
},
});
// Mock environment variables
process.env.TASKMASTER_API_KEY = "test-api-key";
global.fetch.mockRejectedValue(new Error("Network failure"));
const telemetryData = {
timestamp: new Date().toISOString(),
userId: "test-user-id",
commandName: "test-command",
totalCost: 0.001,
currency: "USD",
};
const result = await submitTelemetryData(telemetryData);
expect(result.success).toBe(false);
expect(result.error).toContain("Network failure");
expect(global.fetch).toHaveBeenCalledTimes(3); // All retries attempted
// Clean up
delete process.env.TASKMASTER_API_KEY;
}, 10000);
it("should respect user opt-out preferences", async () => {
// Mock getTelemetryEnabled to return false for this test
const { getTelemetryEnabled } = await import(
"../../../../scripts/modules/config-manager.js"
);
getTelemetryEnabled.mockReturnValue(false);
getConfig.mockReturnValue({
account: {
telemetryEnabled: false,
},
});
const telemetryData = {
timestamp: new Date().toISOString(),
userId: "test-user-id",
commandName: "test-command",
totalCost: 0.001,
currency: "USD",
};
const result = await submitTelemetryData(telemetryData);
expect(result.success).toBe(true);
expect(result.skipped).toBe(true);
expect(result.reason).toBe("Telemetry disabled by user preference");
expect(global.fetch).not.toHaveBeenCalled();
// Reset the mock for other tests
getTelemetryEnabled.mockReturnValue(true);
});
it("should validate telemetry data before submission", async () => {
getConfig.mockReturnValue({
account: {
userId: "test-user-id",
email: "test@example.com",
},
});
// Mock environment variables so config is valid
process.env.TASKMASTER_API_KEY = "test-api-key";
const invalidTelemetryData = {
// Missing required fields
commandName: "test-command",
};
const result = await submitTelemetryData(invalidTelemetryData);
expect(result.success).toBe(false);
expect(result.error).toContain("Telemetry data validation failed");
expect(global.fetch).not.toHaveBeenCalled();
// Clean up
delete process.env.TASKMASTER_API_KEY;
});
it("should handle HTTP error responses appropriately", async () => {
getConfig.mockReturnValue({
account: {
userId: "test-user-id",
email: "test@example.com",
},
});
// Mock environment variables with invalid API key
process.env.TASKMASTER_API_KEY = "invalid-key";
global.fetch.mockResolvedValueOnce({
ok: false,
status: 401,
statusText: "Unauthorized",
json: async () => ({}),
});
const telemetryData = {
timestamp: new Date().toISOString(),
userId: "test-user-id",
commandName: "test-command",
totalCost: 0.001,
currency: "USD",
};
const result = await submitTelemetryData(telemetryData);
expect(result.success).toBe(false);
expect(result.statusCode).toBe(401);
expect(global.fetch).toHaveBeenCalledTimes(1); // No retries for auth errors
// Clean up
delete process.env.TASKMASTER_API_KEY;
});
});
describe("Gateway User Registration", () => {
it("should successfully register a user with gateway using /auth/init", async () => {
const mockResponse = {
success: true,
message: "New user created successfully",
data: {
userId: "test-user-id",
isNewUser: true,
user: {
email: "test@example.com",
planType: "free",
creditsBalance: 0,
},
token: "test-api-key",
},
timestamp: new Date().toISOString(),
};
global.fetch.mockResolvedValueOnce({
ok: true,
json: async () => mockResponse,
});
const result = await registerUserWithGateway("test@example.com");
expect(result).toEqual({
success: true,
apiKey: "test-api-key",
userId: "test-user-id",
email: "test@example.com",
isNewUser: true,
});
expect(global.fetch).toHaveBeenCalledWith(
"http://localhost:4444/auth/init",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email: "test@example.com" }),
}
);
});
it("should handle existing user with /auth/init", async () => {
const mockResponse = {
success: true,
message: "Existing user found",
data: {
userId: "existing-user-id",
isNewUser: false,
user: {
email: "existing@example.com",
planType: "free",
creditsBalance: 20,
},
token: "existing-api-key",
},
timestamp: new Date().toISOString(),
};
global.fetch.mockResolvedValueOnce({
ok: true,
json: async () => mockResponse,
});
const result = await registerUserWithGateway("existing@example.com");
expect(result).toEqual({
success: true,
apiKey: "existing-api-key",
userId: "existing-user-id",
email: "existing@example.com",
isNewUser: false,
});
});
it("should handle registration failures gracefully", async () => {
global.fetch.mockResolvedValueOnce({
ok: false,
status: 500,
statusText: "Internal Server Error",
});
const result = await registerUserWithGateway("test@example.com");
expect(result).toEqual({
success: false,
error: "Gateway registration failed: 500 Internal Server Error",
});
});
it("should handle network errors during registration", async () => {
global.fetch.mockRejectedValueOnce(new Error("Network error"));
const result = await registerUserWithGateway("test@example.com");
expect(result).toEqual({
success: false,
error: "Gateway registration error: Network error",
});
});
it("should handle invalid response format from /auth/init", async () => {
const mockResponse = {
success: false,
error: "Invalid email format",
timestamp: new Date().toISOString(),
};
global.fetch.mockResolvedValueOnce({
ok: false,
status: 401,
statusText: "Unauthorized",
});
const result = await registerUserWithGateway("invalid-email");
expect(result).toEqual({
success: false,
error: "Gateway registration failed: 401 Unauthorized",
});
});
});
});