From a0851884e6de15c70e509b4e1e2ba6b08fcc7ffa Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Fri, 18 May 2018 15:54:05 +0200 Subject: [PATCH] Added more tests --- ...e-entries-response-parsing.service.spec.ts | 146 +++++++++++++++++ .../browse-response-parsing.service.spec.ts | 2 +- src/app/core/shared/operators.spec.ts | 153 ++++++++++++++++++ src/app/core/shared/operators.ts | 2 +- src/app/shared/mocks/mock-request.service.ts | 9 +- .../mocks/mock-response-cache.service.ts | 16 +- 6 files changed, 316 insertions(+), 12 deletions(-) create mode 100644 src/app/core/data/browse-entries-response-parsing.service.spec.ts create mode 100644 src/app/core/shared/operators.spec.ts diff --git a/src/app/core/data/browse-entries-response-parsing.service.spec.ts b/src/app/core/data/browse-entries-response-parsing.service.spec.ts new file mode 100644 index 0000000000..dd04e4f2f5 --- /dev/null +++ b/src/app/core/data/browse-entries-response-parsing.service.spec.ts @@ -0,0 +1,146 @@ +import { getMockObjectCacheService } from '../../shared/mocks/mock-object-cache.service'; +import { ErrorResponse, GenericSuccessResponse } from '../cache/response-cache.models'; +import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; +import { BrowseEntriesResponseParsingService } from './browse-entries-response-parsing.service'; +import { BrowseEntriesRequest } from './request.models'; + +describe('BrowseEntriesResponseParsingService', () => { + let service: BrowseEntriesResponseParsingService; + + beforeEach(() => { + service = new BrowseEntriesResponseParsingService(undefined, getMockObjectCacheService()); + }); + + describe('parse', () => { + const request = new BrowseEntriesRequest('client/f5b4ccb8-fbb0-4548-b558-f234d9fdfad6', 'https://rest.api/discover/browses/author/entries'); + + const validResponse = { + payload: { + _embedded: { + browseEntries: [ + { + authority: null, + value: 'Arulmozhiyal, Ramaswamy', + valueLang: null, + count: 1, + type: 'browseEntry', + _links: { + items: { + href: 'https://rest.api/discover/browses/author/items?filterValue=Arulmozhiyal, Ramaswamy' + } + } + }, + { + authority: null, + value: 'Bastida-Jumilla, Ma Consuelo', + valueLang: null, + count: 1, + type: 'browseEntry', + _links: { + items: { + href: 'https://rest.api/discover/browses/author/items?filterValue=Bastida-Jumilla, Ma Consuelo' + } + } + }, + { + authority: null, + value: 'Cao, Binggang', + valueLang: null, + count: 1, + type: 'browseEntry', + _links: { + items: { + href: 'https://rest.api/discover/browses/author/items?filterValue=Cao, Binggang' + } + } + }, + { + authority: null, + value: 'Castelli, Mauro', + valueLang: null, + count: 1, + type: 'browseEntry', + _links: { + items: { + href: 'https://rest.api/discover/browses/author/items?filterValue=Castelli, Mauro' + } + } + }, + { + authority: null, + value: 'Cat, Lily', + valueLang: null, + count: 1, + type: 'browseEntry', + _links: { + items: { + href: 'https://rest.api/discover/browses/author/items?filterValue=Cat, Lily' + } + } + } + ] + }, + _links: { + first: { + href: 'https://rest.api/discover/browses/author/entries?page=0&size=5' + }, + self: { + href: 'https://rest.api/discover/browses/author/entries' + }, + next: { + href: 'https://rest.api/discover/browses/author/entries?page=1&size=5' + }, + last: { + href: 'https://rest.api/discover/browses/author/entries?page=9&size=5' + } + }, + page: { + size: 5, + totalElements: 50, + totalPages: 10, + number: 0 + } + }, + statusCode: '200' + } as DSpaceRESTV2Response; + + const invalidResponseNotAList = { + payload: { + authority: null, + value: 'Arulmozhiyal, Ramaswamy', + valueLang: null, + count: 1, + type: 'browseEntry', + _links: { + self: { + href: 'https://rest.api/discover/browses/author/entries' + }, + items: { + href: 'https://rest.api/discover/browses/author/items?filterValue=Arulmozhiyal, Ramaswamy' + } + }, + }, + statusCode: '200' + } as DSpaceRESTV2Response; + + const invalidResponseStatusCode = { + payload: {}, statusCode: '500' + } as DSpaceRESTV2Response; + + it('should return a GenericSuccessResponse if data contains a valid browse entries response', () => { + const response = service.parse(request, validResponse); + expect(response.constructor).toBe(GenericSuccessResponse); + }); + + it('should return an ErrorResponse if data contains an invalid browse entries response', () => { + const response = service.parse(request, invalidResponseNotAList); + expect(response.constructor).toBe(ErrorResponse); + }); + + it('should return an ErrorResponse if data contains a statuscode other than 200', () => { + const response = service.parse(request, invalidResponseStatusCode); + expect(response.constructor).toBe(ErrorResponse); + }); + + }); +}); diff --git a/src/app/core/data/browse-response-parsing.service.spec.ts b/src/app/core/data/browse-response-parsing.service.spec.ts index 5ba8a50d7b..b0fbb1f977 100644 --- a/src/app/core/data/browse-response-parsing.service.spec.ts +++ b/src/app/core/data/browse-response-parsing.service.spec.ts @@ -12,7 +12,7 @@ describe('BrowseResponseParsingService', () => { }); describe('parse', () => { - const validRequest = new BrowseEndpointRequest('clients/b186e8ce-e99c-4183-bc9a-42b4821bdb78', 'https://rest.api/discover/browses'); + const validRequest = new BrowseEndpointRequest('client/b186e8ce-e99c-4183-bc9a-42b4821bdb78', 'https://rest.api/discover/browses'); const validResponse = { payload: { diff --git a/src/app/core/shared/operators.spec.ts b/src/app/core/shared/operators.spec.ts new file mode 100644 index 0000000000..cb1d5bc62a --- /dev/null +++ b/src/app/core/shared/operators.spec.ts @@ -0,0 +1,153 @@ +import { cold, getTestScheduler, hot } from 'jasmine-marbles'; +import { TestScheduler } from '../../../../node_modules/rxjs'; +import { getMockRequestService } from '../../shared/mocks/mock-request.service'; +import { getMockResponseCacheService } from '../../shared/mocks/mock-response-cache.service'; +import { ResponseCacheEntry } from '../cache/response-cache.reducer'; +import { ResponseCacheService } from '../cache/response-cache.service'; +import { GetRequest, RestRequest } from '../data/request.models'; +import { RequestEntry } from '../data/request.reducer'; +import { RequestService } from '../data/request.service'; +import { + configureRequest, + filterSuccessfulResponses, getRemoteDataPayload, + getRequestFromSelflink, getResourceLinksFromResponse, + getResponseFromSelflink +} from './operators'; + + +describe('Core Module - RxJS Operators', () => { + let scheduler: TestScheduler; + let requestService: RequestService; + const testSelfLink = 'https://rest.api/'; + + const testRCEs = { + a: { response: { isSuccessful: true, resourceSelfLinks: ['a', 'b', 'c', 'd'] } }, + b: { response: { isSuccessful: false, resourceSelfLinks: ['e', 'f'] } }, + c: { response: { isSuccessful: undefined, resourceSelfLinks: ['g', 'h', 'i'] } }, + d: { response: { isSuccessful: true, resourceSelfLinks: ['j', 'k', 'l', 'm', 'n'] } }, + e: { response: { isSuccessful: 1, resourceSelfLinks: [] } } + }; + + beforeEach(() => { + scheduler = getTestScheduler(); + }); + + describe('getRequestFromSelflink', () => { + + it('should return the RequestEntry corresponding to the self link in the source', () => { + requestService = getMockRequestService(); + + const source = hot('a', { a: testSelfLink }); + const result = source.pipe(getRequestFromSelflink(requestService)); + const expected = cold('a', { a: new RequestEntry()}); + + expect(result).toBeObservable(expected) + }); + + it('should use the requestService to fetch the request by its self link', () => { + requestService = getMockRequestService(); + + const source = hot('a', { a: testSelfLink }); + scheduler.schedule(() => source.pipe(getRequestFromSelflink(requestService)).subscribe()); + scheduler.flush(); + + expect(requestService.getByHref).toHaveBeenCalledWith(testSelfLink) + }); + + it('shouldn\'t return anything if there is no request matching the self link', () => { + requestService = getMockRequestService(cold('a', { a: undefined })); + + const source = hot('a', { a: testSelfLink }); + const result = source.pipe(getRequestFromSelflink(requestService)); + const expected = cold('-'); + + expect(result).toBeObservable(expected) + }); + }); + + describe('getResponseFromSelflink', () => { + let responseCacheService: ResponseCacheService; + + beforeEach(() => { + scheduler = getTestScheduler(); + }); + + it('should return the ResponseCacheEntry corresponding to the self link in the source', () => { + responseCacheService = getMockResponseCacheService(); + + const source = hot('a', { a: testSelfLink }); + const result = source.pipe(getResponseFromSelflink(responseCacheService)); + const expected = cold('a', { a: new ResponseCacheEntry()}); + + expect(result).toBeObservable(expected) + }); + + it('should use the responseCacheService to fetch the response by the request\'s link', () => { + responseCacheService = getMockResponseCacheService(); + + const source = hot('a', { a: testSelfLink }); + scheduler.schedule(() => source.pipe(getResponseFromSelflink(responseCacheService)).subscribe()); + scheduler.flush(); + + expect(responseCacheService.get).toHaveBeenCalledWith(testSelfLink) + }); + + it('shouldn\'t return anything if there is no response matching the request\'s link', () => { + responseCacheService = getMockResponseCacheService(undefined, cold('a', { a: undefined })); + + const source = hot('a', { a: testSelfLink }); + const result = source.pipe(getResponseFromSelflink(responseCacheService)); + const expected = cold('-'); + + expect(result).toBeObservable(expected) + }); + }); + + describe('filterSuccessfulResponses', () => { + it('should only return responses for which isSuccessful === true', () => { + const source = hot('abcde', testRCEs); + const result = source.pipe(filterSuccessfulResponses()); + const expected = cold('a--d-', testRCEs); + + expect(result).toBeObservable(expected) + }); + }); + + describe('getResourceLinksFromResponse', () => { + it('should return the resourceSelfLinks for all successful responses', () => { + const source = hot('abcde', testRCEs); + const result = source.pipe(getResourceLinksFromResponse()); + const expected = cold('a--d-', { + a: testRCEs.a.response.resourceSelfLinks, + d: testRCEs.d.response.resourceSelfLinks + }); + + expect(result).toBeObservable(expected) + }); + }); + + describe('configureRequest', () => { + it('should call requestService.configure with the source request', () => { + requestService = getMockRequestService(); + const testRequest = new GetRequest('6b789e31-f026-4ff8-8993-4eb3b730c841', testSelfLink); + const source = hot('a', { a: testRequest }); + scheduler.schedule(() => source.pipe(configureRequest(requestService)).subscribe()); + scheduler.flush(); + + expect(requestService.configure).toHaveBeenCalledWith(testRequest) + }); + }); + + describe('getRemoteDataPayload', () => { + it('should return the payload of the source RemoteData', () => { + const testRD = { a: { payload: 'a' } }; + const source = hot('a', testRD); + const result = source.pipe(getRemoteDataPayload()); + const expected = cold('a', { + a: testRD.a.payload, + }); + + expect(result).toBeObservable(expected) + }); + }); +}); diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index b2a725bfa6..c0b9be3fbf 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -29,7 +29,7 @@ export const getResponseFromSelflink = (responseCache: ResponseCacheService) => export const filterSuccessfulResponses = () => (source: Observable): Observable => - source.pipe(filter((entry: ResponseCacheEntry) => entry.response.isSuccessful)); + source.pipe(filter((entry: ResponseCacheEntry) => entry.response.isSuccessful === true)); export const getResourceLinksFromResponse = () => (source: Observable): Observable => diff --git a/src/app/shared/mocks/mock-request.service.ts b/src/app/shared/mocks/mock-request.service.ts index 02d3e54282..b4d8c693e5 100644 --- a/src/app/shared/mocks/mock-request.service.ts +++ b/src/app/shared/mocks/mock-request.service.ts @@ -1,10 +1,11 @@ +import { Observable } from 'rxjs/Observable'; import { RequestService } from '../../core/data/request.service'; import { RequestEntry } from '../../core/data/request.reducer'; -export function getMockRequestService(): RequestService { +export function getMockRequestService(getByHref$: Observable = Observable.of(new RequestEntry())): RequestService { return jasmine.createSpyObj('requestService', { - configure: () => false, - generateRequestId: () => 'clients/b186e8ce-e99c-4183-bc9a-42b4821bdb78', - getByHref: (uuid: string) => new RequestEntry() + configure: false, + generateRequestId: 'clients/b186e8ce-e99c-4183-bc9a-42b4821bdb78', + getByHref: getByHref$ }); } diff --git a/src/app/shared/mocks/mock-response-cache.service.ts b/src/app/shared/mocks/mock-response-cache.service.ts index ad1457c3eb..2c21b07777 100644 --- a/src/app/shared/mocks/mock-response-cache.service.ts +++ b/src/app/shared/mocks/mock-response-cache.service.ts @@ -1,12 +1,16 @@ -import { ResponseCacheService } from '../../core/cache/response-cache.service'; +import { Observable } from 'rxjs/Observable'; import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer'; -import { RestResponse } from '../../core/cache/response-cache.models'; +import { ResponseCacheService } from '../../core/cache/response-cache.service'; -export function getMockResponseCacheService(): ResponseCacheService { +export function getMockResponseCacheService( + add$: Observable = Observable.of(new ResponseCacheEntry()), + get$: Observable = Observable.of(new ResponseCacheEntry()), + has: boolean = false +): ResponseCacheService { return jasmine.createSpyObj('ResponseCacheService', { - add: (key: string, response: RestResponse, msToLive: number) => new ResponseCacheEntry(), - get: (key: string) => new ResponseCacheEntry(), - has: (key: string) => false, + add: add$, + get: get$, + has, }); }