Files
flyer-crawler.projectium.com/src/tests/integration/ai.integration.test.ts

168 lines
6.1 KiB
TypeScript

// src/tests/integration/ai.integration.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import supertest from 'supertest';
import app from '../../../server';
import fs from 'node:fs/promises';
import path from 'path';
import { createAndLoginUser } from '../utils/testHelpers';
/**
* @vitest-environment node
*/
const request = supertest(app);
interface TestGeolocationCoordinates {
latitude: number;
longitude: number;
accuracy: number;
altitude: number | null;
altitudeAccuracy: number | null;
heading: number | null;
speed: number | null;
toJSON(): object;
}
describe('AI API Routes Integration Tests', () => {
let authToken: string;
beforeAll(async () => {
// Create and log in as a new user for authenticated tests.
({ token: authToken } = await createAndLoginUser({ fullName: 'AI Tester' }));
});
afterAll(async () => {
// Clean up any files created in the flyer-images directory during these tests.
const uploadDir = path.resolve(__dirname, '../../../flyer-images');
try {
const files = await fs.readdir(uploadDir);
// Target files created by the 'image' and 'images' multer instances.
const testFiles = files.filter((f) => f.startsWith('image-') || f.startsWith('images-'));
for (const file of testFiles) {
await fs.unlink(path.join(uploadDir, file));
}
} catch (error) {
console.error('Error during AI integration test file cleanup:', error);
}
});
it('POST /api/ai/check-flyer should return a boolean', async () => {
const response = await request
.post('/api/ai/check-flyer')
.set('Authorization', `Bearer ${authToken}`)
.attach('image', Buffer.from('content'), 'test.jpg');
const result = response.body;
expect(response.status).toBe(200);
// The backend is stubbed to always return true for this check
expect(result.is_flyer).toBe(true);
});
it('POST /api/ai/extract-address should return a stubbed address', async () => {
const response = await request
.post('/api/ai/extract-address')
.set('Authorization', `Bearer ${authToken}`)
.attach('image', Buffer.from('content'), 'test.jpg');
const result = response.body;
expect(response.status).toBe(200);
expect(result.address).toBe('not identified');
});
it('POST /api/ai/extract-logo should return a stubbed response', async () => {
const response = await request
.post('/api/ai/extract-logo')
.set('Authorization', `Bearer ${authToken}`)
.attach('images', Buffer.from('content'), 'test.jpg');
const result = response.body;
expect(response.status).toBe(200);
expect(result).toEqual({ store_logo_base_64: null });
});
it('POST /api/ai/quick-insights should return a stubbed insight', async () => {
const response = await request
.post('/api/ai/quick-insights')
.set('Authorization', `Bearer ${authToken}`)
.send({ items: [{ item: 'test' }] });
const result = response.body;
expect(response.status).toBe(200);
expect(result.text).toBe('This is a server-generated quick insight: buy the cheap stuff!');
});
it('POST /api/ai/deep-dive should return a stubbed analysis', async () => {
const response = await request
.post('/api/ai/deep-dive')
.set('Authorization', `Bearer ${authToken}`)
.send({ items: [{ item: 'test' }] });
const result = response.body;
expect(response.status).toBe(200);
expect(result.text).toBe('This is a server-generated deep dive analysis. It is very detailed.');
});
it('POST /api/ai/search-web should return a stubbed search result', async () => {
const response = await request
.post('/api/ai/search-web')
.set('Authorization', `Bearer ${authToken}`)
.send({ query: 'test query' });
const result = response.body;
expect(response.status).toBe(200);
expect(result).toEqual({ text: 'The web says this is good.', sources: [] });
});
it('POST /api/ai/plan-trip should return an error as the feature is disabled', async () => {
// The GeolocationCoordinates type requires more than just lat/lng.
// We create a complete mock object to satisfy the type.
const mockLocation: TestGeolocationCoordinates = {
latitude: 48.4284,
longitude: -123.3656,
accuracy: 100,
altitude: null,
altitudeAccuracy: null,
heading: null,
speed: null,
toJSON: function () {
return {
latitude: this.latitude,
longitude: this.longitude,
accuracy: this.accuracy,
altitude: this.altitude,
altitudeAccuracy: this.altitudeAccuracy,
heading: this.heading,
speed: this.speed,
};
},
};
const mockStore = {
name: 'Test Store for Trip',
store_id: 1,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
};
const response = await request
.post('/api/ai/plan-trip')
.set('Authorization', `Bearer ${authToken}`)
.send({ items: [], store: mockStore, userLocation: mockLocation });
// The service for this endpoint is disabled and throws an error, which results in a 500.
expect(response.status).toBe(500);
const errorResult = response.body;
expect(errorResult.message).toContain('planTripWithMaps');
});
it('POST /api/ai/generate-image should reject because it is not implemented', async () => {
// The backend for this is not stubbed and will throw an error.
// This test confirms that the endpoint is protected and responds as expected to a failure.
const response = await request
.post('/api/ai/generate-image')
.set('Authorization', `Bearer ${authToken}`)
.send({ prompt: 'a test prompt' });
expect(response.status).toBe(501);
});
it('POST /api/ai/generate-speech should reject because it is not implemented', async () => {
// The backend for this is not stubbed and will throw an error.
const response = await request
.post('/api/ai/generate-speech')
.set('Authorization', `Bearer ${authToken}`)
.send({ text: 'a test prompt' });
expect(response.status).toBe(501);
});
});