import {describe, it, expect, vi, Mock, beforeAll, beforeEach} from 'vitest'; import { readFile, writeFile } from 'fs/promises'; import { JsonLinesDataSource } from '../src'; import { resource, validation as v, BaseResourceType } from '@modal-sh/yasumi'; import {DataSource} from '@modal-sh/yasumi/dist/types/backend'; vi.mock('fs/promises'); const toJsonl = (dummyItems: unknown[]) => dummyItems.map((i) => JSON.stringify(i)).join('\n'); const ID_ATTR = 'id' as const; describe('prepareResource', () => { beforeAll(() => { const mockWriteFile = writeFile as Mock; mockWriteFile.mockImplementation(() => { /* noop */ }) }); it('works', () => { const schema = v.object({}); const r = resource(schema); const ds = new JsonLinesDataSource(); expect(() => ds.prepareResource(r)).not.toThrow(); }); }); describe('methods', () => { const dummyItems = [ { id: 1, name: 'foo', }, { id: 2, name: 'bar', }, { id: 3, name: 'baz', }, ]; const schema = v.object({ name: v.string(), }); let ds: DataSource>; let mockGenerationStrategy: Mock; beforeEach(() => { mockGenerationStrategy = vi.fn(); const r = resource(schema) .id(ID_ATTR, { generationStrategy: mockGenerationStrategy, schema: v.any(), serialize: (id) => id.toString(), deserialize: (id) => Number(id?.toString() ?? 0), }); ds = new JsonLinesDataSource(); ds.prepareResource(r); }); beforeEach(() => { const mockReadFile = readFile as Mock; mockReadFile.mockReturnValueOnce(toJsonl(dummyItems)); }); let mockWriteFile: Mock; beforeEach(() => { mockWriteFile = writeFile as Mock; mockWriteFile.mockImplementationOnce(() => { /* noop */ }); }); describe('initialize', () => { it('works', async () => { try { await ds.initialize(); } catch { expect.fail('Could not initialize data source.'); } }); }); describe('operations', () => { beforeEach(async () => { await ds.initialize(); }); describe('getTotalCount', () => { it('works', async () => { if (typeof ds.getTotalCount !== 'function') { return; } const totalCount = await ds.getTotalCount(); expect(totalCount).toBe(dummyItems.length); }); }); describe('getMultiple', () => { it('works', async () => { const items = await ds.getMultiple(); expect(items).toEqual(dummyItems); }); }); describe('getById', () => { it('works', async () => { const id = 2; const item = await ds.getById(id.toString()); // ID is always a string because it originates from URLs const expected = dummyItems.find((i) => i[ID_ATTR] === id); expect(item).toEqual(expected); }); }); describe('getSingle', () => { it('works', async () => { if (typeof ds.getSingle !== 'function') { // skip if data source doesn't offer this return; } const item = await ds.getSingle(); const expected = dummyItems[0]; expect(item).toEqual(expected); }); }); describe('create', () => { it('works', async () => { const data = { // notice we don't have IDs here, as it is expected to be generated by newId() name: 'foo' }; const newItem = await ds.create(data); expect(mockWriteFile).toBeCalledWith( expect.any(String), toJsonl([ ...dummyItems, { id: 0, ...data, } ]) ); expect(newItem).toEqual({ id: 0, ...data}); }); }); describe('delete', () => { it('works', async () => { await ds.delete('1'); expect(mockWriteFile).toBeCalledWith( expect.any(String), toJsonl(dummyItems.filter((d) => d[ID_ATTR] !== 1)), ); }); }); describe('emplace', () => { it('replaces existing data', async () => { const data = { [ID_ATTR]: 2, name: 'foo', }; const { id, ...etcData } = data; const newItem = await ds.emplace( id.toString(), etcData, ); expect(mockWriteFile).toBeCalledWith( expect.any(String), toJsonl(dummyItems.map((d) => d[ID_ATTR] === data[ID_ATTR] // ID will be defined first, since we are just writing to file, we need strict ordering ? { [ID_ATTR]: id, ...etcData } : d )), ); expect(newItem).toEqual([data, false]); }); it('creates new data', async () => { const data = { [ID_ATTR]: 4, name: 'quux', }; const { [ID_ATTR]: id, ...etcData } = data; const newItem = await ds.emplace( id.toString(), etcData, ); expect(mockWriteFile).toBeCalledWith( expect.any(String), toJsonl([ ...dummyItems, data ]), ); expect(newItem).toEqual([data, true]); }); }); describe('patch', () => { it('works', async () => { const data = { [ID_ATTR]: 2, name: 'foo', }; const { id, ...etcData } = data; const newItem = await ds.emplace( id.toString(), etcData, ); expect(mockWriteFile).toBeCalledWith( expect.any(String), toJsonl(dummyItems.map((d) => d[ID_ATTR] === data[ID_ATTR] // ID will be defined first, since we are just writing to file, we need strict ordering ? { [ID_ATTR]: id, ...etcData } : d )), ); expect(newItem).toBeDefined(); }); }); describe('newId', () => { it('works', async () => { const v = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); mockGenerationStrategy.mockResolvedValueOnce(v); const id = await ds.newId(); expect(id).toBe(v); }); }); }); });