fixing routes + routes db mock
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 3m48s

This commit is contained in:
2025-12-01 17:10:04 -08:00
parent 4951aaec14
commit 4100dceb6f
4 changed files with 121 additions and 90 deletions

View File

@@ -1,17 +1,37 @@
// src/services/aiApiClient.test.ts
import { describe, it, expect, vi, beforeEach, type Mocked } from 'vitest';
import * as apiClient from './apiClient';
import * as aiApiClient from './aiApiClient';
import { MasterGroceryItem, FlyerItem } from '../types';
// Mock the underlying apiClient to isolate our tests to the aiApiClient logic.
vi.mock('./apiClient');
const mockedApiClient = apiClient as Mocked<typeof apiClient>;
// 1. Hoist the mock function so it is created before modules are evaluated
// and can be referenced inside the vi.mock factory.
const { mockApiFetchWithAuth } = vi.hoisted(() => ({
mockApiFetchWithAuth: vi.fn(),
}));
// 2. Mock the dependency './apiClient' to use our hoisted spy.
vi.mock('./apiClient', () => ({
apiFetchWithAuth: mockApiFetchWithAuth,
}));
// Mock logger as it is used by aiApiClient
vi.mock('./logger', () => ({
logger: {
debug: vi.fn(),
info: vi.fn(),
error: vi.fn(),
},
}));
describe('AI API Client', () => {
beforeEach(() => {
// Clear all mock history before each test
// We will load the module under test dynamically for each test
let aiApiClient: typeof import('./aiApiClient');
beforeEach(async () => {
vi.clearAllMocks();
vi.resetModules(); // Force a fresh module load
// 3. Import the module under test AFTER mocks are set up.
// This ensures aiApiClient binds to the mocked apiFetchWithAuth.
aiApiClient = await import('./aiApiClient');
});
describe('isImageAFlyer', () => {
@@ -19,8 +39,8 @@ describe('AI API Client', () => {
const file = new File([''], 'flyer.jpg', { type: 'image/jpeg' });
await aiApiClient.isImageAFlyer(file, 'test-token');
expect(mockedApiClient.apiFetchWithAuth).toHaveBeenCalledTimes(1);
const [url, options, token] = mockedApiClient.apiFetchWithAuth.mock.calls[0];
expect(mockApiFetchWithAuth).toHaveBeenCalledTimes(1);
const [url, options, token] = mockApiFetchWithAuth.mock.calls[0];
expect(url).toBe('/ai/check-flyer');
expect(options.method).toBe('POST');
@@ -35,8 +55,8 @@ describe('AI API Client', () => {
const file = new File([''], 'flyer.jpg', { type: 'image/jpeg' });
await aiApiClient.extractAddressFromImage(file, 'test-token');
expect(mockedApiClient.apiFetchWithAuth).toHaveBeenCalledTimes(1);
const [url, options, token] = mockedApiClient.apiFetchWithAuth.mock.calls[0];
expect(mockApiFetchWithAuth).toHaveBeenCalledTimes(1);
const [url, options, token] = mockApiFetchWithAuth.mock.calls[0];
expect(url).toBe('/ai/extract-address');
expect(options.method).toBe('POST');
@@ -49,12 +69,12 @@ describe('AI API Client', () => {
describe('extractCoreDataFromImage', () => {
it('should construct FormData and call apiFetchWithAuth correctly', async () => {
const files = [new File([''], 'flyer1.jpg', { type: 'image/jpeg' })];
const masterItems: MasterGroceryItem[] = [{ master_grocery_item_id: 1, name: 'Milk', created_at: '' }];
const masterItems: any[] = [{ master_grocery_item_id: 1, name: 'Milk', created_at: '' }];
await aiApiClient.extractCoreDataFromImage(files, masterItems);
expect(mockedApiClient.apiFetchWithAuth).toHaveBeenCalledTimes(1);
const [url, options] = mockedApiClient.apiFetchWithAuth.mock.calls[0];
expect(mockApiFetchWithAuth).toHaveBeenCalledTimes(1);
const [url, options] = mockApiFetchWithAuth.mock.calls[0];
expect(url).toBe('/ai/process-flyer');
expect(options.method).toBe('POST');
@@ -72,8 +92,8 @@ describe('AI API Client', () => {
const files = [new File([''], 'logo.jpg', { type: 'image/jpeg' })];
await aiApiClient.extractLogoFromImage(files, 'test-token');
expect(mockedApiClient.apiFetchWithAuth).toHaveBeenCalledTimes(1);
const [url, options, token] = mockedApiClient.apiFetchWithAuth.mock.calls[0];
expect(mockApiFetchWithAuth).toHaveBeenCalledTimes(1);
const [url, options, token] = mockApiFetchWithAuth.mock.calls[0];
expect(url).toBe('/ai/extract-logo');
expect(options.method).toBe('POST');
@@ -85,11 +105,11 @@ describe('AI API Client', () => {
describe('getDeepDiveAnalysis', () => {
it('should call apiFetchWithAuth with the items in the body', async () => {
const items: FlyerItem[] = [];
const items: any[] = [];
await aiApiClient.getDeepDiveAnalysis(items, 'test-token');
expect(mockedApiClient.apiFetchWithAuth).toHaveBeenCalledTimes(1);
expect(mockedApiClient.apiFetchWithAuth).toHaveBeenCalledWith(
expect(mockApiFetchWithAuth).toHaveBeenCalledTimes(1);
expect(mockApiFetchWithAuth).toHaveBeenCalledWith(
'/ai/deep-dive',
{
method: 'POST',
@@ -103,11 +123,11 @@ describe('AI API Client', () => {
describe('searchWeb', () => {
it('should call apiFetchWithAuth with the items in the body', async () => {
const items: FlyerItem[] = [];
const items: any[] = [];
await aiApiClient.searchWeb(items, 'test-token');
expect(mockedApiClient.apiFetchWithAuth).toHaveBeenCalledTimes(1);
expect(mockedApiClient.apiFetchWithAuth).toHaveBeenCalledWith(
expect(mockApiFetchWithAuth).toHaveBeenCalledTimes(1);
expect(mockApiFetchWithAuth).toHaveBeenCalledWith(
'/ai/search-web',
{
method: 'POST',
@@ -124,8 +144,8 @@ describe('AI API Client', () => {
const prompt = 'A delicious meal';
await aiApiClient.generateImageFromText(prompt, 'test-token');
expect(mockedApiClient.apiFetchWithAuth).toHaveBeenCalledTimes(1);
expect(mockedApiClient.apiFetchWithAuth).toHaveBeenCalledWith(
expect(mockApiFetchWithAuth).toHaveBeenCalledTimes(1);
expect(mockApiFetchWithAuth).toHaveBeenCalledWith(
'/ai/generate-image',
{
method: 'POST',
@@ -142,8 +162,8 @@ describe('AI API Client', () => {
const text = 'Hello world';
await aiApiClient.generateSpeechFromText(text, 'test-token');
expect(mockedApiClient.apiFetchWithAuth).toHaveBeenCalledTimes(1);
expect(mockedApiClient.apiFetchWithAuth).toHaveBeenCalledWith(
expect(mockApiFetchWithAuth).toHaveBeenCalledTimes(1);
expect(mockApiFetchWithAuth).toHaveBeenCalledWith(
'/ai/generate-speech',
{
method: 'POST',
@@ -157,7 +177,8 @@ describe('AI API Client', () => {
describe('startVoiceSession', () => {
it('should throw an error as it is not implemented', () => {
expect(() => aiApiClient.startVoiceSession({ onmessage: vi.fn() })).toThrow(
// Ensure the real implementation is called, which should throw
expect(() => aiApiClient.startVoiceSession({ onmessage: vi.fn() } as any)).toThrow(
'Voice session feature is not fully implemented and requires a backend WebSocket proxy.'
);
});