/* * Copyright (c) 2026 by Christian Kellner. * Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause */ import { vi, describe, it, expect, beforeEach } from 'vitest'; // We mock SqliteConnection so we can assert which SQL the storage layer // runs and with which params, without spinning up a real SQLite DB. const calls = { execute: [], query: [], }; const sqliteMock = { execute: (sql, params) => { calls.execute.push({ sql, params }); // Default: pretend 1 row was affected (so setListingStatus reports success). return { changes: 1 }; }, query: (sql, params) => { calls.query.push({ sql, params }); // Return shape varies by test — overridden via queryHandler when needed. if (sqliteMock.__queryHandler) return sqliteMock.__queryHandler(sql, params); return []; }, __queryHandler: null, }; vi.mock('../../lib/services/storage/SqliteConnection.js', () => ({ default: sqliteMock, })); describe('listingsStorage.setListingStatus', () => { let listingsStorage; beforeEach(async () => { calls.execute.length = 0; calls.query.length = 0; sqliteMock.__queryHandler = null; listingsStorage = await import('../../lib/services/storage/listingsStorage.js'); }); it('runs an UPDATE with the normalized status and listing id', () => { const changes = listingsStorage.setListingStatus('listing-1', 'Applied'); expect(changes).toBe(1); expect(calls.execute).toHaveLength(1); expect(calls.execute[0].sql).toMatch(/UPDATE listings SET status = @status WHERE id = @id/); expect(calls.execute[0].params).toEqual({ id: 'listing-1', status: 'applied' }); }); it('accepts null to clear the status', () => { listingsStorage.setListingStatus('listing-2', null); expect(calls.execute[0].params).toEqual({ id: 'listing-2', status: null }); }); it('rejects invalid statuses', () => { expect(() => listingsStorage.setListingStatus('listing-3', 'maybe')).toThrow(/Invalid listing status/); expect(calls.execute).toHaveLength(0); }); it('returns 0 when no id is supplied (no SQL is run)', () => { const result = listingsStorage.setListingStatus(null, 'applied'); expect(result).toBe(0); expect(calls.execute).toHaveLength(0); }); }); describe('listingsStorage.queryListings statusFilter', () => { let listingsStorage; beforeEach(async () => { calls.execute.length = 0; calls.query.length = 0; // Return empty rows for both the count and the page-fetch queries. sqliteMock.__queryHandler = (sql) => { if (/COUNT\(1\)/.test(sql)) return [{ cnt: 0 }]; return []; }; listingsStorage = await import('../../lib/services/storage/listingsStorage.js'); }); it("adds 'l.status IS NULL' to WHERE when statusFilter is 'none'", () => { listingsStorage.queryListings({ statusFilter: 'none', userId: 'u1', isAdmin: true }); const pageQuery = calls.query.find((c) => !/COUNT\(1\)/.test(c.sql)); expect(pageQuery.sql).toMatch(/\(l\.status IS NULL\)/); }); it("adds 'l.status = @statusValue' for a concrete status", () => { listingsStorage.queryListings({ statusFilter: 'applied', userId: 'u1', isAdmin: true }); const pageQuery = calls.query.find((c) => !/COUNT\(1\)/.test(c.sql)); expect(pageQuery.sql).toMatch(/\(l\.status = @statusValue\)/); expect(pageQuery.params.statusValue).toBe('applied'); }); it('ignores unknown statusFilter values silently', () => { listingsStorage.queryListings({ statusFilter: 'bogus', userId: 'u1', isAdmin: true }); const pageQuery = calls.query.find((c) => !/COUNT\(1\)/.test(c.sql)); expect(pageQuery.sql).not.toMatch(/status/i); }); }); describe('watchListStorage.ensureWatch', () => { let watchListStorage; beforeEach(async () => { calls.execute.length = 0; calls.query.length = 0; sqliteMock.__queryHandler = null; watchListStorage = await import('../../lib/services/storage/watchListStorage.js'); }); it('inserts and reports watched=true on first call', () => { // After INSERT, createWatch queries for existence and gets a row back. sqliteMock.__queryHandler = () => [{ ok: 1 }]; const result = watchListStorage.ensureWatch('listing-1', 'user-1'); expect(result).toEqual({ watched: true }); // INSERT should have been issued. expect(calls.execute.some((c) => /INSERT INTO watch_list/.test(c.sql))).toBe(true); }); it('returns watched=true when an entry already exists', () => { // Simulate ON CONFLICT being a no-op: execute reports no changes, then SELECT confirms row exists. sqliteMock.execute = (sql, params) => { calls.execute.push({ sql, params }); return { changes: 0 }; }; sqliteMock.__queryHandler = () => [{ ok: 1 }]; const result = watchListStorage.ensureWatch('listing-2', 'user-2'); expect(result).toEqual({ watched: true }); // Restore execute to default for subsequent tests. sqliteMock.execute = (sql, params) => { calls.execute.push({ sql, params }); return { changes: 1 }; }; }); it('returns watched=false when listingId or userId is missing', () => { expect(watchListStorage.ensureWatch(null, 'u')).toEqual({ watched: false }); expect(watchListStorage.ensureWatch('l', null)).toEqual({ watched: false }); expect(calls.execute).toHaveLength(0); }); });