feat(config): Restructure .taskmasterconfig and enhance gateway integration
Config Structure Changes and Gateway Integration ## Configuration Structure Changes - Restructured .taskmasterconfig to use 'account' section for user settings - Moved userId, userEmail, mode, telemetryEnabled from global to account section - API keys remain isolated in .env file (not accessible to AI) - Enhanced getUserId() to always return value, never null (sets default '1234567890') ## Gateway Integration Enhancements - Updated registerUserWithGateway() to accept both email and userId parameters - Enhanced /auth/init endpoint integration for existing user validation - API key updates automatically written to .env during registration process - Improved user identification and validation flow ## Code Updates for New Structure - Fixed config-manager.js getter functions for account section access - Updated user-management.js to use config.account.userId/mode - Modified telemetry-submission.js to read from account section - Added getTelemetryEnabled() function with proper account section access - Enhanced telemetry configuration reading with new structure ## Comprehensive Test Updates - Updated integration tests (init-config.test.js) for new config structure - Fixed unit tests (config-manager.test.js) with updated default config - Updated telemetry tests (telemetry-submission.test.js) for account structure - Added missing getTelemetryEnabled mock to ai-services-unified.test.js - Fixed all test expectations to use config.account.* instead of config.global.* - Removed references to deprecated config.subscription object ## Configuration Access Consistency - Standardized configuration access patterns across entire codebase - Clean separation: user settings in account, API keys in .env, models/global in respective sections - All tests passing with new configuration structure - Maintained backward compatibility during transition Changes support enhanced telemetry system with proper user management and gateway integration while maintaining security through API key isolation.
This commit is contained in:
@@ -39,11 +39,11 @@ describe("TaskMaster Init Configuration Tests", () => {
|
||||
});
|
||||
|
||||
describe("getUserId functionality", () => {
|
||||
it("should read userId from config.global.userId", async () => {
|
||||
// Create config with userId in global section
|
||||
it("should read userId from config.account.userId", async () => {
|
||||
// Create config with userId in account section
|
||||
const config = {
|
||||
mode: "byok",
|
||||
global: {
|
||||
account: {
|
||||
mode: "byok",
|
||||
userId: "test-user-123",
|
||||
},
|
||||
};
|
||||
@@ -61,8 +61,9 @@ describe("TaskMaster Init Configuration Tests", () => {
|
||||
it("should set default userId if none exists", async () => {
|
||||
// Create config without userId
|
||||
const config = {
|
||||
mode: "byok",
|
||||
global: {},
|
||||
account: {
|
||||
mode: "byok",
|
||||
},
|
||||
};
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
|
||||
@@ -76,14 +77,14 @@ describe("TaskMaster Init Configuration Tests", () => {
|
||||
|
||||
// Verify it was written to config
|
||||
const savedConfig = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
||||
expect(savedConfig.global.userId).toBe("1234567890");
|
||||
expect(savedConfig.account.userId).toBe("1234567890");
|
||||
});
|
||||
|
||||
it("should return existing userId even if it's the default value", async () => {
|
||||
// Create config with default userId already set
|
||||
const config = {
|
||||
mode: "byok",
|
||||
global: {
|
||||
account: {
|
||||
mode: "byok",
|
||||
userId: "1234567890",
|
||||
},
|
||||
};
|
||||
@@ -103,27 +104,17 @@ describe("TaskMaster Init Configuration Tests", () => {
|
||||
it("should store mode (byok/hosted) in config", () => {
|
||||
// Test that mode gets stored correctly
|
||||
const config = {
|
||||
mode: "hosted",
|
||||
global: {
|
||||
account: {
|
||||
mode: "hosted",
|
||||
userId: "test-user-789",
|
||||
},
|
||||
subscription: {
|
||||
plan: "starter",
|
||||
credits: 50,
|
||||
price: 5,
|
||||
},
|
||||
};
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
|
||||
// Read config back
|
||||
const savedConfig = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
||||
expect(savedConfig.mode).toBe("hosted");
|
||||
expect(savedConfig.global.userId).toBe("test-user-789");
|
||||
expect(savedConfig.subscription).toEqual({
|
||||
plan: "starter",
|
||||
credits: 50,
|
||||
price: 5,
|
||||
});
|
||||
expect(savedConfig.account.mode).toBe("hosted");
|
||||
expect(savedConfig.account.userId).toBe("test-user-789");
|
||||
});
|
||||
|
||||
it("should store API key in .env file (NOT config)", () => {
|
||||
@@ -138,8 +129,8 @@ describe("TaskMaster Init Configuration Tests", () => {
|
||||
|
||||
// Test that API key is NOT in config
|
||||
const config = {
|
||||
mode: "byok",
|
||||
global: {
|
||||
account: {
|
||||
mode: "byok",
|
||||
userId: "test-user-abc",
|
||||
},
|
||||
};
|
||||
@@ -200,51 +191,42 @@ describe("TaskMaster Init Configuration Tests", () => {
|
||||
it("should maintain consistent structure for both BYOK and hosted modes", () => {
|
||||
// Test BYOK mode structure
|
||||
const byokConfig = {
|
||||
mode: "byok",
|
||||
global: {
|
||||
account: {
|
||||
mode: "byok",
|
||||
userId: "byok-user-123",
|
||||
telemetryEnabled: false,
|
||||
},
|
||||
telemetryEnabled: false,
|
||||
};
|
||||
fs.writeFileSync(configPath, JSON.stringify(byokConfig, null, 2));
|
||||
|
||||
let config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
||||
expect(config.mode).toBe("byok");
|
||||
expect(config.global.userId).toBe("byok-user-123");
|
||||
expect(config.telemetryEnabled).toBe(false);
|
||||
expect(config.subscription).toBeUndefined();
|
||||
expect(config.account.mode).toBe("byok");
|
||||
expect(config.account.userId).toBe("byok-user-123");
|
||||
expect(config.account.telemetryEnabled).toBe(false);
|
||||
|
||||
// Test hosted mode structure
|
||||
const hostedConfig = {
|
||||
mode: "hosted",
|
||||
global: {
|
||||
account: {
|
||||
mode: "hosted",
|
||||
userId: "hosted-user-456",
|
||||
},
|
||||
telemetryEnabled: true,
|
||||
subscription: {
|
||||
plan: "pro",
|
||||
credits: 250,
|
||||
price: 20,
|
||||
telemetryEnabled: true,
|
||||
},
|
||||
};
|
||||
fs.writeFileSync(configPath, JSON.stringify(hostedConfig, null, 2));
|
||||
|
||||
config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
||||
expect(config.mode).toBe("hosted");
|
||||
expect(config.global.userId).toBe("hosted-user-456");
|
||||
expect(config.telemetryEnabled).toBe(true);
|
||||
expect(config.subscription).toEqual({
|
||||
plan: "pro",
|
||||
credits: 250,
|
||||
price: 20,
|
||||
});
|
||||
expect(config.account.mode).toBe("hosted");
|
||||
expect(config.account.userId).toBe("hosted-user-456");
|
||||
expect(config.account.telemetryEnabled).toBe(true);
|
||||
});
|
||||
|
||||
it("should use consistent userId location (config.global.userId)", async () => {
|
||||
it("should use consistent userId location (config.account.userId)", async () => {
|
||||
const config = {
|
||||
mode: "byok",
|
||||
global: {
|
||||
account: {
|
||||
mode: "byok",
|
||||
userId: "consistent-user-789",
|
||||
},
|
||||
global: {
|
||||
logLevel: "info",
|
||||
},
|
||||
};
|
||||
@@ -260,9 +242,9 @@ describe("TaskMaster Init Configuration Tests", () => {
|
||||
|
||||
expect(userId).toBe("consistent-user-789");
|
||||
|
||||
// Verify it's in global section, not root
|
||||
// Verify it's in account section, not root
|
||||
const savedConfig = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
||||
expect(savedConfig.global.userId).toBe("consistent-user-789");
|
||||
expect(savedConfig.account.userId).toBe("consistent-user-789");
|
||||
expect(savedConfig.userId).toBeUndefined(); // Should NOT be in root
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -231,4 +231,105 @@ describe("Telemetry Enhancements - Task 90", () => {
|
||||
expect(result.userId).toBe("test-user-123");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Subtask 90.4: Non-AI command telemetry queue", () => {
|
||||
let mockTelemetryQueue;
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock the telemetry queue module
|
||||
mockTelemetryQueue = {
|
||||
addToQueue: jest.fn(),
|
||||
processQueue: jest.fn(),
|
||||
startBackgroundProcessor: jest.fn(),
|
||||
stopBackgroundProcessor: jest.fn(),
|
||||
getQueueStats: jest.fn(() => ({ pending: 0, processed: 0, failed: 0 })),
|
||||
};
|
||||
});
|
||||
|
||||
it("should add non-AI command telemetry to queue without blocking", async () => {
|
||||
const commandData = {
|
||||
timestamp: new Date().toISOString(),
|
||||
userId: "test-user-123",
|
||||
commandName: "list-tasks",
|
||||
executionTimeMs: 45,
|
||||
success: true,
|
||||
arguments: { status: "pending" },
|
||||
};
|
||||
|
||||
// Should return immediately without waiting
|
||||
const startTime = Date.now();
|
||||
mockTelemetryQueue.addToQueue(commandData);
|
||||
const endTime = Date.now();
|
||||
|
||||
expect(endTime - startTime).toBeLessThan(10); // Should be nearly instantaneous
|
||||
expect(mockTelemetryQueue.addToQueue).toHaveBeenCalledWith(commandData);
|
||||
});
|
||||
|
||||
it("should process queued telemetry in background", async () => {
|
||||
const queuedItems = [
|
||||
{
|
||||
commandName: "set-status",
|
||||
executionTimeMs: 23,
|
||||
success: true,
|
||||
},
|
||||
{
|
||||
commandName: "next-task",
|
||||
executionTimeMs: 12,
|
||||
success: true,
|
||||
},
|
||||
];
|
||||
|
||||
mockTelemetryQueue.processQueue.mockResolvedValue({
|
||||
processed: 2,
|
||||
failed: 0,
|
||||
errors: [],
|
||||
});
|
||||
|
||||
const result = await mockTelemetryQueue.processQueue();
|
||||
|
||||
expect(result.processed).toBe(2);
|
||||
expect(result.failed).toBe(0);
|
||||
expect(mockTelemetryQueue.processQueue).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle queue processing failures gracefully", async () => {
|
||||
mockTelemetryQueue.processQueue.mockResolvedValue({
|
||||
processed: 1,
|
||||
failed: 1,
|
||||
errors: ["Network timeout for item 2"],
|
||||
});
|
||||
|
||||
const result = await mockTelemetryQueue.processQueue();
|
||||
|
||||
expect(result.processed).toBe(1);
|
||||
expect(result.failed).toBe(1);
|
||||
expect(result.errors).toContain("Network timeout for item 2");
|
||||
});
|
||||
|
||||
it("should provide queue statistics", () => {
|
||||
mockTelemetryQueue.getQueueStats.mockReturnValue({
|
||||
pending: 5,
|
||||
processed: 127,
|
||||
failed: 3,
|
||||
lastProcessedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
const stats = mockTelemetryQueue.getQueueStats();
|
||||
|
||||
expect(stats.pending).toBe(5);
|
||||
expect(stats.processed).toBe(127);
|
||||
expect(stats.failed).toBe(3);
|
||||
expect(stats.lastProcessedAt).toBeDefined();
|
||||
});
|
||||
|
||||
it("should start and stop background processor", () => {
|
||||
mockTelemetryQueue.startBackgroundProcessor(30000); // 30 second interval
|
||||
expect(mockTelemetryQueue.startBackgroundProcessor).toHaveBeenCalledWith(
|
||||
30000
|
||||
);
|
||||
|
||||
mockTelemetryQueue.stopBackgroundProcessor();
|
||||
expect(mockTelemetryQueue.stopBackgroundProcessor).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,6 +34,7 @@ jest.unstable_mockModule(
|
||||
getProjectName: jest.fn(() => "Test Project"),
|
||||
getDefaultPriority: jest.fn(() => "medium"),
|
||||
getDefaultNumTasks: jest.fn(() => 10),
|
||||
getTelemetryEnabled: jest.fn(() => true),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -48,17 +49,17 @@ const { getConfig } = await import(
|
||||
"../../../../scripts/modules/config-manager.js"
|
||||
);
|
||||
|
||||
describe("Telemetry Submission Service - Task 90.2", () => {
|
||||
describe("Telemetry Submission Service", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
global.fetch.mockClear();
|
||||
});
|
||||
|
||||
describe("Subtask 90.2: Send telemetry data to remote database endpoint", () => {
|
||||
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({
|
||||
global: {
|
||||
account: {
|
||||
userId: "test-user-id",
|
||||
},
|
||||
});
|
||||
@@ -113,7 +114,7 @@ describe("Telemetry Submission Service - Task 90.2", () => {
|
||||
|
||||
it("should implement retry logic for failed requests", async () => {
|
||||
getConfig.mockReturnValue({
|
||||
global: {
|
||||
account: {
|
||||
userId: "test-user-id",
|
||||
},
|
||||
});
|
||||
@@ -149,7 +150,7 @@ describe("Telemetry Submission Service - Task 90.2", () => {
|
||||
|
||||
it("should handle failures gracefully without blocking execution", async () => {
|
||||
getConfig.mockReturnValue({
|
||||
global: {
|
||||
account: {
|
||||
userId: "test-user-id",
|
||||
},
|
||||
});
|
||||
@@ -180,8 +181,16 @@ describe("Telemetry Submission Service - Task 90.2", () => {
|
||||
}, 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({
|
||||
telemetryEnabled: false,
|
||||
account: {
|
||||
telemetryEnabled: false,
|
||||
},
|
||||
});
|
||||
|
||||
const telemetryData = {
|
||||
@@ -198,11 +207,14 @@ describe("Telemetry Submission Service - Task 90.2", () => {
|
||||
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({
|
||||
global: {
|
||||
account: {
|
||||
userId: "test-user-id",
|
||||
},
|
||||
});
|
||||
@@ -229,7 +241,7 @@ describe("Telemetry Submission Service - Task 90.2", () => {
|
||||
|
||||
it("should handle HTTP error responses appropriately", async () => {
|
||||
getConfig.mockReturnValue({
|
||||
global: {
|
||||
account: {
|
||||
userId: "test-user-id",
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user