import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' // Mock fetch globally const mockFetch = vi.fn() global.fetch = mockFetch describe('Sites Verification API', () => { beforeEach(() => { mockFetch.mockReset() }) afterEach(() => { vi.clearAllMocks() }) describe('checkLlmsTxtUrl logic', () => { it('should detect valid llms.txt content', () => { const validContent = `# Project Documentation ## Getting Started This is the documentation for our project. ` // Content starts with # - valid expect(validContent.trim().startsWith('#')).toBe(true) }) it('should reject HTML error pages', () => { const htmlContent = ` 404 Not Found Page not found ` // Contains { const shortContent = '# Title\nShort doc' const longContent = '# Title\n' + 'Documentation line\n'.repeat(100) // Short content is standard expect(shortContent.split('\n').length > 50).toBe(false) // Long content is full expect(longContent.split('\n').length > 50).toBe(true) }) it('should detect full llms.txt by heading depth', () => { const standardContent = '# Title\nSome content' const fullContent = '# Title\n## Section\n### Subsection\nContent' expect(fullContent.includes('## ')).toBe(true) expect(fullContent.includes('### ')).toBe(true) }) }) describe('URL normalization', () => { it('should extract origin from URL', () => { const testUrls = [ { input: 'https://docs.example.com/some/path', expected: 'https://docs.example.com' }, { input: 'https://example.com/docs', expected: 'https://example.com' }, { input: 'https://api.example.com:8080/v1', expected: 'https://api.example.com:8080' }, ] testUrls.forEach(({ input, expected }) => { const url = new URL(input) expect(url.origin).toBe(expected) }) }) it('should handle various llms.txt locations', () => { const baseUrl = 'https://example.com' const locations = [ `${baseUrl}/llms.txt`, `${baseUrl}/llms-full.txt`, `${baseUrl}/docs/llms.txt`, `${baseUrl}/docs/llms-full.txt`, ] expect(locations).toHaveLength(4) expect(locations[0]).toBe('https://example.com/llms.txt') expect(locations[1]).toBe('https://example.com/llms-full.txt') }) }) describe('Verification result structure', () => { it('should have correct structure for online status', () => { const result = { status: 'online' as const, llmsTxt: { exists: true, url: 'https://example.com/llms.txt', size: 1024, type: 'full' as const, }, responseTime: 250, checkedAt: new Date().toISOString(), } expect(result.status).toBe('online') expect(result.llmsTxt?.exists).toBe(true) expect(result.responseTime).toBeGreaterThan(0) expect(result.checkedAt).toBeDefined() }) it('should have correct structure for offline status', () => { const result = { status: 'offline' as const, checkedAt: new Date().toISOString(), responseTime: 100, } expect(result.status).toBe('offline') expect(result.checkedAt).toBeDefined() }) it('should have correct structure for error status', () => { const result = { status: 'error' as const, checkedAt: new Date().toISOString(), responseTime: 50, error: 'Connection timeout', } expect(result.status).toBe('error') expect(result.error).toBeDefined() }) }) describe('Cache behavior', () => { it('should cache results with TTL', () => { const cache = new Map() const CACHE_TTL_MS = 5 * 60 * 1000 // 5 minutes const result = { status: 'online' } const timestamp = Date.now() cache.set('https://example.com', { result, timestamp }) // Check if cache is fresh const cached = cache.get('https://example.com') expect(cached).toBeDefined() expect(Date.now() - cached!.timestamp < CACHE_TTL_MS).toBe(true) }) it('should expire stale cache entries', () => { const CACHE_TTL_MS = 5 * 60 * 1000 const staleTimestamp = Date.now() - CACHE_TTL_MS - 1000 // 1 second past expiry const isFresh = Date.now() - staleTimestamp < CACHE_TTL_MS expect(isFresh).toBe(false) }) }) describe('Batch verification', () => { it('should limit batch size', () => { const maxBatchSize = 20 const largeArray = Array.from({ length: 50 }, (_, i) => `site-${i}`) const limitedArray = largeArray.slice(0, maxBatchSize) expect(limitedArray).toHaveLength(20) }) it('should handle empty batch', () => { const targets: string[] = [] expect(targets.length === 0).toBe(true) }) }) }) describe('Sites Health API', () => { describe('Health check result structure', () => { it('should categorize status correctly', () => { const categorize = (responseTime: number, found: boolean): string => { if (!found) return 'offline' if (responseTime > 5000) return 'slow' return 'online' } expect(categorize(200, true)).toBe('online') expect(categorize(6000, true)).toBe('slow') expect(categorize(200, false)).toBe('offline') }) it('should calculate summary statistics', () => { const results = [ { status: 'online', responseTime: 200 }, { status: 'online', responseTime: 300 }, { status: 'offline', responseTime: 100 }, { status: 'error', responseTime: 50 }, { status: 'slow', responseTime: 6000 }, ] const online = results.filter(r => r.status === 'online').length const offline = results.filter(r => r.status === 'offline').length const errors = results.filter(r => r.status === 'error').length const slow = results.filter(r => r.status === 'slow').length const avgResponseTime = results.reduce((sum, r) => sum + r.responseTime, 0) / results.length expect(online).toBe(2) expect(offline).toBe(1) expect(errors).toBe(1) expect(slow).toBe(1) expect(avgResponseTime).toBe(1330) }) }) describe('Cooldown mechanism', () => { it('should enforce cooldown between health checks', () => { const COOLDOWN = 60 * 1000 // 1 minute let lastCheckTime = Date.now() - 30 * 1000 // 30 seconds ago const canCheck = Date.now() - lastCheckTime >= COOLDOWN expect(canCheck).toBe(false) lastCheckTime = Date.now() - 61 * 1000 // 61 seconds ago const canCheckNow = Date.now() - lastCheckTime >= COOLDOWN expect(canCheckNow).toBe(true) }) }) }) describe('Known Sites Data', () => { it('should have required fields for each site', () => { const requiredFields = ['id', 'name', 'url', 'category', 'description', 'llmsTxtType'] const site = { id: 'example', name: 'Example', url: 'https://example.com', category: 'developer-tools', description: 'Example documentation', llmsTxtType: 'full', verified: true, } requiredFields.forEach(field => { expect(site).toHaveProperty(field) }) }) it('should have valid category values', () => { const validCategories = ['ai', 'developer-tools', 'documentation', 'cloud', 'other'] const testCategories = ['ai', 'developer-tools', 'cloud'] testCategories.forEach(cat => { expect(validCategories.includes(cat)).toBe(true) }) }) it('should have valid llmsTxtType values', () => { const validTypes = ['full', 'standard'] expect(validTypes.includes('full')).toBe(true) expect(validTypes.includes('standard')).toBe(true) expect(validTypes.includes('invalid')).toBe(false) }) })