Files
flyer-crawler.projectium.com/src/services/aiAnalysisService.test.ts

209 lines
7.0 KiB
TypeScript

// src/services/aiAnalysisService.test.ts
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as aiApiClient from './aiApiClient';
import { AiAnalysisService } from './aiAnalysisService';
import { createMockFlyerItem } from '../tests/utils/mockFactories';
// Mock the dependencies
vi.mock('./aiApiClient');
vi.mock('./logger.client', () => ({
logger: {
info: vi.fn(),
error: vi.fn(),
warn: vi.fn(),
debug: vi.fn(),
},
}));
describe('AiAnalysisService', () => {
let service: AiAnalysisService;
beforeEach(() => {
vi.clearAllMocks();
service = new AiAnalysisService();
});
describe('getQuickInsights', () => {
it('should return insights string on success', async () => {
const mockResponse = 'Quick insights result';
vi.mocked(aiApiClient.getQuickInsights).mockResolvedValue({
json: () => Promise.resolve(mockResponse),
} as Response);
const result = await service.getQuickInsights([]);
expect(result).toBe(mockResponse);
});
});
describe('getDeepDiveAnalysis', () => {
it('should return analysis string on success', async () => {
const mockResponse = 'Deep dive result';
vi.mocked(aiApiClient.getDeepDiveAnalysis).mockResolvedValue({
json: () => Promise.resolve(mockResponse),
} as Response);
const result = await service.getDeepDiveAnalysis([]);
expect(result).toBe(mockResponse);
});
});
describe('searchWeb', () => {
it('should return a grounded response on success', async () => {
const mockResponse = {
text: 'Search results',
sources: [{ web: { uri: 'https://example.com', title: 'Example' } }],
};
vi.mocked(aiApiClient.searchWeb).mockResolvedValue({
json: () => Promise.resolve(mockResponse),
} as Response);
const result = await service.searchWeb([createMockFlyerItem({ item: 'test' })]);
expect(result.text).toBe('Search results');
expect(result.sources).toEqual([{ uri: 'https://example.com', title: 'Example' }]);
});
it('should handle responses with missing or empty sources array', async () => {
const mockResponse = { text: 'Search results', sources: null };
vi.mocked(aiApiClient.searchWeb).mockResolvedValue({
json: () => Promise.resolve(mockResponse),
} as Response);
const result = await service.searchWeb([createMockFlyerItem({ item: 'test' })]);
expect(result.text).toBe('Search results');
expect(result.sources).toEqual([]);
});
it('should handle source objects without a "web" property', async () => {
const mockResponse = {
text: 'Search results',
sources: [{ some_other_property: 'value' }],
};
vi.mocked(aiApiClient.searchWeb).mockResolvedValue({
json: () => Promise.resolve(mockResponse),
} as Response);
const result = await service.searchWeb([createMockFlyerItem({ item: 'test' })]);
expect(result.sources).toEqual([{ uri: '', title: 'Untitled' }]);
});
it('should re-throw an error if the API call fails', async () => {
const apiError = new Error('API is down');
vi.mocked(aiApiClient.searchWeb).mockRejectedValue(apiError);
await expect(service.searchWeb([createMockFlyerItem({ item: 'test' })])).rejects.toThrow(
apiError,
);
});
});
describe('planTripWithMaps', () => {
const mockGeolocation = {
getCurrentPosition: vi.fn(),
};
beforeEach(() => {
// Mock the global navigator object
Object.defineProperty(global, 'navigator', {
value: {
geolocation: mockGeolocation,
},
writable: true,
});
});
afterEach(() => {
// Clean up the global mock
vi.stubGlobal('navigator', undefined);
});
it('should call the API with the fetched location on success', async () => {
const mockCoords = { latitude: 45, longitude: -75 };
mockGeolocation.getCurrentPosition.mockImplementationOnce((success) => {
success({ coords: mockCoords });
});
vi.mocked(aiApiClient.planTripWithMaps).mockResolvedValue({
json: () => Promise.resolve({ text: 'Trip planned!', sources: [] }),
} as Response);
const result = await service.planTripWithMaps([], undefined);
expect(mockGeolocation.getCurrentPosition).toHaveBeenCalledTimes(1);
expect(aiApiClient.planTripWithMaps).toHaveBeenCalledWith([], undefined, mockCoords);
expect(result.text).toBe('Trip planned!');
});
it('should reject if geolocation is not supported', async () => {
// Undefine geolocation for this test
Object.defineProperty(global, 'navigator', {
value: {
geolocation: undefined,
},
writable: true,
});
await expect(service.planTripWithMaps([], undefined)).rejects.toThrow(
'Geolocation is not supported by your browser.',
);
expect(aiApiClient.planTripWithMaps).not.toHaveBeenCalled();
});
it('should reject if the user denies geolocation permission', async () => {
// Create a mock object that conforms to the GeolocationPositionError interface.
const permissionError = {
code: 1, // PERMISSION_DENIED
message: 'User denied Geolocation',
};
mockGeolocation.getCurrentPosition.mockImplementationOnce((_, error) => {
if (error) {
error(permissionError as GeolocationPositionError);
}
});
await expect(service.planTripWithMaps([], undefined)).rejects.toEqual(permissionError);
expect(aiApiClient.planTripWithMaps).not.toHaveBeenCalled();
});
});
describe('compareWatchedItemPrices', () => {
it('should return grounded response on success', async () => {
const mockResponse = { text: 'Comparison', sources: [] };
vi.mocked(aiApiClient.compareWatchedItemPrices).mockResolvedValue({
json: () => Promise.resolve(mockResponse),
} as Response);
const result = await service.compareWatchedItemPrices([]);
expect(result.text).toBe('Comparison');
});
it('should re-throw an error if the API call fails', async () => {
const apiError = new Error('API is down');
vi.mocked(aiApiClient.compareWatchedItemPrices).mockRejectedValue(apiError);
await expect(service.compareWatchedItemPrices([])).rejects.toThrow(apiError);
});
});
describe('generateImageFromText', () => {
it('should return image string on success', async () => {
const mockResponse = 'base64image';
vi.mocked(aiApiClient.generateImageFromText).mockResolvedValue({
json: () => Promise.resolve(mockResponse),
} as Response);
const result = await service.generateImageFromText('prompt');
expect(result).toBe(mockResponse);
});
it('should re-throw an error if the API call fails', async () => {
const apiError = new Error('API is down');
vi.mocked(aiApiClient.generateImageFromText).mockRejectedValue(apiError);
await expect(service.generateImageFromText('a prompt')).rejects.toThrow(apiError);
});
});
});