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

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);
});