deckhearth/test/lib/card-import-sync-catalog.test.js
Randall Stillwell ec9bb2b93e feat(catalog): unified multi-game bulk sync + schema map update
Ship Pokemon and Lorcana bulk import libs/scripts, unified weekly cron
sync across MTG/Pokemon/Lorcana with per-game error isolation and
catalog_sync_log telemetry. Admin UI adds Unified/Incremental/Bulk MTG modes.

- Rename reconcile migrations to 1781442330* timestamps so they apply
  after bulk-data migrations without node-pg-migrate ordering conflicts
- Add Lorcana set-code normalization + orphan cleanup migrations
- Drop stricter user_cards_user_card_unique (keep 3-column foil unique)
- Update docs/SCHEMA_MAP.md for tags, card_tags, catalog_sync_log, bulk columns

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 08:22:19 -05:00

83 lines
2.6 KiB
JavaScript

import { describe, expect, it, vi, beforeEach } from 'vitest';
import { buildCatalogSyncQueue, runUnifiedCatalogSync } from '../../lib/card-import/sync-catalog.js';
import { runBulkMtgSync } from '../../lib/card-import/bulk-sync.js';
import { runBulkPokemonSync } from '../../lib/card-import/pokemon-bulk.js';
import { runBulkLorcanaSync } from '../../lib/card-import/lorcana-bulk.js';
vi.mock('../../lib/card-import/bulk-sync.js', () => ({
runBulkMtgSync: vi.fn(),
}));
vi.mock('../../lib/card-import/pokemon-bulk.js', () => ({
runBulkPokemonSync: vi.fn(),
}));
vi.mock('../../lib/card-import/lorcana-bulk.js', () => ({
runBulkLorcanaSync: vi.fn(),
}));
vi.mock('@vercel/postgres', () => ({
sql: vi.fn(() => Promise.resolve({ rows: [] })),
}));
describe('buildCatalogSyncQueue', () => {
it('merges MTG and Pokémon missing sets by release date, newest first', () => {
const queue = buildCatalogSyncQueue(
[
{ code: 'fin', name: 'Final Fantasy', releasedAt: '2026-05-01' },
{ code: 'dsk', name: 'Duskmourn', releasedAt: '2024-09-27' },
],
[
{ id: 'sv10', name: 'Perfect Order', releasedAt: '2026-03-27' },
{ id: 'sv9', name: 'Journey Together', releasedAt: '2025-03-28' },
],
3
);
expect(queue).toEqual([
{
game: 'mtg',
setCode: 'fin',
name: 'Final Fantasy',
releasedAt: '2026-05-01',
},
{
game: 'pokemon',
setCode: 'sv10',
name: 'Perfect Order',
releasedAt: '2026-03-27',
},
{
game: 'pokemon',
setCode: 'sv9',
name: 'Journey Together',
releasedAt: '2025-03-28',
},
]);
});
});
describe('runUnifiedCatalogSync', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('runs all three games and isolates per-game failures', async () => {
runBulkMtgSync.mockResolvedValue({ mode: 'bulk', upserted: 36000, errors: 0 });
runBulkPokemonSync.mockRejectedValue(new Error('Pokemon API down'));
runBulkLorcanaSync.mockResolvedValue({ mode: 'bulk', upserted: 2283, errors: 0 });
const summary = await runUnifiedCatalogSync();
expect(summary.mode).toBe('unified');
expect(summary.mtg.upserted).toBe(36000);
expect(summary.pokemon.error).toBe('Pokemon API down');
expect(summary.lorcana.upserted).toBe(2283);
expect(summary.errors).toEqual([{ game: 'pokemon', message: 'Pokemon API down' }]);
expect(summary.durationMs).toBeGreaterThanOrEqual(0);
expect(runBulkMtgSync).toHaveBeenCalledOnce();
expect(runBulkPokemonSync).toHaveBeenCalledOnce();
expect(runBulkLorcanaSync).toHaveBeenCalledOnce();
});
});