Replace @vercel/postgres, Blob, and Upstash with lib/sql.js, MinIO object storage, and CT 102 Redis rate limits. Add Dockerfile for Dokploy deploy, homelab runbooks, Neon data-copy helper, and point CI smoke/visual at the homelab URL instead of Vercel previews. Co-authored-by: Cursor <cursoragent@cursor.com>
72 lines
2.3 KiB
JavaScript
72 lines
2.3 KiB
JavaScript
import { describe, expect, it, vi, afterEach } from 'vitest';
|
|
import { embedCardImage, formatEmbeddingForPg } from '../../lib/card-embed.js';
|
|
|
|
describe('formatEmbeddingForPg', () => {
|
|
it('formats vectors for pgvector literals', () => {
|
|
expect(formatEmbeddingForPg([0.1, 0.2, 0.3])).toBe('[0.10000000,0.20000000,0.30000000]');
|
|
});
|
|
});
|
|
|
|
describe('embedCardImage', () => {
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals();
|
|
delete process.env.AI_GATEWAY_API_KEY;
|
|
});
|
|
|
|
it('throws when AI_GATEWAY_API_KEY is missing', async () => {
|
|
await expect(embedCardImage('data:image/jpeg;base64,abc')).rejects.toThrow(
|
|
'AI_GATEWAY_API_KEY is not configured'
|
|
);
|
|
});
|
|
|
|
it('returns embedding values from the gateway response', async () => {
|
|
process.env.AI_GATEWAY_API_KEY = 'test-key';
|
|
const vector = Array.from({ length: 1024 }, (_, index) => index / 1024);
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async () => ({
|
|
ok: true,
|
|
json: async () => ({ data: [{ embedding: vector }] }),
|
|
}))
|
|
);
|
|
|
|
const embedding = await embedCardImage('data:image/jpeg;base64,abc');
|
|
expect(embedding).toEqual(vector);
|
|
});
|
|
|
|
it('sends catalog image URLs as plain strings (not OpenAI image_url objects)', async () => {
|
|
process.env.AI_GATEWAY_API_KEY = 'test-key';
|
|
const fetchMock = vi.fn(async () => ({
|
|
ok: true,
|
|
json: async () => ({
|
|
data: [{ embedding: Array.from({ length: 1024 }, () => 0.1) }],
|
|
}),
|
|
}));
|
|
vi.stubGlobal('fetch', fetchMock);
|
|
|
|
await embedCardImage({ imageUrl: 'https://cards.scryfall.io/normal/front/a.jpg' });
|
|
|
|
const [, requestInit] = fetchMock.mock.calls[0];
|
|
expect(JSON.parse(requestInit.body)).toMatchObject({
|
|
input: 'https://cards.scryfall.io/normal/front/a.jpg',
|
|
});
|
|
});
|
|
|
|
it('truncates oversized gateway vectors to the catalog dimension', async () => {
|
|
process.env.AI_GATEWAY_API_KEY = 'test-key';
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async () => ({
|
|
ok: true,
|
|
json: async () => ({
|
|
data: [{ embedding: Array.from({ length: 1536 }, (_, index) => index / 1536) }],
|
|
}),
|
|
}))
|
|
);
|
|
|
|
const embedding = await embedCardImage('data:image/jpeg;base64,abc');
|
|
expect(embedding).toHaveLength(1024);
|
|
expect(embedding[0]).toBe(0);
|
|
expect(embedding[1023]).toBeCloseTo(1023 / 1536, 5);
|
|
});
|
|
});
|