|
- 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<typeof schema>();
- 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<v.Output<typeof schema>>;
- 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<typeof schema>();
- 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);
- });
- });
- });
- });
|