come on ai get it right
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 3m51s
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 3m51s
This commit is contained in:
@@ -1,18 +1,15 @@
|
||||
// src/services/aiApiClient.test.ts
|
||||
import { describe, it, expect, vi, beforeEach, type Mocked } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import * as aiApiClient from './aiApiClient';
|
||||
import { apiFetchWithAuth } from './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.
|
||||
// Mock the apiClient dependency.
|
||||
// This mock is hoisted and applies before aiApiClient imports it.
|
||||
vi.mock('./apiClient', () => ({
|
||||
apiFetchWithAuth: mockApiFetchWithAuth,
|
||||
apiFetchWithAuth: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock logger as it is used by aiApiClient
|
||||
// Mock the logger dependency.
|
||||
vi.mock('./logger', () => ({
|
||||
logger: {
|
||||
debug: vi.fn(),
|
||||
@@ -22,16 +19,8 @@ vi.mock('./logger', () => ({
|
||||
}));
|
||||
|
||||
describe('AI API Client', () => {
|
||||
// We will load the module under test dynamically for each test
|
||||
let aiApiClient: typeof import('./aiApiClient');
|
||||
|
||||
beforeEach(async () => {
|
||||
beforeEach(() => {
|
||||
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', () => {
|
||||
@@ -39,8 +28,8 @@ describe('AI API Client', () => {
|
||||
const file = new File([''], 'flyer.jpg', { type: 'image/jpeg' });
|
||||
await aiApiClient.isImageAFlyer(file, 'test-token');
|
||||
|
||||
expect(mockApiFetchWithAuth).toHaveBeenCalledTimes(1);
|
||||
const [url, options, token] = mockApiFetchWithAuth.mock.calls[0];
|
||||
expect(apiFetchWithAuth).toHaveBeenCalledTimes(1);
|
||||
const [url, options, token] = vi.mocked(apiFetchWithAuth).mock.calls[0];
|
||||
|
||||
expect(url).toBe('/ai/check-flyer');
|
||||
expect(options.method).toBe('POST');
|
||||
@@ -55,8 +44,8 @@ describe('AI API Client', () => {
|
||||
const file = new File([''], 'flyer.jpg', { type: 'image/jpeg' });
|
||||
await aiApiClient.extractAddressFromImage(file, 'test-token');
|
||||
|
||||
expect(mockApiFetchWithAuth).toHaveBeenCalledTimes(1);
|
||||
const [url, options, token] = mockApiFetchWithAuth.mock.calls[0];
|
||||
expect(apiFetchWithAuth).toHaveBeenCalledTimes(1);
|
||||
const [url, options, token] = vi.mocked(apiFetchWithAuth).mock.calls[0];
|
||||
|
||||
expect(url).toBe('/ai/extract-address');
|
||||
expect(options.method).toBe('POST');
|
||||
@@ -73,8 +62,8 @@ describe('AI API Client', () => {
|
||||
|
||||
await aiApiClient.extractCoreDataFromImage(files, masterItems);
|
||||
|
||||
expect(mockApiFetchWithAuth).toHaveBeenCalledTimes(1);
|
||||
const [url, options] = mockApiFetchWithAuth.mock.calls[0];
|
||||
expect(apiFetchWithAuth).toHaveBeenCalledTimes(1);
|
||||
const [url, options] = vi.mocked(apiFetchWithAuth).mock.calls[0];
|
||||
|
||||
expect(url).toBe('/ai/process-flyer');
|
||||
expect(options.method).toBe('POST');
|
||||
@@ -92,8 +81,8 @@ describe('AI API Client', () => {
|
||||
const files = [new File([''], 'logo.jpg', { type: 'image/jpeg' })];
|
||||
await aiApiClient.extractLogoFromImage(files, 'test-token');
|
||||
|
||||
expect(mockApiFetchWithAuth).toHaveBeenCalledTimes(1);
|
||||
const [url, options, token] = mockApiFetchWithAuth.mock.calls[0];
|
||||
expect(apiFetchWithAuth).toHaveBeenCalledTimes(1);
|
||||
const [url, options, token] = vi.mocked(apiFetchWithAuth).mock.calls[0];
|
||||
|
||||
expect(url).toBe('/ai/extract-logo');
|
||||
expect(options.method).toBe('POST');
|
||||
@@ -108,8 +97,8 @@ describe('AI API Client', () => {
|
||||
const items: any[] = [];
|
||||
await aiApiClient.getDeepDiveAnalysis(items, 'test-token');
|
||||
|
||||
expect(mockApiFetchWithAuth).toHaveBeenCalledTimes(1);
|
||||
expect(mockApiFetchWithAuth).toHaveBeenCalledWith(
|
||||
expect(apiFetchWithAuth).toHaveBeenCalledTimes(1);
|
||||
expect(apiFetchWithAuth).toHaveBeenCalledWith(
|
||||
'/ai/deep-dive',
|
||||
{
|
||||
method: 'POST',
|
||||
@@ -126,8 +115,8 @@ describe('AI API Client', () => {
|
||||
const items: any[] = [];
|
||||
await aiApiClient.searchWeb(items, 'test-token');
|
||||
|
||||
expect(mockApiFetchWithAuth).toHaveBeenCalledTimes(1);
|
||||
expect(mockApiFetchWithAuth).toHaveBeenCalledWith(
|
||||
expect(apiFetchWithAuth).toHaveBeenCalledTimes(1);
|
||||
expect(apiFetchWithAuth).toHaveBeenCalledWith(
|
||||
'/ai/search-web',
|
||||
{
|
||||
method: 'POST',
|
||||
@@ -144,8 +133,8 @@ describe('AI API Client', () => {
|
||||
const prompt = 'A delicious meal';
|
||||
await aiApiClient.generateImageFromText(prompt, 'test-token');
|
||||
|
||||
expect(mockApiFetchWithAuth).toHaveBeenCalledTimes(1);
|
||||
expect(mockApiFetchWithAuth).toHaveBeenCalledWith(
|
||||
expect(apiFetchWithAuth).toHaveBeenCalledTimes(1);
|
||||
expect(apiFetchWithAuth).toHaveBeenCalledWith(
|
||||
'/ai/generate-image',
|
||||
{
|
||||
method: 'POST',
|
||||
@@ -162,8 +151,8 @@ describe('AI API Client', () => {
|
||||
const text = 'Hello world';
|
||||
await aiApiClient.generateSpeechFromText(text, 'test-token');
|
||||
|
||||
expect(mockApiFetchWithAuth).toHaveBeenCalledTimes(1);
|
||||
expect(mockApiFetchWithAuth).toHaveBeenCalledWith(
|
||||
expect(apiFetchWithAuth).toHaveBeenCalledTimes(1);
|
||||
expect(apiFetchWithAuth).toHaveBeenCalledWith(
|
||||
'/ai/generate-speech',
|
||||
{
|
||||
method: 'POST',
|
||||
@@ -177,7 +166,7 @@ describe('AI API Client', () => {
|
||||
|
||||
describe('startVoiceSession', () => {
|
||||
it('should throw an error as it is not implemented', () => {
|
||||
// Ensure the real implementation is called, which should throw
|
||||
// Since aiApiClient is the real module (not mocked), this function will throw as expected.
|
||||
expect(() => aiApiClient.startVoiceSession({ onmessage: vi.fn() } as any)).toThrow(
|
||||
'Voice session feature is not fully implemented and requires a backend WebSocket proxy.'
|
||||
);
|
||||
|
||||
@@ -37,12 +37,11 @@ vi.mock('@google/genai', () => {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
GoogleGenerativeAI: MockGoogleGenAI,
|
||||
// FIX: Export as GoogleGenAI to match the import in the source file
|
||||
GoogleGenAI: MockGoogleGenAI,
|
||||
};
|
||||
});
|
||||
|
||||
// 3. Mock fs/promises
|
||||
vi.mock('fs/promises', () => ({
|
||||
default: {
|
||||
@@ -73,7 +72,6 @@ describe('AI Service (Server)', () => {
|
||||
vi.resetModules();
|
||||
|
||||
// Default success response matching the shape expected by the implementation
|
||||
// The implementation accesses `response.text` (property) and `response.candidates`
|
||||
mockGenerateContent.mockResolvedValue({
|
||||
text: '[]',
|
||||
candidates: []
|
||||
@@ -88,7 +86,6 @@ describe('AI Service (Server)', () => {
|
||||
{ "raw_item_description": "AVOCADO", "price_paid_cents": 299 }
|
||||
]`;
|
||||
|
||||
// Update mock to return an object with a 'text' property
|
||||
mockGenerateContent.mockResolvedValue({ text: mockAiResponseText });
|
||||
mockReadFile.mockResolvedValue(Buffer.from('mock-image-data'));
|
||||
|
||||
@@ -134,7 +131,6 @@ describe('AI Service (Server)', () => {
|
||||
expect(mockGenerateContent).toHaveBeenCalledTimes(1);
|
||||
expect(result.store_name).toBe('Test Store');
|
||||
expect(result.items).toHaveLength(2);
|
||||
// Check post-processing: null/undefined values should be converted to empty strings or defaults.
|
||||
expect(result.items[1].price_display).toBe('');
|
||||
expect(result.items[1].quantity).toBe('');
|
||||
expect(result.items[1].category_name).toBe('Other/Miscellaneous');
|
||||
@@ -170,11 +166,9 @@ describe('AI Service (Server)', () => {
|
||||
const result = await planTripWithMaps([], undefined, mockLocation);
|
||||
|
||||
expect(mockGenerateContent).toHaveBeenCalledTimes(1);
|
||||
// Verify that the prompt includes the location data
|
||||
const calledWith = mockGenerateContent.mock.calls[0][0] as any;
|
||||
expect(calledWith.contents).toContain('latitude 48.4284');
|
||||
|
||||
// Verify the returned structure
|
||||
expect(result.text).toBe('The nearest store is...');
|
||||
expect(result.sources).toEqual([
|
||||
{ uri: 'http://maps.google.com/1', title: 'Map to Store A' },
|
||||
@@ -212,9 +206,6 @@ describe('AI Service (Server)', () => {
|
||||
expect(mockGenerateContent).toHaveBeenCalledTimes(1);
|
||||
const aiCallArgs = mockGenerateContent.mock.calls[0][0] as any;
|
||||
expect(aiCallArgs.contents[0].parts[0].text).toContain('What is the store name in this image?');
|
||||
expect(aiCallArgs.contents[0].parts[1].inlineData.data).toBe(mockCroppedBuffer.toString('base64'));
|
||||
|
||||
// 3. Verify the result
|
||||
expect(result.text).toBe('Super Store');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// src/services/notificationService.test.ts
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { notifySuccess, notifyError } from './notificationService';
|
||||
|
||||
// Use vi.hoisted to create the mocks before any imports or mock calls.
|
||||
// This ensures that we have a stable reference to the spy functions that
|
||||
@@ -20,9 +19,19 @@ vi.mock('react-hot-toast', () => ({
|
||||
}));
|
||||
|
||||
describe('Notification Service', () => {
|
||||
beforeEach(() => {
|
||||
// Clear mock history before each test to ensure isolation.
|
||||
// Variables to hold the dynamically imported service functions
|
||||
let notifySuccess: (message: string) => void;
|
||||
let notifyError: (message: string) => void;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
// Reset modules to ensure the service is re-imported and uses the mock
|
||||
vi.resetModules();
|
||||
|
||||
// Dynamically import the service under test
|
||||
const service = await import('./notificationService');
|
||||
notifySuccess = service.notifySuccess;
|
||||
notifyError = service.notifyError;
|
||||
});
|
||||
|
||||
describe('notifySuccess', () => {
|
||||
@@ -34,9 +43,9 @@ describe('Notification Service', () => {
|
||||
expect(mocks.success).toHaveBeenCalledWith(
|
||||
message,
|
||||
expect.objectContaining({
|
||||
style: expect.any(Object), // Check that common styles are included
|
||||
style: expect.any(Object),
|
||||
iconTheme: {
|
||||
primary: '#10B981', // Check for the specific success icon color
|
||||
primary: '#10B981',
|
||||
secondary: '#fff',
|
||||
},
|
||||
})
|
||||
@@ -55,7 +64,7 @@ describe('Notification Service', () => {
|
||||
expect.objectContaining({
|
||||
style: expect.any(Object),
|
||||
iconTheme: {
|
||||
primary: '#EF4444', // Check for the specific error icon color
|
||||
primary: '#EF4444',
|
||||
secondary: '#fff',
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user