diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 6550435aa3..5ff9a0336e 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -54,7 +54,7 @@ import { UUIDService } from './shared/uuid.service'; import { AuthenticatedGuard } from './auth/authenticated.guard'; import { AuthRequestService } from './auth/auth-request.service'; import { AuthResponseParsingService } from './auth/auth-response-parsing.service'; -import { HTTP_INTERCEPTORS } from '@angular/common/http'; +import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http'; import { AuthInterceptor } from './auth/auth.interceptor'; import { HALEndpointService } from './shared/hal-endpoint.service'; import { FacetValueResponseParsingService } from './data/facet-value-response-parsing.service'; @@ -87,6 +87,21 @@ import { MyDSpaceResponseParsingService } from './data/mydspace-response-parsing import { ClaimedTaskDataService } from './tasks/claimed-task-data.service'; import { PoolTaskDataService } from './tasks/pool-task-data.service'; import { TaskResponseParsingService } from './tasks/task-response-parsing.service'; +import { + MOCK_RESPONSE_MAP, + MockResponseMap, + mockResponseMap +} from './dspace-rest-v2/mocks/mock-response-map'; +import { EndpointMockingRestService } from './dspace-rest-v2/endpoint-mocking-rest.service'; +import { ENV_CONFIG, GLOBAL_CONFIG, GlobalConfig } from '../../config'; + +export const restServiceFactory = (cfg: GlobalConfig, mocks: MockResponseMap, http: HttpClient) => { + if (ENV_CONFIG.production) { + return new DSpaceRESTv2Service(http); + } else { + return new EndpointMockingRestService(cfg, mocks, http); + } +}; const IMPORTS = [ CommonModule, @@ -110,6 +125,8 @@ const PROVIDERS = [ CommunityDataService, CollectionDataService, DSOResponseParsingService, + { provide: MOCK_RESPONSE_MAP, useValue: mockResponseMap }, + { provide: DSpaceRESTv2Service, useFactory: restServiceFactory, deps: [GLOBAL_CONFIG, MOCK_RESPONSE_MAP, HttpClient]}, DSpaceRESTv2Service, DynamicFormLayoutService, DynamicFormService, diff --git a/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts b/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts index 290f4be8a2..cf9b1067c1 100644 --- a/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts +++ b/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts @@ -26,7 +26,7 @@ export interface HttpOptions { @Injectable() export class DSpaceRESTv2Service { - constructor(private http: HttpClient) { + constructor(protected http: HttpClient) { } diff --git a/src/app/core/dspace-rest-v2/endpoint-mocking-rest.service.spec.ts b/src/app/core/dspace-rest-v2/endpoint-mocking-rest.service.spec.ts new file mode 100644 index 0000000000..a53762e8ce --- /dev/null +++ b/src/app/core/dspace-rest-v2/endpoint-mocking-rest.service.spec.ts @@ -0,0 +1,75 @@ +import { HttpHeaders, HttpResponse } from '@angular/common/http'; +import { of as observableOf } from 'rxjs'; +import { GlobalConfig } from '../../../config/global-config.interface'; +import { RestRequestMethod } from '../data/rest-request-method'; +import { EndpointMockingRestService } from './endpoint-mocking-rest.service'; +import { MockResponseMap } from './mocks/mock-response-map'; + +describe('EndpointMockingRestService', () => { + let service: EndpointMockingRestService; + + const serverHttpResponse: HttpResponse = { + body: { bar: false }, + headers: new HttpHeaders(), + statusText: '200' + } as HttpResponse; + + const mockResponseMap: MockResponseMap = new Map([ + [ '/foo', { bar: true } ] + ]); + + beforeEach(() => { + const EnvConfig = { + rest: { + nameSpace: '/api' + } + } as GlobalConfig; + + const httpStub = jasmine.createSpyObj('http', { + get: observableOf(serverHttpResponse), + request: observableOf(serverHttpResponse) + }); + + service = new EndpointMockingRestService(EnvConfig, mockResponseMap, httpStub); + }); + + describe('get', () => { + describe('when the URL is mocked', () => { + it('should return the mock data', (done) => { + service.get('https://rest.com/api/foo').subscribe((response) => { + expect(response.payload).toEqual({ bar: true }); + done(); + }); + }); + }); + + describe('when the URL isn\'t mocked', () => { + it('should return the server data', (done) => { + service.get('https://rest.com/api').subscribe((response) => { + expect(response.payload).toEqual({ bar: false }); + done(); + }); + }); + }); + }); + + describe('request', () => { + describe('when the URL is mocked', () => { + it('should return the mock data', (done) => { + service.request(RestRequestMethod.GET, 'https://rest.com/api/foo').subscribe((response) => { + expect(response.payload).toEqual({ bar: true }); + done(); + }); + }); + }); + + describe('when the URL isn\'t mocked', () => { + it('should return the server data', (done) => { + service.request(RestRequestMethod.GET, 'https://rest.com/api').subscribe((response) => { + expect(response.payload).toEqual({ bar: false }); + done(); + }); + }); + }); + }); +}); diff --git a/src/app/core/dspace-rest-v2/endpoint-mocking-rest.service.ts b/src/app/core/dspace-rest-v2/endpoint-mocking-rest.service.ts new file mode 100644 index 0000000000..91ce479f40 --- /dev/null +++ b/src/app/core/dspace-rest-v2/endpoint-mocking-rest.service.ts @@ -0,0 +1,108 @@ +import { HttpClient, HttpHeaders } from '@angular/common/http' +import { Inject, Injectable } from '@angular/core'; +import { Observable, of as observableOf } from 'rxjs'; +import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; +import { isEmpty } from '../../shared/empty.util'; +import { RestRequestMethod } from '../data/rest-request-method'; + +import { DSpaceRESTV2Response } from './dspace-rest-v2-response.model'; +import { DSpaceRESTv2Service, HttpOptions } from './dspace-rest-v2.service'; +import { MOCK_RESPONSE_MAP, MockResponseMap } from './mocks/mock-response-map'; + +/** + * Service to access DSpace's REST API. + * + * If a URL is found in this.mockResponseMap, it returns the mock response instead + */ +@Injectable() +export class EndpointMockingRestService extends DSpaceRESTv2Service { + + constructor( + @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, + @Inject(MOCK_RESPONSE_MAP) protected mockResponseMap: MockResponseMap, + protected http: HttpClient + ) { + super(http); + } + + /** + * Performs a request to the REST API with the `get` http method. + * + * If the URL is found in this.mockResponseMap, + * it returns the mock response instead + * + * @param absoluteURL + * A URL + * @return Observable + * An Observable containing the response + */ + get(absoluteURL: string): Observable { + const mockData = this.getMockData(absoluteURL); + if (isEmpty(mockData)) { + return super.get(absoluteURL); + } else { + return this.toMockResponse$(mockData); + } + } + + /** + * Performs a request to the REST API. + * + * If the URL is found in this.mockResponseMap, + * it returns the mock response instead + * + * @param method + * the HTTP method for the request + * @param url + * the URL for the request + * @param body + * an optional body for the request + * @return Observable + * An Observable containing the response from the server + */ + request(method: RestRequestMethod, url: string, body?: any, options?: HttpOptions): Observable { + const mockData = this.getMockData(url); + if (isEmpty(mockData)) { + return super.request(method, url, body, options); + } else { + return this.toMockResponse$(mockData); + } + } + + /** + * Turn the mock object in to an Observable + * + * @param mockData + * the mock response + * @return + * an Observable containing the mock response + */ + private toMockResponse$(mockData: any): Observable { + return observableOf({ + payload: mockData, + headers: new HttpHeaders(), + statusCode: 200, + statusText: 'OK' + }); + } + + /** + * Get the mock response associated with this URL from this.mockResponseMap + * + * @param urlStr + * the URL to fetch a mock reponse for + * @return any + * the mock response if there is one, undefined otherwise + */ + private getMockData(urlStr: string): any { + const url = new URL(urlStr); + const key = url.pathname.slice(this.EnvConfig.rest.nameSpace.length); + if (this.mockResponseMap.has(key)) { + // parse and stringify to clone the object to ensure that any changes made + // to it afterwards don't affect future calls + return JSON.parse(JSON.stringify(this.mockResponseMap.get(key))); + } else { + return undefined; + } + } +} diff --git a/src/app/core/dspace-rest-v2/mocks/mock-response-map.ts b/src/app/core/dspace-rest-v2/mocks/mock-response-map.ts new file mode 100644 index 0000000000..9a1bf4239b --- /dev/null +++ b/src/app/core/dspace-rest-v2/mocks/mock-response-map.ts @@ -0,0 +1,10 @@ +import { InjectionToken } from '@angular/core'; +import mockSuggestResponse from './mock-suggest-response.json'; + +export class MockResponseMap extends Map {}; + +export const MOCK_RESPONSE_MAP: InjectionToken = new InjectionToken('mockResponseMap'); + +export const mockResponseMap: MockResponseMap = new Map([ + [ '/discover/suggestions', mockSuggestResponse ] +]); diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js index 7fb4656a15..723e883462 100644 --- a/webpack/webpack.common.js +++ b/webpack/webpack.common.js @@ -91,6 +91,10 @@ module.exports = { { test: /\.html$/, loader: 'raw-loader' + }, + { + test: /\.json$/, + loader: 'json-loader' } ] },