422 lines
11 KiB
JavaScript
422 lines
11 KiB
JavaScript
/**
|
|
* API Endpoints Test Suite
|
|
*
|
|
* CRITICAL: These tests MUST pass before deploying to production.
|
|
* Tracking endpoints are essential for component metrics.
|
|
*
|
|
* Run: npm run test:api
|
|
*/
|
|
|
|
const axios = require('axios');
|
|
|
|
// Configuration
|
|
const BASE_URL = process.env.API_BASE_URL || 'https://aitmpl.com';
|
|
const TIMEOUT = 30000; // 30 seconds
|
|
|
|
describe('API Endpoints - Critical Tests', () => {
|
|
|
|
describe('🔴 CRITICAL: Component Download Tracking', () => {
|
|
|
|
test('POST /api/track-download-supabase should be available', async () => {
|
|
const response = await axios.post(
|
|
`${BASE_URL}/api/track-download-supabase`,
|
|
{
|
|
type: 'agent',
|
|
name: 'test-agent',
|
|
path: 'agents/test',
|
|
category: 'test',
|
|
cliVersion: '1.0.0'
|
|
},
|
|
{
|
|
timeout: TIMEOUT,
|
|
validateStatus: (status) => status < 500 // Accept any non-5xx status
|
|
}
|
|
);
|
|
|
|
// Endpoint must respond (can be 200, 400, etc. but NOT 500)
|
|
expect(response.status).toBeLessThan(500);
|
|
expect(response.status).toBeGreaterThanOrEqual(200);
|
|
}, TIMEOUT);
|
|
|
|
test('POST /api/track-download-supabase should reject requests without data', async () => {
|
|
const response = await axios.post(
|
|
`${BASE_URL}/api/track-download-supabase`,
|
|
{},
|
|
{
|
|
timeout: TIMEOUT,
|
|
validateStatus: () => true // Accept any status
|
|
}
|
|
);
|
|
|
|
// Should return 400 Bad Request
|
|
expect(response.status).toBe(400);
|
|
expect(response.data).toHaveProperty('error');
|
|
}, TIMEOUT);
|
|
|
|
test('POST /api/track-download-supabase should validate component type', async () => {
|
|
const response = await axios.post(
|
|
`${BASE_URL}/api/track-download-supabase`,
|
|
{
|
|
type: 'invalid-type',
|
|
name: 'test',
|
|
path: 'test/path'
|
|
},
|
|
{
|
|
timeout: TIMEOUT,
|
|
validateStatus: () => true
|
|
}
|
|
);
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(response.data.error).toContain('type');
|
|
}, TIMEOUT);
|
|
|
|
});
|
|
|
|
describe('🟡 Discord Bot Integration', () => {
|
|
|
|
test('POST /api/discord/interactions should be available', async () => {
|
|
const response = await axios.post(
|
|
`${BASE_URL}/api/discord/interactions`,
|
|
{},
|
|
{
|
|
timeout: TIMEOUT,
|
|
validateStatus: () => true
|
|
}
|
|
);
|
|
|
|
// Endpoint should respond (even if Discord validation fails)
|
|
expect(response.status).toBeLessThan(500);
|
|
}, TIMEOUT);
|
|
|
|
});
|
|
|
|
describe('🟢 Claude Code Changelog Monitor', () => {
|
|
|
|
test('GET /api/claude-code-check should be available', async () => {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/api/claude-code-check`,
|
|
{
|
|
timeout: TIMEOUT,
|
|
validateStatus: () => true
|
|
}
|
|
);
|
|
|
|
// Endpoint should respond
|
|
expect(response.status).toBeLessThan(500);
|
|
}, TIMEOUT);
|
|
|
|
});
|
|
|
|
describe('🔵 Command Usage Tracking', () => {
|
|
|
|
test('POST /api/track-command-usage should be available', async () => {
|
|
const response = await axios.post(
|
|
`${BASE_URL}/api/track-command-usage`,
|
|
{
|
|
command: 'analytics',
|
|
cliVersion: '1.26.3',
|
|
nodeVersion: 'v18.0.0',
|
|
platform: 'darwin',
|
|
arch: 'arm64',
|
|
sessionId: 'test-session-123',
|
|
metadata: { tunnel: false }
|
|
},
|
|
{
|
|
timeout: TIMEOUT,
|
|
validateStatus: (status) => status < 500
|
|
}
|
|
);
|
|
|
|
// Endpoint must respond (can be 200, 400, etc. but NOT 500)
|
|
expect(response.status).toBeLessThan(500);
|
|
expect(response.status).toBeGreaterThanOrEqual(200);
|
|
}, TIMEOUT);
|
|
|
|
test('POST /api/track-command-usage should reject invalid commands', async () => {
|
|
const response = await axios.post(
|
|
`${BASE_URL}/api/track-command-usage`,
|
|
{
|
|
command: 'invalid-command',
|
|
cliVersion: '1.26.3',
|
|
nodeVersion: 'v18.0.0',
|
|
platform: 'darwin'
|
|
},
|
|
{
|
|
timeout: TIMEOUT,
|
|
validateStatus: () => true
|
|
}
|
|
);
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(response.data).toHaveProperty('error');
|
|
expect(response.data.error).toContain('Invalid command');
|
|
}, TIMEOUT);
|
|
|
|
test('POST /api/track-command-usage should reject requests without command', async () => {
|
|
const response = await axios.post(
|
|
`${BASE_URL}/api/track-command-usage`,
|
|
{
|
|
cliVersion: '1.26.3',
|
|
platform: 'darwin'
|
|
},
|
|
{
|
|
timeout: TIMEOUT,
|
|
validateStatus: () => true
|
|
}
|
|
);
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(response.data).toHaveProperty('error');
|
|
}, TIMEOUT);
|
|
|
|
});
|
|
|
|
describe('📊 API Health Check', () => {
|
|
|
|
test('All critical endpoints should respond within 30s', async () => {
|
|
const startTime = Date.now();
|
|
|
|
const endpoints = [
|
|
'/api/track-download-supabase',
|
|
'/api/track-command-usage',
|
|
'/api/discord/interactions',
|
|
'/api/claude-code-check'
|
|
];
|
|
|
|
for (const endpoint of endpoints) {
|
|
const method = endpoint.includes('track-') || endpoint.includes('discord')
|
|
? 'post'
|
|
: 'get';
|
|
|
|
try {
|
|
await axios[method](
|
|
`${BASE_URL}${endpoint}`,
|
|
method === 'post' ? {} : undefined,
|
|
{
|
|
timeout: TIMEOUT,
|
|
validateStatus: () => true
|
|
}
|
|
);
|
|
} catch (error) {
|
|
// If it fails due to timeout or connection, test should fail
|
|
if (error.code === 'ECONNABORTED' || error.code === 'ENOTFOUND') {
|
|
throw new Error(`Endpoint ${endpoint} not responding: ${error.message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
const totalTime = Date.now() - startTime;
|
|
expect(totalTime).toBeLessThan(30000);
|
|
}, 45000); // Total timeout of 45s for this test
|
|
|
|
});
|
|
|
|
});
|
|
|
|
describe('API Endpoints - Functional Tests', () => {
|
|
|
|
describe('Component Download Tracking - Data Validation', () => {
|
|
|
|
const validTypes = ['agent', 'command', 'setting', 'hook', 'mcp', 'skill', 'template'];
|
|
|
|
test('should accept all valid component types', async () => {
|
|
for (const type of validTypes) {
|
|
const response = await axios.post(
|
|
`${BASE_URL}/api/track-download-supabase`,
|
|
{
|
|
type,
|
|
name: `test-${type}`,
|
|
path: `${type}s/test`
|
|
},
|
|
{
|
|
timeout: TIMEOUT,
|
|
validateStatus: () => true
|
|
}
|
|
);
|
|
|
|
// Should be 200 (success) or 500 if DB fails, but NOT 400 (validation)
|
|
expect([200, 500]).toContain(response.status);
|
|
}
|
|
}, TIMEOUT * validTypes.length);
|
|
|
|
test('should reject names that are too long', async () => {
|
|
const longName = 'a'.repeat(300); // More than 255 characters
|
|
|
|
const response = await axios.post(
|
|
`${BASE_URL}/api/track-download-supabase`,
|
|
{
|
|
type: 'agent',
|
|
name: longName,
|
|
path: 'agents/test'
|
|
},
|
|
{
|
|
timeout: TIMEOUT,
|
|
validateStatus: () => true
|
|
}
|
|
);
|
|
|
|
expect(response.status).toBe(400);
|
|
}, TIMEOUT);
|
|
|
|
});
|
|
|
|
describe('Claude Code Monitor - Parser Tests', () => {
|
|
|
|
test('GET /api/claude-code-check should return valid structure', async () => {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/api/claude-code-check`,
|
|
{
|
|
timeout: TIMEOUT,
|
|
validateStatus: () => true
|
|
}
|
|
);
|
|
|
|
if (response.status === 200) {
|
|
// If successful, should have correct structure
|
|
expect(response.data).toHaveProperty('status');
|
|
|
|
if (response.data.status === 'success') {
|
|
expect(response.data).toHaveProperty('version');
|
|
expect(response.data).toHaveProperty('changes');
|
|
expect(response.data).toHaveProperty('discord');
|
|
}
|
|
}
|
|
}, TIMEOUT);
|
|
|
|
});
|
|
|
|
describe('Command Usage Tracking - Data Validation', () => {
|
|
|
|
const validCommands = [
|
|
'chats',
|
|
'analytics',
|
|
'health-check',
|
|
'plugins',
|
|
'sandbox',
|
|
'agents',
|
|
'chats-mobile',
|
|
'studio',
|
|
'command-stats',
|
|
'hook-stats',
|
|
'mcp-stats'
|
|
];
|
|
|
|
test('should accept all valid commands', async () => {
|
|
for (const command of validCommands) {
|
|
const response = await axios.post(
|
|
`${BASE_URL}/api/track-command-usage`,
|
|
{
|
|
command,
|
|
cliVersion: '1.26.3',
|
|
nodeVersion: 'v18.0.0',
|
|
platform: 'darwin',
|
|
arch: 'arm64',
|
|
sessionId: 'test-session',
|
|
metadata: { test: true }
|
|
},
|
|
{
|
|
timeout: TIMEOUT,
|
|
validateStatus: () => true
|
|
}
|
|
);
|
|
|
|
// Should be 200 (success) or 500 if DB fails, but NOT 400 (validation)
|
|
expect([200, 500]).toContain(response.status);
|
|
}
|
|
}, TIMEOUT * validCommands.length);
|
|
|
|
test('should handle metadata correctly', async () => {
|
|
const response = await axios.post(
|
|
`${BASE_URL}/api/track-command-usage`,
|
|
{
|
|
command: 'analytics',
|
|
cliVersion: '1.26.3',
|
|
nodeVersion: 'v18.0.0',
|
|
platform: 'darwin',
|
|
arch: 'arm64',
|
|
sessionId: 'test-session',
|
|
metadata: {
|
|
tunnel: true,
|
|
customData: 'test-value'
|
|
}
|
|
},
|
|
{
|
|
timeout: TIMEOUT,
|
|
validateStatus: () => true
|
|
}
|
|
);
|
|
|
|
// Should accept metadata as JSONB
|
|
expect([200, 500]).toContain(response.status);
|
|
}, TIMEOUT);
|
|
|
|
test('should reject commands that are too long', async () => {
|
|
const longCommand = 'a'.repeat(150);
|
|
|
|
const response = await axios.post(
|
|
`${BASE_URL}/api/track-command-usage`,
|
|
{
|
|
command: longCommand,
|
|
cliVersion: '1.26.3',
|
|
platform: 'darwin'
|
|
},
|
|
{
|
|
timeout: TIMEOUT,
|
|
validateStatus: () => true
|
|
}
|
|
);
|
|
|
|
expect(response.status).toBe(400);
|
|
}, TIMEOUT);
|
|
|
|
test('should handle missing optional fields', async () => {
|
|
const response = await axios.post(
|
|
`${BASE_URL}/api/track-command-usage`,
|
|
{
|
|
command: 'analytics'
|
|
// Missing optional fields like cliVersion, sessionId, metadata
|
|
},
|
|
{
|
|
timeout: TIMEOUT,
|
|
validateStatus: () => true
|
|
}
|
|
);
|
|
|
|
// Should still work with just command name
|
|
expect([200, 500]).toContain(response.status);
|
|
}, TIMEOUT);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
describe('API Security & CORS', () => {
|
|
|
|
test('endpoints should have correct CORS headers', async () => {
|
|
const response = await axios.options(
|
|
`${BASE_URL}/api/track-download-supabase`,
|
|
{
|
|
timeout: TIMEOUT,
|
|
validateStatus: () => true
|
|
}
|
|
);
|
|
|
|
expect(response.headers['access-control-allow-origin']).toBeDefined();
|
|
}, TIMEOUT);
|
|
|
|
test('endpoints should handle incorrect HTTP methods', async () => {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/api/track-download-supabase`,
|
|
{
|
|
timeout: TIMEOUT,
|
|
validateStatus: () => true
|
|
}
|
|
);
|
|
|
|
// track-download only accepts POST, should return 405
|
|
expect(response.status).toBe(405);
|
|
}, TIMEOUT);
|
|
|
|
});
|