Skip to content
Snippets Groups Projects
resource.service.spec.ts 16.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • OZGCloud's avatar
    OZGCloud committed
    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';
    
    import { createProblemDetail } from '../../../test//error';
    import { singleCold, singleHot } from '../../../test/marbles';
    
    import { createDummyResource } from '../../../test/resource';
    
    import { HttpError, ProblemDetail } from '../tech.model';
    
    import { LinkRelationName, ResourceServiceConfig } from './resource.model';
    
    import { ResourceRepository } from './resource.repository';
    
    import { ResourceService } from './resource.service';
    
    import {
      StateResource,
      createEmptyStateResource,
      createErrorStateResource,
      createStateResource,
    } from './resource.util';
    
    import * as ResourceUtil from './resource.util';
    
    
    describe('ResourceService', () => {
    
      let service: DummyResourceService<Resource, Resource>;
    
      let config: ResourceServiceConfig<Resource>;
    
    OZGCloud's avatar
    OZGCloud committed
    
    
    OZGCloud's avatar
    OZGCloud committed
    
    
      const configResource: Resource = createDummyResource();
      const configStateResource: StateResource<Resource> = createStateResource(configResource);
      const configStateResource$: Observable<StateResource<Resource>> = of(configStateResource);
    
    
      const dummyResource: Resource = createDummyResource();
      const dummyStateResource: StateResource<Resource> = createStateResource(dummyResource);
    
    
      const editLinkRel: string = 'dummyEditLinkRel';
    
      const getLinkRel: LinkRelationName = 'dummyGetLinkRel';
    
      const deleteLinkRel: LinkRelationName = 'dummyDeleteLinkRel';
    
    OZGCloud's avatar
    OZGCloud committed
    
      beforeEach(() => {
        config = {
    
          getLinkRel,
    
          edit: { linkRel: editLinkRel },
          delete: { linkRel: deleteLinkRel },
    
    OZGCloud's avatar
    OZGCloud committed
        };
    
    OZGCloud's avatar
    OZGCloud committed
    
    
        service = new DummyResourceService(config, useFromMock(repository));
    
    OZGCloud's avatar
    OZGCloud committed
      });
    
      it('should be created', () => {
        expect(service).toBeTruthy();
      });
    
    
      describe('get', () => {
        const stateResource: StateResource<Resource> = createStateResource(configResource);
    
        let isInvalidResourceCombinationSpy: jest.SpyInstance;
    
    
        beforeEach(() => {
          service.stateResource.next(stateResource);
    
    
          isInvalidResourceCombinationSpy = jest
            .spyOn(ResourceUtil, 'isInvalidResourceCombination')
            .mockReturnValue(true);
    
    
        it('should handle config resource changed', fakeAsync(() => {
          service.get().subscribe();
          tick();
    
    
          expect(service.handleResourceChanges).toHaveBeenCalledWith(stateResource, configResource);
    
        it('should call isInvalidResourceCombinationSpy', fakeAsync(() => {
    
          expect(isInvalidResourceCombinationSpy).toHaveBeenCalled();
    
    OZGCloud's avatar
    OZGCloud committed
    
        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) }),
          );
        });
    
        const stateResource: StateResource<Resource> = createStateResource(createDummyResource());
    
        const changedConfigResource: Resource = createDummyResource();
    
    
        describe('on different config resource', () => {
    
          beforeEach(() => {
            service.handleConfigResourceChanges = jest.fn();
    
          it('should update state resource by config resource', () => {
    
            service.configResource = createDummyResource();
    
    
            service.handleResourceChanges(stateResource, changedConfigResource);
    
            expect(service.handleConfigResourceChanges).toHaveBeenCalled();
    
        describe('on same config resource', () => {
          const stateResource: StateResource<Resource> = createStateResource(createDummyResource());
    
          beforeEach(() => {
            service.configResource = configResource;
          });
    
          it('should call shouldLoadResource', () => {
            service.shouldLoadResource = jest.fn();
    
            service.handleResourceChanges(stateResource, configResource);
    
    
            expect(service.shouldLoadResource).toHaveBeenCalledWith(stateResource, configResource);
          });
    
          it('should load resource', () => {
            service.shouldLoadResource = jest.fn().mockReturnValue(true);
            service.loadResource = jest.fn();
    
    
            service.handleResourceChanges(stateResource, configResource);
    
    
            expect(service.loadResource).toHaveBeenCalledWith(configResource);
          });
    
    
            service.loadResource = jest.fn();
            service.shouldLoadResource = jest.fn().mockReturnValue(false);
    
    
            service.handleResourceChanges(stateResource, configResource);
    
    
            expect(service.loadResource).not.toHaveBeenCalled();
          });
    
      describe('handle config resource changes', () => {
        const stateResource: StateResource<Resource> = createStateResource(createDummyResource());
    
        it('should update configresource', () => {
          service.configResource = createDummyResource();
    
          service.handleResourceChanges(stateResource, configResource);
    
          expect(service.configResource).toBe(configResource);
        });
    
    
        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();
          });
    
      });
    
      describe('update stateresource by configresource', () => {
        it('should check if should clear stateresource', () => {
          service.shouldClearStateResource = jest.fn();
    
          service.updateStateResourceByConfigResource(dummyStateResource, configResource);
    
          expect(service.shouldClearStateResource).toHaveBeenCalled();
    
        it('should clear resource if should', () => {
          service.stateResource.next(createStateResource(createDummyResource()));
    
          service.shouldClearStateResource = jest.fn().mockReturnValue(true);
    
          service.updateStateResourceByConfigResource(dummyStateResource, configResource);
    
          expect(service.stateResource.value).toEqual(createEmptyStateResource());
        });
    
    
        describe('on NOT clearing stateresource', () => {
          beforeEach(() => {
            service.shouldClearStateResource = jest.fn().mockReturnValue(false);
          });
    
          it('should load resource if link exists', () => {
            service.hasGetLink = jest.fn();
    
            service.updateStateResourceByConfigResource(dummyStateResource, configResource);
    
            expect(service.hasGetLink).toHaveBeenCalledWith(configResource);
          });
    
        });
      });
    
      describe('should clear stateresource', () => {
    
        describe('on existing stateresource', () => {
          beforeEach(() => {
            service.stateResource.next(dummyStateResource);
          });
    
          it('should return true if configresource is null', () => {
            const shouldClear: boolean = service.shouldClearStateResource(dummyStateResource, null);
    
          it('should return true if configresource has no get link', () => {
            const shouldClear: boolean = service.shouldClearStateResource(
              dummyStateResource,
              createDummyResource(),
            );
    
            expect(shouldClear).toBeTruthy();
          });
    
        describe('on empty stateresource', () => {
          it('should return false', () => {
            const shouldClear: boolean = service.shouldClearStateResource(
              createEmptyStateResource(),
              null,
            );
    
            expect(shouldClear).toBeFalsy();
          });
    
          it('should return false if configresource has no get link', () => {
            const shouldClear: boolean = service.shouldClearStateResource(
              createEmptyStateResource(),
              createDummyResource(),
            );
    
        });
      });
    
      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();
        });
      });
    
      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;
    
          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);
        });
      });
    
    
        it('should do load resource', () => {
          service.doLoadResource = jest.fn();
          const resourceUri: ResourceUri = faker.internet.url();
    
    
    
          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();
    
        const resourceWithEditLinkRel: Resource = createDummyResource([editLinkRel]);
    
    
        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();
    
    
          expect(doSaveMock).toHaveBeenCalledWith(resourceWithEditLinkRel, dummyToSave);
    
        }));
    
        it('should return saved object', () => {
    
          service.stateResource.next(createStateResource(resourceWithEditLinkRel));
    
          service.doSave = jest.fn().mockReturnValue(singleHot(loadedResource));
    
          const saved: Observable<StateResource<Resource | HttpError>> = service.save(dummyToSave);
    
    OZGCloud's avatar
    OZGCloud committed
          expect(saved).toBeObservable(singleCold(createStateResource(loadedResource)));
    
    
        it('should call handleError', () => {
    
          service.stateResource.next(createStateResource(createDummyResource([config.edit.linkRel])));
    
          const errorResponse: ProblemDetail = createProblemDetail();
    
          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));
    
          service.doSave = jest.fn().mockReturnValue(of(loadedResource));
    
    
          service.save(dummyToSave).subscribe();
          tick();
    
          expect(service.stateResource.value).toEqual(createStateResource(loadedResource));
        }));
    
      });
    
      describe('handleError', () => {
    
        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(() => {
    
        it('should set reload true on statresource', () => {
          service.stateResource.next(createStateResource(createDummyResource()));
    
    
          service.refresh();
    
          expect(service.stateResource.value.reload).toBeTruthy();
    
      describe('exist resource', () => {
        it('should return true on existing resource', () => {
          service.stateResource.next(createStateResource(createDummyResource()));
    
          const existResource$: Observable<boolean> = service.existResource();
    
          expect(existResource$).toBeObservable(singleCold(true));
    
        it('should return false on null resource', () => {
          service.stateResource.next(createEmptyStateResource());
    
          const existResource$: Observable<boolean> = service.existResource();
    
          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));
        });
      });
    
    OZGCloud's avatar
    OZGCloud committed
    });
    
    
    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);
      }
    }