зеркало из https://github.com/microsoft/paris.git
532 строки
16 KiB
TypeScript
532 строки
16 KiB
TypeScript
import { Observable, of, forkJoin } from 'rxjs';
|
|
import { DataEntityType } from '../lib/api/entity/data-entity.base';
|
|
import { RelationshipRepository } from '../lib/api/repository/relationship-repository';
|
|
import { Repository } from '../lib/api/repository/repository';
|
|
import { DataQuery } from '../lib/data_access/data-query';
|
|
import { DataOptions, defaultDataOptions } from '../lib/data_access/data.options';
|
|
import { Paris } from '../lib/paris';
|
|
import { mergeMap } from 'rxjs/operators';
|
|
import { CreateTodoListApiCall } from './mock/create-new-list.api-call';
|
|
import { Tag } from './mock/tag.value-object';
|
|
import { TodoList } from './mock/todo-list.entity';
|
|
import { TodoListItemsRelationship } from './mock/todo-list.relationships';
|
|
import { TodoStatus } from './mock/todo-status.entity';
|
|
import { Todo } from './mock/todo.entity';
|
|
import { UpdateTodoApiCall } from './mock/update-todo.api-call';
|
|
import { DataSet } from "../lib/data_access/dataset";
|
|
import { DataStoreService } from '../lib/data_access/data-store.service';
|
|
import { TodoType } from './mock/todo-type.entity';
|
|
import { RequestMethod } from '../lib/data_access/http.service';
|
|
import { EntityModelBase } from '../lib/config/entity-model.base';
|
|
|
|
describe('Paris main', () => {
|
|
let paris: Paris;
|
|
|
|
describe('Get repository', () => {
|
|
let repo: Repository<Todo>;
|
|
|
|
beforeEach(() => {
|
|
paris = new Paris();
|
|
repo = paris.getRepository(Todo);
|
|
});
|
|
|
|
it('should create a ToDo repository', () => {
|
|
expect(repo).toBeDefined();
|
|
});
|
|
|
|
it('should return a Repository from getRepository', () => {
|
|
expect(repo).toBeInstanceOf(Repository);
|
|
});
|
|
|
|
it('should return a repository for a ValueObject', () => {
|
|
expect(paris.getRepository(Tag)).toBeDefined();
|
|
});
|
|
|
|
it.skip("should return null if entity doesn't exist", () => { });
|
|
});
|
|
|
|
describe('Get Todo item by ID', () => {
|
|
let repo: Repository<Todo>, item$: Observable<Todo>;
|
|
|
|
beforeEach(() => {
|
|
paris = new Paris();
|
|
repo = paris.getRepository(Todo);
|
|
|
|
jest.spyOn(paris.dataStore.httpService, 'request');
|
|
jest.spyOn(repo, 'getItemById');
|
|
jest.spyOn(paris.dataStore, 'request');
|
|
item$ = paris.getItemById(Todo, 1);
|
|
});
|
|
|
|
it('should call Repository.getItemById with correct default params', () => {
|
|
expect(repo.getItemById).toHaveBeenCalledWith(1, defaultDataOptions, undefined);
|
|
});
|
|
|
|
it('should call Http.request with correct default params', () => {
|
|
expect(paris.dataStore.httpService.request).toHaveBeenCalledWith(
|
|
'GET',
|
|
'/todo/1',
|
|
{"customHeaders": {"keyForRegularTodoItem": "valueForRegularTodoItem"}},
|
|
{ timeout: 20000 }
|
|
);
|
|
});
|
|
|
|
it('should call Repository.getItemById with correct params', () => {
|
|
paris.getItemById(Todo, 1, null, { test: 1 });
|
|
expect(repo.getItemById).toHaveBeenCalledWith(1, defaultDataOptions, { test: 1 });
|
|
});
|
|
|
|
it('should call Http.request with correct params', () => {
|
|
paris.getItemById(Todo, 1, null, { test: 1});
|
|
expect(paris.dataStore.httpService.request).toHaveBeenCalledWith(
|
|
'GET',
|
|
'/todo/1',
|
|
{ customHeaders: {'keyForRegularTodoItem': 'valueForRegularTodoItem'}, params: { test: 1 }},
|
|
{ timeout: 20000 }
|
|
);
|
|
});
|
|
|
|
it('should return an Observable from paris.getItemById', () => {
|
|
expect(item$).toBeInstanceOf(Observable);
|
|
});
|
|
|
|
it.skip('should throw error if getItem is not supported', () => { });
|
|
|
|
it('should call datastore.request', () => {
|
|
expect(paris.dataStore.request).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('getModelBaseConfig', () => {
|
|
beforeEach(() => {
|
|
paris = new Paris();
|
|
});
|
|
|
|
it('should return the entityConfig for Todo', () => {
|
|
expect(paris.getModelBaseConfig(Todo)).toBe((<DataEntityType>Todo).entityConfig);
|
|
});
|
|
|
|
it('should return the valueObjectConfig for a ValueObject', () => {
|
|
expect(paris.getModelBaseConfig(Tag)).toBe((<DataEntityType>Tag).valueObjectConfig);
|
|
});
|
|
});
|
|
|
|
describe('query', () => {
|
|
let repo: Repository<Todo>;
|
|
|
|
|
|
const next = 'https://localhost:666/api/todo?p=2';
|
|
|
|
const todoMockData = {
|
|
items: [
|
|
{
|
|
id: 1,
|
|
text: 'First',
|
|
type: 0
|
|
},
|
|
{
|
|
id: 2,
|
|
text: 'Second',
|
|
type: 1
|
|
},
|
|
{
|
|
id: 3,
|
|
text: 'Third',
|
|
type: 1
|
|
}
|
|
],
|
|
next: next,
|
|
count: 3
|
|
};
|
|
|
|
const typesMockData = {
|
|
count: 2,
|
|
items: [
|
|
{
|
|
id: 0,
|
|
name: 'Human'
|
|
},
|
|
{
|
|
id: 1,
|
|
name: 'Machine-Generated'
|
|
}
|
|
]
|
|
};
|
|
|
|
beforeEach(<T extends EntityModelBase<number>>() => {
|
|
// @ts-ignore
|
|
const mockDataFetch:(method: RequestMethod, endpoint: string) => Observable<DataSet<T>> = (method:string, url: string) => of<DataSet<T>>(<DataSet<T>>(/types/.test(url) ? typesMockData : todoMockData));
|
|
|
|
// @ts-ignore
|
|
DataStoreService.prototype.get = jest.fn<Observable<DataSet<T>>, [RequestMethod, string]>(mockDataFetch);
|
|
|
|
// @ts-ignore
|
|
DataStoreService.prototype.request = jest.fn<Observable<DataSet<T>>, [RequestMethod, string]>(mockDataFetch);
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
|
|
beforeEach(() => {
|
|
paris = new Paris();
|
|
repo = paris.getRepository(Todo);
|
|
jest.spyOn(repo, 'query');
|
|
});
|
|
|
|
it('should call Repository.query with correct default params', () => {
|
|
paris.query(Todo);
|
|
expect(repo.query).toHaveBeenCalledWith(undefined, defaultDataOptions);
|
|
});
|
|
|
|
it('should call Repository.query with correct params', () => {
|
|
const query: DataQuery = { where: { a: 1 } },
|
|
dataOptions: DataOptions = { allowCache: false };
|
|
paris.query(Todo, query, dataOptions);
|
|
expect(repo.query).toHaveBeenCalledWith(query, dataOptions);
|
|
});
|
|
|
|
it('should return an Observable from paris.query', () => {
|
|
expect(paris.query(Todo)).toBeInstanceOf(Observable);
|
|
});
|
|
|
|
it("should throw error if repo doesn't exist", () => {
|
|
expect(() => paris.query(<any>String)).toThrow();
|
|
});
|
|
|
|
it("should have a 'next' property in the DataSet", done => {
|
|
paris.query(Todo).subscribe((dataSet: DataSet<Todo>) => {
|
|
expect(dataSet.next).toEqual(next);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should have a submodel', done => {
|
|
paris.query(Todo).subscribe(({ items }: DataSet<Todo>) => {
|
|
expect(items[0].type).toBeInstanceOf(TodoType);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should point submodels to the same model when using loadAll', done => {
|
|
paris.query(Todo).subscribe(({ items }: DataSet<Todo>) => {
|
|
expect(items[1].type).toBe(items[2].type);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('apiCall', () => {
|
|
let jestGetApiCallCacheSpy: jest.SpyInstance<Observable<Paris>>;
|
|
let jestMakeApiCallSpy: jest.SpyInstance<Observable<any>>;
|
|
|
|
beforeEach(() => {
|
|
paris = new Paris();
|
|
|
|
jest.spyOn(paris.dataStore.httpService, 'request');
|
|
jestGetApiCallCacheSpy = jest.spyOn(paris, 'getApiCallCache' as any);
|
|
jestMakeApiCallSpy = jest.spyOn(paris, 'makeApiCall' as any);
|
|
});
|
|
|
|
describe('cache behaviour', () => {
|
|
|
|
function callTwoDifferentApiCalls() {
|
|
return forkJoin(paris.apiCall(CreateTodoListApiCall), paris.apiCall(UpdateTodoApiCall));
|
|
}
|
|
|
|
const testObj = {};
|
|
|
|
beforeEach(done => {
|
|
jestMakeApiCallSpy.mockReturnValue(of(testObj));
|
|
callTwoDifferentApiCalls().subscribe(_ => done());
|
|
});
|
|
|
|
it('should retreive data from cache', done => {
|
|
callTwoDifferentApiCalls().subscribe(val => {
|
|
expect(val[0]).toBe(testObj);
|
|
expect(jestMakeApiCallSpy).toHaveBeenCalledTimes(2);//expecting no additional calls to 'makeApiCall'
|
|
done();
|
|
})
|
|
});
|
|
|
|
it('should clear a specific ApiCall cache', done => {
|
|
paris.clearApiCallCache(CreateTodoListApiCall);
|
|
callTwoDifferentApiCalls().subscribe(_ => {
|
|
expect(jestMakeApiCallSpy).toHaveBeenCalledTimes(3);//expecting exactly one additional call to 'makeApiCall'
|
|
done();
|
|
})
|
|
});
|
|
|
|
it('should clear all ApiCalls cache', done => {
|
|
paris.clearApiCallCache();
|
|
callTwoDifferentApiCalls().subscribe(_ => {
|
|
expect(jestMakeApiCallSpy).toHaveBeenCalledTimes(4);//expecting exactly two additional calls to 'makeApiCall'
|
|
done();
|
|
})
|
|
});
|
|
});
|
|
|
|
it('should get data from cache if available and configured to', () => {
|
|
jestGetApiCallCacheSpy.mockRestore();
|
|
const fakeCache = { get: jest.fn(() => of(null)) };
|
|
paris['getApiCallCache'] = jest.fn(() => fakeCache) as any;
|
|
|
|
paris.apiCall(CreateTodoListApiCall);
|
|
expect((<any>paris).makeApiCall).not.toHaveBeenCalled();
|
|
expect(paris.dataStore.httpService.request).not.toHaveBeenCalled();
|
|
expect(fakeCache.get).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not get data from cache if allowCache is false', () => {
|
|
paris.apiCall(CreateTodoListApiCall, undefined, { allowCache: false });
|
|
expect((<any>paris).getApiCallCache).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should be able to serialize complex objects with circular dependencies', done => {
|
|
jestMakeApiCallSpy.mockReturnValue(of(new Paris));
|
|
|
|
|
|
const todoItem = paris.createItem(Todo, {
|
|
id: 1,
|
|
text: 'myTodo',
|
|
time: new Date(),
|
|
tags: [],
|
|
status: { name: 'Open' },
|
|
});
|
|
|
|
todoItem
|
|
.pipe(mergeMap(todoItem => paris.apiCall(UpdateTodoApiCall, todoItem)))
|
|
.subscribe(() => {
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should always add newer data to cache if cache exists', () => { });
|
|
|
|
it('should not cache null/undefined values', () => { });
|
|
|
|
it('should call makeApiCall with correct params', () => { });
|
|
|
|
it('should call makeApiCall with correct default params', () => { });
|
|
|
|
it('should emit from error$ if encountered an error', () => { });
|
|
|
|
it('should call modelArray if repo exists data is array', () => { });
|
|
|
|
it('should call createItem if repo exists and data is not an array', () => { });
|
|
|
|
it("should call DataTransformersService.parse if repo doesn't exist and data type is defined", () => { });
|
|
|
|
it('should call parse if defined', () => { });
|
|
|
|
it('should return an Observable', () => { });
|
|
|
|
it('should throw an error if no endpoint is configured', () => { });
|
|
|
|
it('should throw an error if no endpoint is configured', () => { });
|
|
|
|
it.skip('should call datastore.request', () => {
|
|
expect(paris.dataStore.request).toHaveBeenCalled();
|
|
});
|
|
|
|
|
|
it('should call makeApiCall with the right custom headers which are given by a callback', () => {
|
|
paris.apiCall(CreateTodoListApiCall, "test", { allowCache: false });
|
|
expect((<any>paris).makeApiCall).toHaveBeenCalledWith(
|
|
{"cache": true, "customHeaders": jasmine.any(Function), "endpoint": "create_new_list", "method": "POST", "name": "Create a new Todo list"},
|
|
'POST',
|
|
{"customHeaders": {"testHeader": "testValue"}, "data": "test"},
|
|
undefined,
|
|
null
|
|
);
|
|
});
|
|
|
|
it('should call makeApiCall with the right custom headers which are given directly', () => {
|
|
const createToDoListApiCall = Object.assign({}, CreateTodoListApiCall, {config: Object.assign({}, (<any>CreateTodoListApiCall).config, {'customHeaders': {'directTestHeader': 'directTestValue'}})});
|
|
paris.apiCall(createToDoListApiCall, undefined, { allowCache: false });
|
|
expect((<any>paris).makeApiCall).toHaveBeenCalled();
|
|
expect((<any>paris).makeApiCall).toHaveBeenCalledWith(
|
|
{"cache": true, "customHeaders": {"directTestHeader": "directTestValue"}, "endpoint": "create_new_list", "method": "POST", "name": "Create a new Todo list"},
|
|
'POST',
|
|
{"customHeaders": {"directTestHeader": "directTestValue"}},
|
|
undefined,
|
|
null
|
|
);
|
|
});
|
|
|
|
|
|
it('should call Http.request with correct default params', () => { });
|
|
|
|
it('should call Http.request with correct params', () => { });
|
|
|
|
it('should pass the timeout property as part of the options if it exists', () => {
|
|
const timeout = 123456;
|
|
const createToDoListApiCall = {
|
|
...CreateTodoListApiCall,
|
|
config: {
|
|
...(<any>CreateTodoListApiCall).config,
|
|
timeout
|
|
}
|
|
};
|
|
|
|
paris.apiCall(<any>createToDoListApiCall, undefined, { allowCache: false });
|
|
|
|
const [, , , , requestOptions] = (<any>paris).makeApiCall.mock.calls[0];
|
|
expect(requestOptions.timeout).toBe(timeout);
|
|
});
|
|
});
|
|
|
|
describe('callQuery', () => {
|
|
let jestGetApiCallCacheSpy: jest.SpyInstance<Observable<Paris>>;
|
|
let jestMakeApiCallSpy: jest.SpyInstance<Observable<any>>;
|
|
|
|
beforeEach(() => {
|
|
paris = new Paris();
|
|
|
|
jest.spyOn(paris.dataStore.httpService, 'request');
|
|
jestGetApiCallCacheSpy = jest.spyOn(paris, 'getApiCallCache' as any);
|
|
jestMakeApiCallSpy = jest.spyOn(paris, 'makeApiCall' as any);
|
|
});
|
|
|
|
it('should call makeApiCall with correct params', () => { });
|
|
|
|
it('should call makeApiCall with correct default params', () => { });
|
|
|
|
it('should call rawDataToDataSet with correct params', () => { });
|
|
|
|
it('should emit from error$ if encountered an error', () => { });
|
|
|
|
it('should return an Observable', () => { });
|
|
|
|
it.skip('should call datastore.request', () => {
|
|
expect(paris.dataStore.request).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should call Http.request with correct default params', () => { });
|
|
|
|
it('should call Http.request with correct params', () => { });
|
|
|
|
it('should pass the timeout property as part of the options if it exists', () => {
|
|
const timeout = 123456;
|
|
const createToDoListApiCall = {
|
|
...CreateTodoListApiCall,
|
|
config: {
|
|
...(<any>CreateTodoListApiCall).config,
|
|
timeout
|
|
}
|
|
};
|
|
|
|
paris.callQuery(<any>createToDoListApiCall, createToDoListApiCall.config);
|
|
|
|
const [, , , , requestOptions] = (<any>paris).makeApiCall.mock.calls[0];
|
|
expect(requestOptions.timeout).toBe(timeout);
|
|
});
|
|
});
|
|
|
|
describe('createItem', () => {
|
|
let createItem$: Observable<Todo>;
|
|
|
|
beforeEach(() => {
|
|
paris = new Paris();
|
|
createItem$ = paris.createItem<Todo>(Todo, { id: 1, text: 'test' });
|
|
});
|
|
|
|
it('should return an Observable', () => {
|
|
expect(createItem$).toBeInstanceOf(Observable);
|
|
});
|
|
|
|
it('should create a Todo item', done => {
|
|
createItem$.subscribe(todo => {
|
|
expect(todo).toBeInstanceOf(Todo);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('queryForItem', () => {
|
|
beforeEach(() => {
|
|
paris = new Paris();
|
|
});
|
|
it('should call RelationshipRepository.queryForItem with correct params', () => { });
|
|
|
|
it('should call RelationshipRepository.queryForItem with correct default params', () => { });
|
|
|
|
it("should throw error if repo doesn't exist", () => {
|
|
expect(() => paris.queryForItem(TodoList, new Todo({ id: 1 }))).toThrow();
|
|
});
|
|
|
|
it('should return an Observable', () => { });
|
|
});
|
|
|
|
describe('getRelatedItem', () => {
|
|
beforeEach(() => {
|
|
paris = new Paris();
|
|
});
|
|
it('should call RelationshipRepository.getRelatedItem with correct params', () => { });
|
|
|
|
it('should call RelationshipRepository.getRelatedItem with correct default params', () => { });
|
|
|
|
it("should throw error if the relationship repository doesn't exist", () => {
|
|
expect(() => paris.getRelatedItem(TodoList, new Todo({ id: 1 }))).toThrow();
|
|
});
|
|
|
|
it("should throw an error if the relationship doesn't support OneToMany", () => {
|
|
expect(() =>
|
|
paris.getRelatedItem(TodoListItemsRelationship, new TodoList({ id: 1 }))
|
|
).toThrow();
|
|
});
|
|
|
|
it('should return an Observable', () => {
|
|
expect(
|
|
paris.queryForItem(TodoListItemsRelationship, new TodoList({ id: 1 }))
|
|
).toBeInstanceOf(Observable);
|
|
});
|
|
});
|
|
|
|
describe('getValue', () => {
|
|
beforeEach(() => {
|
|
paris = new Paris();
|
|
});
|
|
|
|
it("should return null if repo doesn't exist", () => {
|
|
class SomeClass { }
|
|
expect(paris.getValue(SomeClass, 1)).toBe(null);
|
|
});
|
|
|
|
it("should return null if repo doesn't have defined values", () => {
|
|
expect(paris.getValue(TodoStatus, 9)).toBe(null);
|
|
});
|
|
|
|
it("should call valueId if it's a function (predicate)", () => {
|
|
expect(paris.getValue(TodoStatus, status => /done/i.test(status.name)).name).toBe(
|
|
'Done'
|
|
);
|
|
});
|
|
|
|
it('should call getValueById if valueId is not a function', () => {
|
|
expect(paris.getValue(TodoStatus, 1).name).toBe('Open');
|
|
});
|
|
|
|
it('should return the correct type', () => {
|
|
expect(paris.getValue(TodoStatus, 1)).toBeInstanceOf(TodoStatus);
|
|
});
|
|
});
|
|
|
|
describe('getRelationshipRepository', () => {
|
|
beforeEach(() => {
|
|
paris = new Paris();
|
|
});
|
|
|
|
it('should create a RelationshipRepository', () => {
|
|
expect(paris.getRelationshipRepository(TodoListItemsRelationship)).toBeDefined();
|
|
});
|
|
|
|
it('should return a RelationshipRepository', () => {
|
|
expect(paris.getRelationshipRepository(TodoListItemsRelationship)).toBeInstanceOf(
|
|
RelationshipRepository
|
|
);
|
|
});
|
|
});
|
|
});
|