Newer
Older
import { Mock, mock, useFromMock } from '@alfa-client/test-utils';
import { HttpErrorResponse } from '@angular/common/http';
import { fakeAsync, tick } from '@angular/core/testing';
import { faker } from '@faker-js/faker';
import { Resource, ResourceUri, getUrl } from '@ngxp/rest';
import { cold } from 'jest-marbles';
import { Observable, lastValueFrom, of, throwError } from 'rxjs';

OZGCloud
committed
import { createProblemDetail } from '../../../test//error';
import { singleCold, singleHot } from '../../../test/marbles';
import { createDummyResource } from '../../../test/resource';
import { HttpError, ProblemDetail } from '../tech.model';

OZGCloud
committed
import { LinkRelationName, ResourceServiceConfig } from './resource.model';
import { ResourceRepository } from './resource.repository';

OZGCloud
committed
import { ResourceService } from './resource.service';
import {
StateResource,
createEmptyStateResource,
createErrorStateResource,
createStateResource,
} from './resource.util';
import * as ResourceUtil from './resource.util';
let service: DummyResourceService<Resource, Resource>;

OZGCloud
committed
let repository: Mock<ResourceRepository>;
const configResource: Resource = createDummyResource();
const configStateResource: StateResource<Resource> = createStateResource(configResource);
const configStateResource$: Observable<StateResource<Resource>> = of(configStateResource);

OZGCloud
committed
const dummyResource: Resource = createDummyResource();
const dummyStateResource: StateResource<Resource> = createStateResource(dummyResource);
const editLinkRel: string = 'dummyEditLinkRel';
const getLinkRel: LinkRelationName = 'dummyGetLinkRel';

OZGCloud
committed
const deleteLinkRel: LinkRelationName = 'dummyDeleteLinkRel';
resource: configStateResource$,

OZGCloud
committed
edit: { linkRel: editLinkRel },
delete: { linkRel: deleteLinkRel },

OZGCloud
committed
repository = mock(ResourceRepository);
service = new DummyResourceService(config, useFromMock(repository));
});
it('should be created', () => {
expect(service).toBeTruthy();
});
describe('get', () => {
const stateResource: StateResource<Resource> = createStateResource(configResource);

OZGCloud
committed
let isInvalidResourceCombinationSpy: jest.SpyInstance;
beforeEach(() => {
service.stateResource.next(stateResource);

OZGCloud
committed
service.handleResourceChanges = jest.fn();

OZGCloud
committed
isInvalidResourceCombinationSpy = jest
.spyOn(ResourceUtil, 'isInvalidResourceCombination')
.mockReturnValue(true);

OZGCloud
committed
});
it('should handle config resource changed', fakeAsync(() => {
service.get().subscribe();
tick();

OZGCloud
committed
expect(service.handleResourceChanges).toHaveBeenCalledWith(stateResource, configResource);
}));

OZGCloud
committed
it('should call isInvalidResourceCombinationSpy', fakeAsync(() => {
service.get().subscribe();
tick();

OZGCloud
committed
expect(isInvalidResourceCombinationSpy).toHaveBeenCalled();
it('should return initial value', () => {
service.stateResource.asObservable = jest
.fn()
.mockReturnValue(singleHot(stateResource, '-a'));
const apiRootStateResource$: Observable<StateResource<Resource>> = service.get();
expect(apiRootStateResource$).toBeObservable(
cold('a', { a: createEmptyStateResource(true) }),
);
});

OZGCloud
committed
});

OZGCloud
committed
describe('handle resource changes', () => {
const stateResource: StateResource<Resource> = createStateResource(createDummyResource());
const changedConfigResource: Resource = createDummyResource();
describe('on different config resource', () => {

OZGCloud
committed
beforeEach(() => {
service.handleConfigResourceChanges = jest.fn();

OZGCloud
committed
it('should update state resource by config resource', () => {
service.configResource = createDummyResource();

OZGCloud
committed
service.handleResourceChanges(stateResource, changedConfigResource);

OZGCloud
committed

OZGCloud
committed
expect(service.handleConfigResourceChanges).toHaveBeenCalled();

OZGCloud
committed
});
describe('on same config resource', () => {
const stateResource: StateResource<Resource> = createStateResource(createDummyResource());

OZGCloud
committed
beforeEach(() => {
service.configResource = configResource;
});

OZGCloud
committed
it('should call shouldLoadResource', () => {
service.shouldLoadResource = jest.fn();

OZGCloud
committed

OZGCloud
committed
service.handleResourceChanges(stateResource, configResource);
expect(service.shouldLoadResource).toHaveBeenCalledWith(stateResource, configResource);
});
it('should load resource', () => {
service.shouldLoadResource = jest.fn().mockReturnValue(true);
service.loadResource = jest.fn();

OZGCloud
committed
service.handleResourceChanges(stateResource, configResource);
expect(service.loadResource).toHaveBeenCalledWith(configResource);
});

OZGCloud
committed
it('should NOT load resource', () => {
service.loadResource = jest.fn();
service.shouldLoadResource = jest.fn().mockReturnValue(false);

OZGCloud
committed
service.handleResourceChanges(stateResource, configResource);
expect(service.loadResource).not.toHaveBeenCalled();
});
});
});

OZGCloud
committed

OZGCloud
committed
describe('handle config resource changes', () => {
const stateResource: StateResource<Resource> = createStateResource(createDummyResource());

OZGCloud
committed
it('should update configresource', () => {
service.configResource = createDummyResource();
service.handleResourceChanges(stateResource, configResource);
expect(service.configResource).toBe(configResource);
});

OZGCloud
committed
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
describe('on stable stateresource', () => {
beforeEach(() => {
jest.spyOn(ResourceUtil, 'isStateResoureStable').mockReturnValue(true);
service.updateStateResourceByConfigResource = jest.fn();
});
it('should update stateresource by configresource', () => {
service.handleResourceChanges(stateResource, configResource);
expect(service.updateStateResourceByConfigResource).toHaveBeenCalledWith(
stateResource,
configResource,
);
});
});
describe('on instable stateresource', () => {
beforeEach(() => {
jest.spyOn(ResourceUtil, 'isStateResoureStable').mockReturnValue(false);
});
it('should NOT update stateresource by configresource', () => {
service.updateStateResourceByConfigResource = jest.fn();
service.handleResourceChanges(stateResource, configResource);
expect(service.updateStateResourceByConfigResource).not.toHaveBeenCalled();
});

OZGCloud
committed
});
describe('update stateresource by configresource', () => {
it('should check if should clear stateresource', () => {
service.shouldClearStateResource = jest.fn();
service.updateStateResourceByConfigResource(dummyStateResource, configResource);

OZGCloud
committed
expect(service.shouldClearStateResource).toHaveBeenCalled();

OZGCloud
committed

OZGCloud
committed
it('should clear resource if should', () => {
service.stateResource.next(createStateResource(createDummyResource()));
service.shouldClearStateResource = jest.fn().mockReturnValue(true);

OZGCloud
committed

OZGCloud
committed
service.updateStateResourceByConfigResource(dummyStateResource, configResource);

OZGCloud
committed
expect(service.stateResource.value).toEqual(createEmptyStateResource());
});

OZGCloud
committed
describe('on NOT clearing stateresource', () => {
beforeEach(() => {
service.shouldClearStateResource = jest.fn().mockReturnValue(false);
});
it('should load resource if link exists', () => {
service.hasGetLink = jest.fn();

OZGCloud
committed

OZGCloud
committed
service.updateStateResourceByConfigResource(dummyStateResource, configResource);

OZGCloud
committed

OZGCloud
committed
expect(service.hasGetLink).toHaveBeenCalledWith(configResource);
});
});
});
describe('should clear stateresource', () => {

OZGCloud
committed
describe('on existing stateresource', () => {
beforeEach(() => {
service.stateResource.next(dummyStateResource);
});

OZGCloud
committed
it('should return true if configresource is null', () => {
const shouldClear: boolean = service.shouldClearStateResource(dummyStateResource, null);

OZGCloud
committed
expect(shouldClear).toBeTruthy();
});

OZGCloud
committed
it('should return true if configresource has no get link', () => {
const shouldClear: boolean = service.shouldClearStateResource(
dummyStateResource,
createDummyResource(),
);
expect(shouldClear).toBeTruthy();
});
});

OZGCloud
committed
describe('on empty stateresource', () => {
it('should return false', () => {
const shouldClear: boolean = service.shouldClearStateResource(
createEmptyStateResource(),
null,
);

OZGCloud
committed
expect(shouldClear).toBeFalsy();
});
it('should return false if configresource has no get link', () => {
const shouldClear: boolean = service.shouldClearStateResource(
createEmptyStateResource(),
createDummyResource(),
);

OZGCloud
committed
expect(shouldClear).toBeFalsy();
});
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
});
});
describe('should load resource', () => {
const resource: Resource = createDummyResource();
const stateResource: StateResource<Resource> = createStateResource(resource);
let isLoadingRequiredSpy: jest.SpyInstance<boolean>;
beforeEach(() => {
isLoadingRequiredSpy = jest.spyOn(ResourceUtil, 'isLoadingRequired');
});
it('should return true on existing configresource and loading is required', () => {
isLoadingRequiredSpy.mockReturnValue(true);
const shouldLoad: boolean = service.shouldLoadResource(stateResource, configResource);
expect(shouldLoad).toBeTruthy();
});
it('should call isLoadingRequired', () => {
service.shouldLoadResource(stateResource, configResource);
expect(isLoadingRequiredSpy).toBeCalledWith(stateResource);
});
it('should return false if configresource exists but loading is NOT required', () => {
isLoadingRequiredSpy.mockReturnValue(false);
const shouldLoad: boolean = service.shouldLoadResource(stateResource, configResource);
expect(shouldLoad).toBeFalsy();
});
});

OZGCloud
committed
describe('load resource', () => {
const configResourceWithGetLinkRel: Resource = createDummyResource([getLinkRel]);
it('should call do load resource', () => {
service.doLoadResource = jest.fn();
service.loadResource(configResourceWithGetLinkRel);
expect(service.doLoadResource).toHaveBeenCalledWith(
getUrl(configResourceWithGetLinkRel, config.getLinkRel),
);
});
});
describe('set state resource loading', () => {
it('should set loading true', () => {
service.stateResource.next(createStateResource(createDummyResource()));
service.setStateResourceLoading();
expect(service.stateResource.value.loading).toBeTruthy();
});
it('should set reload false', () => {
service.stateResource.next({ ...createStateResource(createDummyResource()), reload: true });
service.setStateResourceLoading();
expect(service.stateResource.value.reload).toBeFalsy();
});
});
describe('do load resource', () => {
let resourceUri: ResourceUri;
let loadedResource: Resource;
beforeEach(() => {
service.setStateResourceLoading = jest.fn();
resourceUri = faker.internet.url();
loadedResource = createDummyResource();
repository.getResource.mockReturnValue(of(loadedResource));
});
it('should set state resource to loading', () => {
service.doLoadResource(resourceUri);
expect(service.setStateResourceLoading).toHaveBeenCalled();
});
it('should call repository', () => {
service.doLoadResource(resourceUri);
expect(repository.getResource).toHaveBeenCalledWith(resourceUri);
});
it('should update stateresource', () => {
service.updateStateResource = jest.fn();
service.doLoadResource(resourceUri);
expect(service.updateStateResource).toHaveBeenCalledWith(loadedResource);
});
});

OZGCloud
committed
describe('loadByResourceUri', () => {
it('should do load resource', () => {
service.doLoadResource = jest.fn();
const resourceUri: ResourceUri = faker.internet.url();

OZGCloud
committed
service.loadByResourceUri(resourceUri);
expect(service.doLoadResource).toHaveBeenCalledWith(resourceUri);
});
});
describe('update stateresource', () => {
const resourceToBeSet: Resource = createDummyResource();
it('should set resource as stateresource', () => {
service.stateResource.next(createEmptyStateResource());
service.updateStateResource(resourceToBeSet);
expect(service.stateResource.value).toEqual(createStateResource(resourceToBeSet));
});
});
describe('save', () => {
const dummyToSave: unknown = {};
const loadedResource: Resource = createDummyResource();

OZGCloud
committed
const resourceWithEditLinkRel: Resource = createDummyResource([editLinkRel]);

OZGCloud
committed
it('should do save', fakeAsync(() => {
const stateResource: StateResource<Resource> = createStateResource(resourceWithEditLinkRel);
service.stateResource.next(stateResource);
const doSaveMock: jest.Mock = (service.doSave = jest.fn()).mockReturnValue(
of(loadedResource),
);
service.save(dummyToSave).subscribe();
tick();

OZGCloud
committed
expect(doSaveMock).toHaveBeenCalledWith(resourceWithEditLinkRel, dummyToSave);
}));
it('should return saved object', () => {
service.stateResource.next(createStateResource(resourceWithEditLinkRel));

OZGCloud
committed
service.doSave = jest.fn().mockReturnValue(singleHot(loadedResource));
const saved: Observable<StateResource<Resource | HttpError>> = service.save(dummyToSave);
expect(saved).toBeObservable(singleCold(createStateResource(loadedResource)));
it('should call handleError', () => {

OZGCloud
committed
service.stateResource.next(createStateResource(createDummyResource([config.edit.linkRel])));
const errorResponse: ProblemDetail = createProblemDetail();

OZGCloud
committed
service.doSave = jest.fn().mockReturnValue(throwError(() => errorResponse));
service.handleError = jest.fn();
service.save(<any>{}).subscribe();
expect(service.handleError).toHaveBeenCalledWith(errorResponse);
});
it('should update state resource subject', fakeAsync(() => {
service.stateResource.next(createStateResource(resourceWithEditLinkRel));

OZGCloud
committed
service.doSave = jest.fn().mockReturnValue(of(loadedResource));
service.save(dummyToSave).subscribe();
tick();
expect(service.stateResource.value).toEqual(createStateResource(loadedResource));
}));
});
describe('handleError', () => {

OZGCloud
committed
it('should return error stateresource on problem unprocessable entity', (done: jest.DoneCallback) => {
const error: ProblemDetail = createProblemDetail();
service
.handleError(<HttpErrorResponse>(<any>error))
.subscribe((responseError: StateResource<HttpError>) => {
expect(responseError).toEqual(createErrorStateResource(error));
done();
});
});
it('should rethrow error', () => {
const error: HttpErrorResponse = <HttpErrorResponse>{
status: 500,
statusText: 'Internal Server Error',
};
const thrownError$: Observable<StateResource<HttpError>> = service.handleError(error);
expect.assertions(1);
expect(lastValueFrom(thrownError$)).rejects.toThrowError('Internal Server Error');
});
});
describe('refresh', () => {
beforeEach(() => {

OZGCloud
committed
service.loadResource = jest.fn();
it('should set reload true on statresource', () => {
service.stateResource.next(createStateResource(createDummyResource()));

OZGCloud
committed
expect(service.stateResource.value.reload).toBeTruthy();

OZGCloud
committed
});
});

OZGCloud
committed
describe('exist resource', () => {
it('should return true on existing resource', () => {
service.stateResource.next(createStateResource(createDummyResource()));

OZGCloud
committed
const existResource$: Observable<boolean> = service.existResource();

OZGCloud
committed
expect(existResource$).toBeObservable(singleCold(true));

OZGCloud
committed
it('should return false on null resource', () => {
service.stateResource.next(createEmptyStateResource());

OZGCloud
committed
const existResource$: Observable<boolean> = service.existResource();

OZGCloud
committed
expect(existResource$).toBeObservable(singleCold(false));
describe('select resource', () => {
it('should return state resource', () => {
service.stateResource.next(dummyStateResource);
const resource$: Observable<StateResource<Resource>> = service.selectResource();
expect(resource$).toBeObservable(singleCold(dummyStateResource));
});
});
export class DummyResourceService<B extends Resource, T extends Resource> extends ResourceService<
B,
T
> {
constructor(
protected config: ResourceServiceConfig<B>,
protected repository: ResourceRepository,
) {
super(config, repository);
}
doSave(resource: T, toSave: unknown): Observable<T> {
return of(resource);
}
}