Files
wehub-resource-sync bb5c75ce05
Component Security Validation / Security Audit (push) Has been cancelled
Deploy to Cloudflare Pages / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:58 +08:00

349 lines
11 KiB
JavaScript

/**
* Integration Tests for Analytics System
* Tests the complete analytics system integration
*/
const path = require('path');
const fs = require('fs-extra');
const { spawn } = require('child_process');
describe('Analytics System Integration', () => {
let analyticsProcess;
let testDataDir;
beforeAll(async () => {
// Create test data directory
testDataDir = path.join(__dirname, '../fixtures/test-conversations');
await fs.ensureDir(testDataDir);
// Create sample conversation files
await createTestConversationFiles(testDataDir);
});
afterAll(async () => {
// Cleanup test data
await fs.remove(testDataDir);
// Kill analytics process if running
if (analyticsProcess) {
analyticsProcess.kill('SIGTERM');
}
});
describe('Backend Integration', () => {
it('should load and analyze conversation data correctly', async () => {
const ClaudeAnalytics = require('../../src/analytics');
const analytics = new ClaudeAnalytics();
analytics.claudeDir = testDataDir;
// Mock the setup methods to avoid actual server startup
analytics.setupWebServer = jest.fn();
analytics.setupFileWatchers = jest.fn();
await analytics.loadInitialData();
expect(analytics.data.conversations).toBeDefined();
expect(analytics.data.conversations.length).toBeGreaterThan(0);
expect(analytics.data.summary).toBeDefined();
expect(analytics.data.summary.totalConversations).toBeGreaterThan(0);
});
it('should detect conversation states correctly', async () => {
const StateCalculator = require('../../src/analytics/core/StateCalculator');
const ConversationAnalyzer = require('../../src/analytics/core/ConversationAnalyzer');
const stateCalculator = new StateCalculator();
const analyzer = new ConversationAnalyzer(testDataDir);
const data = await analyzer.loadInitialData(stateCalculator);
expect(data.conversations).toBeDefined();
data.conversations.forEach(conv => {
expect(['active', 'waiting', 'idle', 'completed']).toContain(conv.status);
expect(conv.tokens).toBeGreaterThan(0);
expect(conv.messages).toBeGreaterThan(0);
});
});
it('should cache data efficiently', async () => {
const DataCache = require('../../src/analytics/data/DataCache');
const cache = new DataCache();
const testFile = path.join(testDataDir, 'conversation_1.jsonl');
// First read - cache miss
const start1 = Date.now();
const content1 = await cache.getFileContent(testFile);
const time1 = Date.now() - start1;
// Second read - cache hit
const start2 = Date.now();
const content2 = await cache.getFileContent(testFile);
const time2 = Date.now() - start2;
expect(content1).toBe(content2);
expect(time2).toBeLessThan(time1); // Cache should be faster
expect(cache.metrics.hits).toBe(1);
expect(cache.metrics.misses).toBe(1);
});
});
describe('WebSocket Integration', () => {
let webSocketServer;
let httpServer;
let mockClient;
beforeEach(async () => {
const WebSocketServer = require('../../src/analytics/notifications/WebSocketServer');
const http = require('http');
httpServer = http.createServer();
webSocketServer = new WebSocketServer(httpServer);
await new Promise((resolve) => {
httpServer.listen(0, () => {
resolve();
});
});
await webSocketServer.initialize();
});
afterEach(async () => {
if (webSocketServer) {
await webSocketServer.close();
}
if (httpServer) {
httpServer.close();
}
});
it('should handle WebSocket connections and messaging', async () => {
const WebSocket = require('ws');
const port = httpServer.address().port;
// Create WebSocket client
mockClient = new WebSocket(`ws://localhost:${port}/ws`);
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('WebSocket test timeout'));
}, 5000);
mockClient.on('open', () => {
// Send subscribe message
mockClient.send(JSON.stringify({
type: 'subscribe',
channel: 'conversation_updates'
}));
});
mockClient.on('message', (data) => {
const message = JSON.parse(data);
if (message.type === 'connection') {
expect(message.data.clientId).toBeDefined();
} else if (message.type === 'subscription_confirmed') {
expect(message.data.channel).toBe('conversation_updates');
clearTimeout(timeout);
mockClient.close();
resolve();
}
});
mockClient.on('error', (error) => {
clearTimeout(timeout);
reject(error);
});
});
});
it('should broadcast notifications to subscribed clients', async () => {
const NotificationManager = require('../../src/analytics/notifications/NotificationManager');
const notificationManager = new NotificationManager(webSocketServer);
await notificationManager.initialize();
const WebSocket = require('ws');
const port = httpServer.address().port;
mockClient = new WebSocket(`ws://localhost:${port}/ws`);
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Notification test timeout'));
}, 5000);
let subscribed = false;
mockClient.on('open', () => {
mockClient.send(JSON.stringify({
type: 'subscribe',
channel: 'conversation_updates'
}));
});
mockClient.on('message', (data) => {
const message = JSON.parse(data);
if (message.type === 'subscription_confirmed' && !subscribed) {
subscribed = true;
// Send notification
notificationManager.notifyConversationStateChange(
'conv_123', 'idle', 'active', { project: 'test' }
);
} else if (message.type === 'conversation_state_change') {
expect(message.data.conversationId).toBe('conv_123');
expect(message.data.newState).toBe('active');
clearTimeout(timeout);
mockClient.close();
resolve();
}
});
mockClient.on('error', (error) => {
clearTimeout(timeout);
reject(error);
});
});
});
});
describe('End-to-End Analytics Flow', () => {
it('should process conversation changes end-to-end', async () => {
const ClaudeAnalytics = require('../../src/analytics');
const analytics = new ClaudeAnalytics();
// Mock server setup
analytics.setupWebServer = jest.fn();
analytics.setupFileWatchers = jest.fn();
analytics.claudeDir = testDataDir;
// Initialize analytics
await analytics.loadInitialData();
const initialConversationCount = analytics.data.conversations.length;
// Create new conversation file
const newConvFile = path.join(testDataDir, 'new_conversation.jsonl');
const newConvData = {
message: {
role: 'user',
content: 'New test message'
},
timestamp: new Date().toISOString()
};
await fs.writeFile(newConvFile, JSON.stringify(newConvData) + '\n');
// Reload data
await analytics.loadInitialData();
expect(analytics.data.conversations.length).toBe(initialConversationCount + 1);
// Cleanup
await fs.remove(newConvFile);
});
});
describe('Performance Tests', () => {
it('should handle large datasets efficiently', async () => {
const ConversationAnalyzer = require('../../src/analytics/core/ConversationAnalyzer');
const DataCache = require('../../src/analytics/data/DataCache');
const cache = new DataCache();
const analyzer = new ConversationAnalyzer(testDataDir, cache);
const startTime = Date.now();
// Run analysis multiple times to test caching
for (let i = 0; i < 5; i++) {
await analyzer.loadInitialData();
}
const endTime = Date.now();
const totalTime = endTime - startTime;
// Should complete within reasonable time (adjust based on test data size)
expect(totalTime).toBeLessThan(10000); // 10 seconds max
// Cache should have improved performance
expect(cache.metrics.hits).toBeGreaterThan(0);
});
it('should handle concurrent operations safely', async () => {
const DataCache = require('../../src/analytics/data/DataCache');
const cache = new DataCache();
const testFile = path.join(testDataDir, 'conversation_1.jsonl');
// Simulate concurrent file reads
const promises = [];
for (let i = 0; i < 10; i++) {
promises.push(cache.getFileContent(testFile));
}
const results = await Promise.all(promises);
// All results should be identical
const firstResult = results[0];
results.forEach(result => {
expect(result).toBe(firstResult);
});
});
});
});
/**
* Helper function to create test conversation files
*/
async function createTestConversationFiles(testDir) {
const conversations = [
{
filename: 'conversation_1.jsonl',
data: [
{
message: { role: 'user', content: 'Hello, can you help me with JavaScript?' },
timestamp: new Date(Date.now() - 3600000).toISOString() // 1 hour ago
},
{
message: { role: 'assistant', content: 'Of course! I\'d be happy to help you with JavaScript.' },
timestamp: new Date(Date.now() - 3500000).toISOString()
},
{
message: { role: 'user', content: 'How do I create a function?' },
timestamp: new Date(Date.now() - 3000000).toISOString()
}
]
},
{
filename: 'conversation_2.jsonl',
data: [
{
message: { role: 'user', content: 'I need help with React components' },
timestamp: new Date(Date.now() - 7200000).toISOString() // 2 hours ago
},
{
message: { role: 'assistant', content: 'React components are the building blocks of React applications.' },
timestamp: new Date(Date.now() - 7000000).toISOString()
}
]
},
{
filename: 'conversation_3.jsonl',
data: [
{
message: { role: 'user', content: 'Quick question about CSS' },
timestamp: new Date(Date.now() - 60000).toISOString() // 1 minute ago
}
]
}
];
for (const conv of conversations) {
const filePath = path.join(testDir, conv.filename);
const content = conv.data.map(item => JSON.stringify(item)).join('\n') + '\n';
await fs.writeFile(filePath, content);
}
}