diff --git a/src/services/aiService.server.ts b/src/services/aiService.server.ts index c4d1548c..af28d8d6 100644 --- a/src/services/aiService.server.ts +++ b/src/services/aiService.server.ts @@ -91,11 +91,55 @@ export class AIService { private fs: IFileSystem; private rateLimiter: (fn: () => Promise) => Promise; private logger: Logger; - // The fallback list is ordered by preference (speed/cost vs. power). - // We try the fastest models first, then the more powerful 'pro' model as a high-quality fallback, - // and finally the 'lite' model as a last resort. - private readonly models = [ 'gemini-3-flash-preview','gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-2.5-flash-lite','gemini-2.0-flash-001','gemini-2.0-flash','gemini-2.0-flash-exp','gemini-2.0-flash-lite-001','gemini-2.0-flash-lite', 'gemma-3-27b-it', 'gemma-3-12b-it']; - private readonly models_lite = ["gemma-3-4b-it", "gemma-3-2b-it", "gemma-3-1b-it"]; + +// OPTIMIZED: Flyer Image Processing (Vision + Long Output) + // PRIORITIES: + // 1. Output Limit: Must be 65k+ (Gemini 2.5/3.0) to avoid cutting off data. + // 2. Intelligence: 'Pro' models handle messy layouts better. + // 3. Quota Management: 'Preview' and 'Exp' models are added as fallbacks to tap into separate rate limits. + private readonly models = [ + // --- TIER A: The Happy Path (Fast & Stable) --- + 'gemini-2.5-flash', // Primary workhorse. 65k output. + 'gemini-2.5-flash-lite', // Cost-saver. 65k output. + + // --- TIER B: The Heavy Lifters (Complex Layouts) --- + 'gemini-2.5-pro', // High IQ for messy flyers. 65k output. + + // --- TIER C: Separate Quota Buckets (Previews) --- + 'gemini-3-flash-preview', // Newer/Faster. Separate 'Preview' quota. 65k output. + 'gemini-3-pro-preview', // High IQ. Separate 'Preview' quota. 65k output. + + // --- TIER D: Experimental Buckets (High Capacity) --- + 'gemini-exp-1206', // Excellent reasoning. Separate 'Experimental' quota. 65k output. + + // --- TIER E: Last Resorts (Lower Capacity/Local) --- + 'gemma-3-27b-it', // Open model fallback. + 'gemini-2.0-flash-exp' // Exp fallback. WARNING: 8k output limit. Good for small flyers only. + ]; + + // OPTIMIZED: Simple Text Tasks (Recipes, Shopping Lists, Summaries) + // PRIORITIES: + // 1. Cost/Speed: These tasks are simple. + // 2. Output Limit: The 8k limit of Gemini 2.0 is perfectly fine here. + private readonly models_lite = [ + // --- Best Value (Smart + Cheap) --- + "gemini-2.5-flash-lite", // Current generation efficiency king. + + // --- The "Recycled" Gemini 2.0 Models (Perfect for Text) --- + "gemini-2.0-flash-lite-001", // Extremely cheap, very capable for text. + "gemini-2.0-flash-001", // Smarter than Lite, good for complex recipes. + + // --- Open Models (Good for simple categorization) --- + "gemma-3-12b-it", // Solid reasoning for an open model. + "gemma-3-4b-it", // Very fast. + + // --- Quota Fallbacks (Experimental/Preview) --- + "gemini-2.0-flash-exp", // Use this separate quota bucket if others are exhausted. + + // --- Edge/Nano Models (Simple string manipulation only) --- + "gemma-3n-e4b-it", // Corrected name from JSON + "gemma-3n-e2b-it" // Corrected name from JSON + ]; constructor(logger: Logger, aiClient?: IAiClient, fs?: IFileSystem) { this.logger = logger; diff --git a/src/tests/e2e/auth.e2e.test.ts b/src/tests/e2e/auth.e2e.test.ts index 0606d9e5..84618a09 100644 --- a/src/tests/e2e/auth.e2e.test.ts +++ b/src/tests/e2e/auth.e2e.test.ts @@ -1,5 +1,5 @@ // src/tests/e2e/auth.e2e.test.ts -import { describe, it, expect, afterAll, beforeAll } from 'vitest'; +import { describe, it, expect, afterAll, beforeAll } from 'vitest'; import * as apiClient from '../../services/apiClient'; import { cleanupDb } from '../utils/cleanup'; import { createAndLoginUser, TEST_PASSWORD } from '../utils/testHelpers'; @@ -13,15 +13,19 @@ describe('Authentication E2E Flow', () => { let testUser: UserProfile; const createdUserIds: string[] = []; - beforeAll(async () => { + beforeAll(async () => { // Create a user that can be used for login-related tests in this suite. - const { user } = await createAndLoginUser({ - email: `e2e-login-user-${Date.now()}@example.com`, - fullName: 'E2E Login User', - // E2E tests use apiClient which doesn't need the `request` object. - }); - testUser = user; - createdUserIds.push(user.user.user_id); + try { + const { user } = await createAndLoginUser({ + email: `e2e-login-user-${Date.now()}@example.com`, + fullName: 'E2E Login User', + }); + testUser = user; + createdUserIds.push(user.user.user_id); + } catch (error) { + console.error('[FATAL] Setup failed. DB might be down.', error); + throw error; + } }); afterAll(async () => { @@ -70,7 +74,7 @@ describe('Authentication E2E Flow', () => { const firstResponse = await apiClient.registerUser(email, TEST_PASSWORD, 'Duplicate User'); const firstData = await firstResponse.json(); expect(firstResponse.status).toBe(201); - createdUserIds.push(firstData.userprofile.user.user_id); // Add for cleanup + createdUserIds.push(firstData.userprofile.user.user_id); // Act 2: Attempt to register the same user again const secondResponse = await apiClient.registerUser(email, TEST_PASSWORD, 'Duplicate User'); @@ -186,17 +190,23 @@ describe('Authentication E2E Flow', () => { } await new Promise((resolve) => setTimeout(resolve, 1000)); } - expect(loginSuccess, 'User should be able to log in after registration before password reset is attempted.').toBe(true); + expect(loginSuccess, 'User should be able to log in after registration. DB might be lagging.').toBe(true); - // Act 1: Request a password reset. - // The test environment returns the token directly in the response for E2E testing. + // Act 1: Request a password reset const forgotResponse = await apiClient.requestPasswordReset(email); const forgotData = await forgotResponse.json(); const resetToken = forgotData.token; + // --- DEBUG SECTION FOR FAILURE --- + if (!resetToken) { + console.error(' [DEBUG FAILURE] Token missing in response:', JSON.stringify(forgotData, null, 2)); + console.error(' [DEBUG FAILURE] This usually means the backend hit a DB error or is not in NODE_ENV=test mode.'); + } + // --------------------------------- + // Assert 1: Check that we received a token. expect(forgotResponse.status).toBe(200); - expect(resetToken).toBeDefined(); + expect(resetToken, 'Backend returned 200 but no token. Check backend logs for "Connection terminated" errors.').toBeDefined(); expect(resetToken).toBeTypeOf('string'); // Act 2: Use the token to set a new password. @@ -208,7 +218,7 @@ describe('Authentication E2E Flow', () => { expect(resetResponse.status).toBe(200); expect(resetData.message).toBe('Password has been reset successfully.'); - // Act 3 & Assert 3 (Verification): Log in with the NEW password to confirm the change. + // Act 3: Log in with the NEW password const loginResponse = await apiClient.loginUser(email, newPassword, false); const loginData = await loginResponse.json();