chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,421 @@
|
||||
/**
|
||||
* 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);
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user