/* eslint-disable @typescript-eslint/no-non-null-assertion,@typescript-eslint/ban-ts-comment,@typescript-eslint/no-explicit-any */ /* * Copyright (C) 2018, 2019 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ import {TestBed} from '@angular/core/testing'; import {Client} from '@openstapps/api/lib/client'; import { SCDish, SCMessage, SCMultiSearchRequest, SCSaveableThing, SCSearchQuery, SCSearchResponse, SCSearchValueFilter, SCThingOriginType, SCThingType, } from '@openstapps/core'; import {sampleThingsMap} from '../../_helpers/data/sample-things'; import {StorageProvider} from '../storage/storage.provider'; import {DataModule} from './data.module'; import {DataProvider, DataScope} from './data.provider'; import {StAppsWebHttpClient} from './stapps-web-http-client.provider'; describe('DataProvider', () => { let dataProvider: DataProvider; let storageProvider: StorageProvider; const sampleThing: SCMessage = sampleThingsMap.message[0] as SCMessage; const sampleResponse: SCSearchResponse = { data: sampleThingsMap.dish as SCDish[], facets: [ { buckets: [], field: 'foo', }, ], pagination: { count: 0, offset: 0, total: 0, }, stats: { time: 123, }, }; const sampleFilter: SCSearchValueFilter = { arguments: { field: 'type', value: 'dish', }, type: 'value', }; const sampleQuery: SCSearchQuery = { filter: sampleFilter, }; const sampleSavable: SCSaveableThing = { data: sampleThing, name: sampleThing.name, origin: { created: new Date().toISOString(), type: SCThingOriginType.User, }, type: SCThingType.Message, uid: sampleThing.uid, }; const otherSampleThing: SCMessage = { ...sampleThing, uid: 'message-456', name: 'bar', }; beforeEach(async () => { TestBed.configureTestingModule({ imports: [DataModule], providers: [DataProvider, StAppsWebHttpClient], }); storageProvider = TestBed.get(StorageProvider); dataProvider = TestBed.get(DataProvider); await storageProvider.deleteAll(); }); it('should generate data key', async () => { dataProvider.storagePrefix = 'foo.data'; expect(dataProvider.getDataKey('123')).toBe('foo.data.123'); }); it('should provide backend data items using search query', async () => { spyOn(Client.prototype as any, 'search').and.callFake(() => { return { then: (callback: any) => { return callback(sampleResponse); }, }; }); const response = await dataProvider.search(sampleQuery); expect(response).toEqual(sampleResponse); }); it('should provide backend data items using multi search query', async () => { spyOn(Client.prototype as any, 'multiSearch').and.callFake(() => ({ then: (callback: any) => { return callback({ a: sampleResponse, }); }, })); const response = await dataProvider.multiSearch({a: sampleQuery}); expect(response).toEqual({a: sampleResponse}); }); it('should partition search requests correctly', async () => { const request = { a: 'a', b: 'b', c: 'c', d: 'd', e: 'e', } as SCMultiSearchRequest; // and response... const requestCheck = Object.assign({}, request); const responseShould = { a: 'A', b: 'B', c: 'C', d: 'D', e: 'E', }; dataProvider.backendQueriesLimit = 2; spyOn(Client.prototype as any, 'multiSearch').and.callFake( (request_: SCMultiSearchRequest) => ({ then: (callback: any) => { let i = 0; for (const key in request_) { if (request_.hasOwnProperty(key)) { i++; // @ts-ignore expect(requestCheck[key]).not.toBeNull(); expect(requestCheck[key]).toEqual(request_[key]); // @ts-ignore // eslint-disable-next-line unicorn/no-null requestCheck[key] = null; // @ts-ignore request_[key] = request_[key].toUpperCase(); } } expect(i).toBeLessThanOrEqual(dataProvider.backendQueriesLimit); return callback(request_); }, }), ); const response = await dataProvider.multiSearch(request); expect(response).toEqual(responseShould); }); it('should put an data item into the local database (storage)', async () => { let providedThing: SCSaveableThing; spyOn(storageProvider, 'put' as any).and.callFake( (_id: any, thing: any) => { providedThing = thing; providedThing.origin.created = sampleSavable.origin.created; }, ); expect(storageProvider.put).not.toHaveBeenCalled(); expect(providedThing!).not.toBeDefined(); await dataProvider.put(sampleThing); expect(providedThing!).toBeDefined(); expect(providedThing!).toEqual(sampleSavable); }); it('should correctly set and get single data item from the local database (storage)', async () => { await dataProvider.put(sampleThing); spyOn(storageProvider, 'get').and.callThrough(); expect(storageProvider.get).not.toHaveBeenCalled(); const providedThing = await dataProvider.get( sampleThing.uid, DataScope.Local, ); providedThing.origin.created = sampleSavable.origin.created; expect(storageProvider.get).toHaveBeenCalledWith( dataProvider.getDataKey(sampleThing.uid), ); expect(providedThing).toEqual(sampleSavable); }); it('should provide all data items from the local database (storage)', async () => { await dataProvider.put(sampleThing); await dataProvider.put(otherSampleThing); const result = await dataProvider.getAll(); expect([...result.keys()].sort()).toEqual([ dataProvider.getDataKey(sampleThing.uid), dataProvider.getDataKey(otherSampleThing.uid), ]); expect(result.get(dataProvider.getDataKey(sampleThing.uid))!.data).toEqual( sampleThing, ); expect( result.get(dataProvider.getDataKey(otherSampleThing.uid))!.data, ).toEqual(otherSampleThing); }); it('should provide single data from the backend', async () => { spyOn(Client.prototype, 'getThing' as any).and.callFake(() => { return { then: (callback: any) => { return callback(sampleThing); }, }; }); expect(Client.prototype.getThing).not.toHaveBeenCalled(); const providedThing = await dataProvider.get( sampleThing.uid, DataScope.Remote, ); expect(Client.prototype.getThing).toHaveBeenCalledWith(sampleThing.uid); expect(providedThing).toBe(sampleThing); }); it('should get an item from both local and remote database', async () => { spyOn(Client.prototype, 'getThing' as any).and.callFake(() => { return { then: (callback: any) => { return callback(sampleThing); }, }; }); spyOn(storageProvider, 'get' as any).and.callFake(() => { return { then: (callback: any) => { return callback(sampleSavable); }, }; }); const result = await dataProvider.get(sampleThing.uid); expect(result.get(DataScope.Local)).toEqual(sampleSavable); expect(result.get(DataScope.Remote)).toEqual(sampleThing); }); it('should properly delete a data item from the local database (storage)', async () => { spyOn(storageProvider, 'delete').and.callThrough(); await dataProvider.put(sampleThing); expect(await storageProvider.length()).toBe(1); await dataProvider.delete(sampleThing.uid); expect(storageProvider.delete).toHaveBeenCalledWith( dataProvider.getDataKey(sampleThing.uid), ); expect(await storageProvider.length()).toBe(0); }); it('should properly delete all the data items from the local database (storage)', async () => { spyOn(storageProvider, 'delete').and.callThrough(); await dataProvider.put(sampleThing); await dataProvider.put(otherSampleThing); await storageProvider.put('some-uid', {some: 'thing'}); expect(await storageProvider.length()).not.toBe(0); await dataProvider.deleteAll(); expect(storageProvider.delete).toHaveBeenCalledWith( dataProvider.getDataKey(sampleThing.uid), dataProvider.getDataKey(otherSampleThing.uid), ); const result = await storageProvider.getAll(); expect([...result.keys()]).toEqual(['some-uid']); }); it('should properly check if a data item has already been saved', async () => { expect(await dataProvider.isSaved(sampleThing.uid)).toBeFalsy(); await dataProvider.put(sampleThing); expect(await dataProvider.isSaved(sampleThing.uid)).toBeTruthy(); }); });