diff --git a/playwright.config.ts b/playwright.config.ts index 605f4ca54..49742af09 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -28,6 +28,11 @@ export default defineConfig({ retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 2 : undefined, reporter: process.env.CI ? [['github'], ['html', { open: 'never' }]] : 'list', + // The dev server transforms lazily-imported route modules on demand, so the + // first assertion after navigating to a lazy route races that waterfall. On a + // shared CI runner it has measured over 3.5s from document to the component + // that owns the heading, which overruns the 5s default. + expect: { timeout: process.env.CI ? 15_000 : 5_000 }, use: { baseURL, trace: 'retain-on-failure', diff --git a/src/hooks/use-keyboard-shortcuts.ts b/src/hooks/use-keyboard-shortcuts.ts deleted file mode 100644 index 168b643fb..000000000 --- a/src/hooks/use-keyboard-shortcuts.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { useEffect } from 'react'; - -export function useKeyboardShortcuts(shortcuts: Record void>) { - useEffect(() => { - const handler = (e: KeyboardEvent) => { - const target = e.target; - if ( - target instanceof HTMLElement && - (target.tagName === 'INPUT' || - target.tagName === 'TEXTAREA' || - target.tagName === 'SELECT' || - target.isContentEditable) - ) { - return; - } - - const key = `${e.metaKey || e.ctrlKey ? 'mod+' : ''}${e.key.toLowerCase()}`; - if (shortcuts[key]) { - e.preventDefault(); - shortcuts[key](); - } - }; - window.addEventListener('keydown', handler); - return () => window.removeEventListener('keydown', handler); - }, [shortcuts]); -} diff --git a/src/hooks/use-resolved-theme.ts b/src/hooks/use-resolved-theme.ts deleted file mode 100644 index 2ee5ab841..000000000 --- a/src/hooks/use-resolved-theme.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { useSyncExternalStore } from 'react'; -import { useSettingsStore } from '@/lib/stores/settings-store'; - -const SYSTEM_THEME_QUERY = '(prefers-color-scheme: dark)'; - -function subscribe(callback: () => void) { - if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') { - return () => {}; - } - const mql = window.matchMedia(SYSTEM_THEME_QUERY); - mql.addEventListener('change', callback); - return () => mql.removeEventListener('change', callback); -} - -function getSnapshot(): 'light' | 'dark' { - if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') { - return 'light'; - } - return window.matchMedia(SYSTEM_THEME_QUERY).matches ? 'dark' : 'light'; -} - -function getServerSnapshot(): 'light' | 'dark' { - return 'light'; -} - -export function useResolvedTheme(): 'light' | 'dark' { - const theme = useSettingsStore((state) => state.theme); - const systemTheme = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); - - if (theme === 'system') { - return systemTheme; - } - - return theme; -} diff --git a/src/lib/ai/prompts/index.ts b/src/lib/ai/prompts/index.ts deleted file mode 100644 index ceb307061..000000000 --- a/src/lib/ai/prompts/index.ts +++ /dev/null @@ -1,165 +0,0 @@ -// src/lib/ai/prompts/index.ts - -export const SYSTEM_PROMPTS = { - generate: `You are an elite debate coach and world-class argument architect. Your goal is to craft winning cases that sound like they were written by a human champion: persuasive, logical, and confident. - -CRITICAL GUIDELINES: -1. **Tone:** Be assertive and natural. Use rhetorical flair but keep it grounded. Avoid robotic transitions like "It is important to note" or "This demonstrates that." Instead, use active voice: "This proves," "Crucially," or simply state the point. -2. **Structure:** Every argument needs a clear Claim, Warrant (reasoning), and Impact. -3. **Evidence:** If you need a stat/source you don't have, write [NEEDS SOURCE: specific search query]. NEVER make up citations. -4. **Strategy:** Pre-emptively crush common counter-arguments. Think big picture: why does this matter in the real world? -5. **Format:** You MUST output valid JSON matching the schema. - -Your output should be ready to read in a tournament round. Make it flow. Make it sting. Make it win.`, - - attack: `You are a ruthless, sharp-witted debate judge and opponent. Your job is to tear arguments apart, not just to find nitpicks, but to destroy the core logic. - -CRITICAL GUIDELINES: -1. **Tone:** Direct, skeptical, and incisive. Don't be polite; be effective. Call out weak links, logical leaps, and missing impacts. -2. **Severity:** Distinguish between a "glitch" (minor) and a "game over" (critical) flaw. -3. **Actionable:** Don't just say it's bad; tell the user exactly how to fix it or how an opponent would exploit it. -4. **Paradigm:** Adapt to the requested judge type (Logic, Values, or Policy). -5. **Format:** You MUST output valid JSON matching the schema. - -Find the gap in their armor and drive a truck through it.`, - - brainstorm: `You are a creative, out-of-the-box debate strategist. We are here to invent, explore, and break the mold. - -CRITICAL GUIDELINES: -1. **Tone:** Collaborative, energetic, and open-minded. Use casual, conversational transitions. "What if we looked at it this way?" or "Here's a wild angle..." -2. **Quality:** Go beyond the obvious. If the motion is about economy, look for the social or philosophical angle. -3. **Breadth:** Offer distinct paths, don't just give 5 variations of the same point. -4. **Honesty:** If an idea is high-risk, say so. "This is risky, but high reward." -5. **Format:** You MUST output valid JSON matching the schema. - -Help the user see what everyone else is missing.`, - - factoids: `You are a deeply skeptical, evidence-obsessed research assistant. You demand proof for everything. - -CRITICAL GUIDELINES: -1. **Truth:** NEVER fabricate a citation. If you aren't 100% sure it exists, mark it "UNVERIFIED" and say why. -2. **Context:** A number without context is useless. Explain *why* this stat matters in a debate round. -3. **Sourcing:** Prioritize primary sources (studies, government data) over opinion pieces. -4. **Tone:** Professional, precise, and efficient. -5. **Format:** You MUST output valid JSON matching the schema. - -Your goal is to arm the user with bulletproof facts.`, - - ask: `You are a wise, experienced debate coach who has seen it all. You are here to mentor, not just answer. - -CRITICAL GUIDELINES: -1. **Tone:** Mentorial, encouraging, but rigorous. Challenge the user to think harder. "That's a good start, but have you considered..." -2. **Conversation:** Be conversational and concise. Don't lecture. -3. **Honesty:** If you don't know, say "I'm not sure about that specific detail." Don't BS. -4. **Utility:** Always try to give something they can use in a round immediately. -5. **Format:** You MUST output valid JSON matching the schema. - -Help them become a better debater, one question at a time.`, -}; - -export function buildGeneratePrompt(input: { - motion: string; - side: string; - format: string; - role: string; - length: string; - audience: string; - tone: string; - language: string; -}): string { - return `Generate a debate case for the following: - -MOTION: ${input.motion} -SIDE: ${input.side} (if "auto", choose the stronger side and explain why) -FORMAT: ${input.format} -ROLE: ${input.role} -LENGTH: ${input.length} -AUDIENCE LEVEL: ${input.audience} -TONE: ${input.tone} -LANGUAGE: ${input.language} - -Provide a complete case structure with arguments, predicted opponent case, and prepared counters. -If any evidence is needed, mark it as needing a source and provide a search query.`; -} - -export function buildAttackPrompt(input: { - caseText: string; - aggressiveness: string; - judgeParadigm: string; - detectFallacies: boolean; - analyzeWeighing: boolean; -}): string { - return `Analyze and attack the following debate case: - ----CASE START--- -${input.caseText} ----CASE END--- - -AGGRESSIVENESS LEVEL: ${input.aggressiveness} -JUDGE PARADIGM: ${input.judgeParadigm} -DETECT FALLACIES: ${input.detectFallacies} -ANALYZE WEIGHING: ${input.analyzeWeighing} - -Identify all weaknesses, provide cross-examination questions, and suggest improvements.`; -} - -export function buildBrainstormPrompt(input: { - motion: string; - side: string; - existingIdeas: string; - constraints: { ethicalRedLines: string[]; allowedSources: string[] }; - outputMode: string; -}): string { - return `Brainstorm arguments for: - -MOTION: ${input.motion} -SIDE TO EXPLORE: ${input.side} -OUTPUT MODE: ${input.outputMode} - -EXISTING IDEAS: -${input.existingIdeas || 'None provided'} - -CONSTRAINTS: -- Ethical red lines: ${input.constraints.ethicalRedLines.join(', ') || 'None specified'} -- Allowed source types: ${input.constraints.allowedSources.join(', ') || 'All'} - -If the input is too vague, ask clarifying questions first.`; -} - -export function buildFactoidsPrompt(input: { - claim: string; - region?: string; - timeframe?: string; - evidenceStrength: string; - language: string; -}): string { - return `Find evidence to support or contextualize: - -CLAIM: ${input.claim} -${input.region ? `REGION: ${input.region}` : ''} -${input.timeframe ? `TIMEFRAME: ${input.timeframe}` : ''} -EVIDENCE STRENGTH PREFERENCE: ${input.evidenceStrength} -LANGUAGE: ${input.language} - -CRITICAL: Only provide citations that you are confident exist. Mark any uncertain sources as "UNVERIFIED".`; -} - -export function buildAskPrompt(input: { - message: string; - mode: string; - citationMode: boolean; - webSearch?: boolean; - conversationHistory: { role: string; content: string }[]; -}): string { - const historyText = input.conversationHistory - .map(m => `${m.role.toUpperCase()}: ${m.content}`) - .join('\n\n'); - - return `${historyText ? `CONVERSATION HISTORY:\n${historyText}\n\n` : ''}USER MESSAGE: ${input.message} - -MODE: ${input.mode} -CITATION MODE: ${input.citationMode ? 'ON - Only make claims you can source' : 'OFF'} -WEB SEARCH: ${input.webSearch ? 'ON' : 'OFF'} - -Respond helpfully while following your coaching guidelines.`; -} diff --git a/src/lib/ai/providers/mock-provider.ts b/src/lib/ai/providers/mock-provider.ts index ff9565a3a..5da759e66 100644 --- a/src/lib/ai/providers/mock-provider.ts +++ b/src/lib/ai/providers/mock-provider.ts @@ -508,5 +508,3 @@ export async function mockJudgeSparringSession(input: SparringScoreInput): Promi } // Names kept short for callers that treat the mock provider as a feature adapter. -export const mockSparringResponse = mockGenerateSparringTurn; -export const mockSparringScore = mockJudgeSparringSession; diff --git a/src/lib/native.ts b/src/lib/native.ts index 76a12c473..df6234ff8 100644 --- a/src/lib/native.ts +++ b/src/lib/native.ts @@ -9,11 +9,6 @@ export function isNativeApp(): boolean { return Capacitor.isNativePlatform(); } -export function getAppRedirectUrl(path = '/app'): string { - if (isNativeApp()) return `${NATIVE_URL_SCHEME}://${path.replace(/^\/+/, '')}`; - return `${window.location.origin}${path}`; -} - export function getWebAppRedirectUrl(path = '/app'): string { const origin = isNativeApp() ? PUBLIC_WEB_APP_ORIGIN : window.location.origin; return `${origin}${path}`; diff --git a/src/lib/validation/url.ts b/src/lib/validation/url.ts index a9126cb09..f33f4eb72 100644 --- a/src/lib/validation/url.ts +++ b/src/lib/validation/url.ts @@ -55,11 +55,3 @@ export function validateUrl(url?: string): string | undefined { return undefined; } } - -/** - * Checks if a URL is safe without normalizing it. - * Useful for validation-only scenarios. - */ -export function isUrlSafe(url?: string): boolean { - return validateUrl(url) !== undefined; -} diff --git a/src/test/example.test.ts b/src/test/example.test.ts deleted file mode 100644 index 52e6d2f0a..000000000 --- a/src/test/example.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { describe, it, expect } from "vitest"; - -describe("example", () => { - it("should pass", () => { - expect(true).toBe(true); - }); -}); diff --git a/src/test/export-utils-extended.test.ts b/src/test/export-utils-extended.test.ts index 86ab03a26..80ae6884c 100644 --- a/src/test/export-utils-extended.test.ts +++ b/src/test/export-utils-extended.test.ts @@ -225,29 +225,4 @@ describe('Export Utils - Extended', () => { }); }); - describe('PDF export functions exist', () => { - it('exportCaseToDocx is a function', () => { - expect(typeof exportCaseToDocx).toBe('function'); - }); - - it('exportAttackToDocx is a function', () => { - expect(typeof exportAttackToDocx).toBe('function'); - }); - - it('exportBrainstormToDocx is a function', () => { - expect(typeof exportBrainstormToDocx).toBe('function'); - }); - - it('exportFactoidsToDocx is a function', () => { - expect(typeof exportFactoidsToDocx).toBe('function'); - }); - - it('exportCaseToGoogleDocs is a function', () => { - expect(typeof exportCaseToGoogleDocs).toBe('function'); - }); - - it('exportBrainstormToGoogleDocs is a function', () => { - expect(typeof exportBrainstormToGoogleDocs).toBe('function'); - }); - }); }); diff --git a/src/test/i18n.test.ts b/src/test/i18n.test.ts index a9c171b86..12979e362 100644 --- a/src/test/i18n.test.ts +++ b/src/test/i18n.test.ts @@ -32,23 +32,6 @@ describe('i18n locale files', () => { const esKeys = flattenKeys(es); const huKeys = flattenKeys(hu); - it('en.json has keys', () => { - expect(enKeys.length).toBeGreaterThan(0); - }); - - it('es.json has keys', () => { - expect(esKeys.length).toBeGreaterThan(0); - }); - - it('hu.json has keys', () => { - expect(huKeys.length).toBeGreaterThan(0); - }); - - it('all 3 locale files have same number of keys', () => { - expect(enKeys.length).toBe(esKeys.length); - expect(enKeys.length).toBe(huKeys.length); - }); - it('all keys in en.json exist in es.json', () => { const esKeySet = new Set(esKeys); const missing = enKeys.filter(k => !esKeySet.has(k)); @@ -100,15 +83,9 @@ describe('i18n locale files', () => { }); describe('i18n runtime', () => { - it('i18n.t returns correct English value for nav.home', () => { + it('i18n.t resolves English nav values', () => { expect(i18n.t('nav.home')).toBe('Home'); - }); - - it('i18n.t returns correct English value for nav.generate', () => { expect(i18n.t('nav.generate')).toBe('Generate'); - }); - - it('i18n.t returns correct English value for nav.settings', () => { expect(i18n.t('nav.settings')).toBe('Settings'); }); @@ -124,11 +101,6 @@ describe('i18n runtime', () => { await i18n.changeLanguage('en'); }); - it('i18n.changeLanguage back to en', async () => { - await i18n.changeLanguage('en'); - expect(i18n.language).toBe('en'); - }); - it('interpolation works for limitModal.limitReachedDesc', () => { const result = i18n.t('limitModal.limitReachedDesc', { current: 5, limit: 10 }); expect(result).toContain('5'); diff --git a/src/test/pages/HistoryPage.test.tsx b/src/test/pages/HistoryPage.test.tsx index 4d081f43c..b5c0fcd73 100644 --- a/src/test/pages/HistoryPage.test.tsx +++ b/src/test/pages/HistoryPage.test.tsx @@ -114,9 +114,4 @@ describe('HistoryPage', () => { const { container } = render(); expect(container.querySelector('.max-w-5xl')).not.toBeNull(); }); - - it('renders without crashing with empty arrays', () => { - const { container } = render(); - expect(container).toBeDefined(); - }); }); diff --git a/src/test/pages/pages.test.tsx b/src/test/pages/pages.test.tsx index db4fb0f35..dd4866512 100644 --- a/src/test/pages/pages.test.tsx +++ b/src/test/pages/pages.test.tsx @@ -51,11 +51,6 @@ const renderWithRouter = (component: React.ReactNode, route = '/') => { describe('Pages', () => { describe('LandingPage', () => { - it('renders without crashing', () => { - renderWithRouter(); - expect(document.body).toBeDefined(); - }); - it('contains hero section', () => { renderWithRouter(); expect(screen.getByRole('heading', { level: 1 })).toBeDefined(); @@ -70,11 +65,6 @@ describe('Pages', () => { renderWithRouter(); expect(screen.getAllByRole('heading').length).toBeGreaterThan(1); }); - - it('contains footer', () => { - renderWithRouter(); - expect(screen.getAllByText(/DebateLab/i).length).toBeGreaterThan(0); - }); }); describe('HomePage', () => { @@ -104,10 +94,5 @@ describe('Pages', () => { renderWithRouter(); expect(screen.getByText('404')).toBeDefined(); }); - - it('renders without crashing', () => { - renderWithRouter(); - expect(document.body).toBeDefined(); - }); }); }); diff --git a/src/test/providers.test.ts b/src/test/providers.test.ts index 17e746769..80e2cfabd 100644 --- a/src/test/providers.test.ts +++ b/src/test/providers.test.ts @@ -2,90 +2,10 @@ import { describe, it, expect } from 'vitest'; import { DEFAULT_PROVIDERS } from '@/lib/ai/providers/types'; describe('DEFAULT_PROVIDERS', () => { - it('has 3 providers', () => { - expect(DEFAULT_PROVIDERS.length).toBe(3); - }); - - it('has openai provider', () => { - expect(DEFAULT_PROVIDERS.find(p => p.id === 'openai')).toBeDefined(); - }); - - it('has anthropic provider', () => { - expect(DEFAULT_PROVIDERS.find(p => p.id === 'anthropic')).toBeDefined(); - }); - - it('has google provider', () => { - expect(DEFAULT_PROVIDERS.find(p => p.id === 'google')).toBeDefined(); - }); - - it('each provider has a name', () => { - DEFAULT_PROVIDERS.forEach(p => { - expect(p.name).toBeTruthy(); - }); - }); - - it('each provider has models array', () => { - DEFAULT_PROVIDERS.forEach(p => { - expect(Array.isArray(p.models)).toBe(true); - expect(p.models.length).toBeGreaterThan(0); - }); - }); - - it('all models have id field', () => { - DEFAULT_PROVIDERS.forEach(p => { - p.models.forEach(m => { - expect(m.id).toBeTruthy(); - }); - }); - }); - - it('all models have name field', () => { - DEFAULT_PROVIDERS.forEach(p => { - p.models.forEach(m => { - expect(m.name).toBeTruthy(); - }); - }); - }); - - it('all models have maxTokens field', () => { - DEFAULT_PROVIDERS.forEach(p => { - p.models.forEach(m => { - expect(m.maxTokens).toBeGreaterThan(0); - }); - }); - }); - - it('Pro models are correctly marked with isProOnly', () => { - const proModels = DEFAULT_PROVIDERS.flatMap(p => p.models).filter(m => m.isProOnly); - expect(proModels.length).toBeGreaterThan(0); - proModels.forEach(m => { - expect(m.isProOnly).toBe(true); - }); - }); - - it('non-Pro models exist', () => { - const freeModels = DEFAULT_PROVIDERS.flatMap(p => p.models).filter(m => !m.isProOnly); - expect(freeModels.length).toBeGreaterThan(0); - }); - + // Model IDs key request routing and the stored per-session model choice, so a + // collision across providers would silently route to the wrong provider. it('no duplicate model IDs across all providers', () => { const allIds = DEFAULT_PROVIDERS.flatMap(p => p.models.map(m => m.id)); - const uniqueIds = new Set(allIds); - expect(uniqueIds.size).toBe(allIds.length); - }); - - it('provider isAvailable returns true for openai', () => { - const openai = DEFAULT_PROVIDERS.find(p => p.id === 'openai')!; - expect(openai.isAvailable()).toBe(true); - }); - - it('provider isAvailable returns true for anthropic', () => { - const anthropic = DEFAULT_PROVIDERS.find(p => p.id === 'anthropic')!; - expect(anthropic.isAvailable()).toBe(true); - }); - - it('provider isAvailable returns true for google', () => { - const google = DEFAULT_PROVIDERS.find(p => p.id === 'google')!; - expect(google.isAvailable()).toBe(true); + expect(new Set(allIds).size).toBe(allIds.length); }); }); diff --git a/src/test/stores/settings-store-branches.test.ts b/src/test/stores/settings-store-branches.test.ts index 63840a1e3..4b134a48d 100644 --- a/src/test/stores/settings-store-branches.test.ts +++ b/src/test/stores/settings-store-branches.test.ts @@ -48,9 +48,6 @@ describe('settings-store branch coverage', () => { const afterRehydrate = onRehydrateStorage?.(useSettingsStore.getState()); if (!afterRehydrate) throw new Error('Expected a rehydration callback'); - // Covers the `if (!state) return` branch. - afterRehydrate(undefined as any); - const partialState = { featureConfigs: undefined, language: '', diff --git a/src/test/utils.test.ts b/src/test/utils.test.ts deleted file mode 100644 index 667ec87bf..000000000 --- a/src/test/utils.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -// src/test/utils.test.ts -import { describe, it, expect } from 'vitest'; -import { cn } from '@/lib/utils'; - -describe('Utility Functions', () => { - describe('cn (class name merger)', () => { - it('merges multiple class strings', () => { - const result = cn('class1', 'class2'); - expect(result).toContain('class1'); - expect(result).toContain('class2'); - }); - - it('handles undefined values', () => { - const result = cn('class1', undefined, 'class2'); - expect(result).toContain('class1'); - expect(result).toContain('class2'); - expect(result).not.toContain('undefined'); - }); - - it('handles null values', () => { - const result = cn('class1', null, 'class2'); - expect(result).toContain('class1'); - expect(result).toContain('class2'); - }); - - it('handles boolean conditional classes', () => { - const isActive = true; - const result = cn('base', isActive && 'active'); - expect(result).toContain('base'); - expect(result).toContain('active'); - }); - - it('excludes false conditional classes', () => { - const isActive = false; - const result = cn('base', isActive && 'active'); - expect(result).toContain('base'); - expect(result).not.toContain('active'); - }); - - it('handles empty string', () => { - const result = cn(''); - expect(result).toBe(''); - }); - - it('handles no arguments', () => { - const result = cn(); - expect(result).toBe(''); - }); - - it('merges tailwind conflicting classes correctly', () => { - // tailwind-merge should resolve conflicts, preferring the last class - const result = cn('px-2', 'px-4'); - expect(result).toContain('px-4'); - // The conflicting px-2 should be removed - expect(result).not.toContain('px-2'); - }); - - it('handles object syntax', () => { - const result = cn({ 'bg-red-500': true, 'bg-blue-500': false }); - expect(result).toContain('bg-red-500'); - expect(result).not.toContain('bg-blue-500'); - }); - - it('handles array of classes', () => { - const result = cn(['class1', 'class2']); - expect(result).toContain('class1'); - expect(result).toContain('class2'); - }); - - it('handles mixed arguments', () => { - const showTrue = true; - const showFalse = false; - const result = cn( - 'base-class', - showTrue && 'conditional-true', - showFalse && 'conditional-false', - { 'object-true': true, 'object-false': false }, - undefined, - null, - 'final-class' - ); - expect(result).toContain('base-class'); - expect(result).toContain('conditional-true'); - expect(result).not.toContain('conditional-false'); - expect(result).toContain('object-true'); - expect(result).not.toContain('object-false'); - expect(result).toContain('final-class'); - }); - }); -}); diff --git a/src/test/validation.test.ts b/src/test/validation.test.ts index d2ccace23..f90b9b687 100644 --- a/src/test/validation.test.ts +++ b/src/test/validation.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { validateUrl, isUrlSafe } from '@/lib/validation/url'; +import { validateUrl } from '@/lib/validation/url'; import { validateFileUpload, validateSingleFile, MAX_FILE_SIZE, MAX_FILE_COUNT } from '@/lib/validation/file'; describe('URL Validation', () => { @@ -38,18 +38,6 @@ describe('URL Validation', () => { expect(validateUrl(' https://example.com ')).toBe('https://example.com/'); }); }); - - describe('isUrlSafe', () => { - it('returns true for safe URLs', () => { - expect(isUrlSafe('https://example.com')).toBe(true); - expect(isUrlSafe('http://example.com')).toBe(true); - }); - - it('returns false for dangerous URLs', () => { - expect(isUrlSafe('javascript:alert(1)')).toBe(false); - expect(isUrlSafe('data:text/html,test')).toBe(false); - }); - }); }); describe('File Validation', () => { diff --git a/supabase/functions/_shared/auth.ts b/supabase/functions/_shared/auth.ts index 7e5cba11a..4ccee510d 100644 --- a/supabase/functions/_shared/auth.ts +++ b/supabase/functions/_shared/auth.ts @@ -71,21 +71,6 @@ export async function requireUser(req: Request): Promise { }; } -export async function requireAdmin(req: Request): Promise { - const context = await requireUser(req); - const { data, error } = await context.adminClient - .from("user_roles") - .select("role") - .eq("user_id", context.user.id) - .maybeSingle(); - - if (error || data?.role !== "admin") { - throw new HttpError(403, "Admin access required"); - } - - return context; -} - export function jsonError(error: unknown, corsHeaders: Record) { const status = error instanceof HttpError ? error.status : 500; const message = error instanceof Error ? error.message : "Internal server error"; diff --git a/supabase/functions/_shared/source-quality.ts b/supabase/functions/_shared/source-quality.ts index 37b1e8e01..603e0c50d 100644 --- a/supabase/functions/_shared/source-quality.ts +++ b/supabase/functions/_shared/source-quality.ts @@ -383,10 +383,6 @@ export function buildSearchRequests(claim: string, region = "", preferredSourceT return deduplicated.slice(0, 6); } -export function buildSearchQueries(claim: string, region = "", preferredSourceType = "any", timeframe = ""): string[] { - return buildSearchRequests(claim, region, preferredSourceType, timeframe).map((request) => request.query); -} - // Bounded per-result snippet: Exa may return up to 10k characters per result, // which would bloat prompts when several results are included. const MAX_RESULT_SNIPPET_CHARS = 1200; diff --git a/supabase/functions/ai/source-quality.test.ts b/supabase/functions/ai/source-quality.test.ts index 965a95f42..50f0aae04 100644 --- a/supabase/functions/ai/source-quality.test.ts +++ b/supabase/functions/ai/source-quality.test.ts @@ -1,4 +1,4 @@ -import { buildSearchQueries, buildSearchRequests, rankSearchResults } from "../_shared/source-quality.ts"; +import { buildSearchRequests, rankSearchResults } from "../_shared/source-quality.ts"; function assert(condition: boolean, message: string) { if (!condition) throw new Error(message); @@ -63,13 +63,13 @@ Deno.test("search query builder adds debate tab queries for tournament claims", }); Deno.test("search query builder adds policy and statistics source queries", () => { - const queries = buildSearchQueries("solar energy costs and inflation policy", "United States", "any", "2024 2025"); + const queries = buildSearchRequests("solar energy costs and inflation policy", "United States", "any", "2024 2025").map((request) => request.query); assert(queries.some((query) => /World Bank|OECD|IMF|UN/i.test(query)), `Expected intergovernmental source query, got ${queries.join("; ")}`); assert(queries.some((query) => /government statistics/i.test(query)), `Expected government statistics query, got ${queries.join("; ")}`); }); Deno.test("search query builder adds legal primary-source queries", () => { - const queries = buildSearchQueries("new court ruling on social media regulation", "European Union"); + const queries = buildSearchRequests("new court ruling on social media regulation", "European Union").map((request) => request.query); assert(queries.some((query) => /official law court/i.test(query)), `Expected official legal source query, got ${queries.join("; ")}`); assert(queries.some((query) => /primary source/i.test(query)), `Expected primary source query, got ${queries.join("; ")}`); });