From 8698970ce4f848165639685733a56190d1c608f1 Mon Sep 17 00:00:00 2001 From: christophercr Date: Fri, 12 Apr 2019 10:31:10 +0200 Subject: [PATCH] feat(stark-core): allow configuring the User profile resource path used by the User Repository ISSUES CLOSED: #956 --- .../repository/http-abstract-repository.ts | 6 +- .../stark-core/src/modules/user/entities.ts | 1 + .../user-module-config.entity.intf.ts | 18 +++ .../user/repository/user.repository.spec.ts | 141 ++++++++++++++++++ .../user/repository/user.repository.ts | 21 ++- .../user/services/user.service.intf.ts | 2 +- .../src/modules/user/services/user.service.ts | 3 - .../src/modules/user/user.module.ts | 7 +- 8 files changed, 184 insertions(+), 15 deletions(-) create mode 100644 packages/stark-core/src/modules/user/entities/user-module-config.entity.intf.ts create mode 100644 packages/stark-core/src/modules/user/repository/user.repository.spec.ts diff --git a/packages/stark-core/src/modules/http/repository/http-abstract-repository.ts b/packages/stark-core/src/modules/http/repository/http-abstract-repository.ts index b4f131ca91..bf8ab8ec90 100644 --- a/packages/stark-core/src/modules/http/repository/http-abstract-repository.ts +++ b/packages/stark-core/src/modules/http/repository/http-abstract-repository.ts @@ -35,9 +35,9 @@ export abstract class AbstractStarkHttpRepository { * Class constructor * @param starkHttpService The Http Service provided by Stark. * @param logger The logging service. @link StarkLoggingService. - * @param backend The backend that this HttpRespository will target. - * @param resourcePath The resource path that will be used as default for all the requests performed by this HttpRespository. This will be replaced by the resourcePath provided (if any) in the getRequestBuilder() method. - * @param serializer The serializer that will be attached by default to every StarkHttpRequest sent by this HttpRespository to serialize/deserialize the items to be sent/received to/from the backend. + * @param backend The backend that this HttpRepository will target. + * @param resourcePath The resource path that will be used as default for all the requests performed by this HttpRepository. This will be replaced by the resourcePath provided (if any) in the getRequestBuilder() method. + * @param serializer The serializer that will be attached by default to every StarkHttpRequest sent by this HttpRepository to serialize/deserialize the items to be sent/received to/from the backend. */ public constructor( protected starkHttpService: StarkHttpService, diff --git a/packages/stark-core/src/modules/user/entities.ts b/packages/stark-core/src/modules/user/entities.ts index 9c268bb216..21e7a9034a 100644 --- a/packages/stark-core/src/modules/user/entities.ts +++ b/packages/stark-core/src/modules/user/entities.ts @@ -1,3 +1,4 @@ export * from "./entities/user.entity"; +export * from "./entities/user-module-config.entity.intf"; export * from "./entities/user-profile.entity.intf"; export * from "./entities/user-security-profile.entity.intf"; diff --git a/packages/stark-core/src/modules/user/entities/user-module-config.entity.intf.ts b/packages/stark-core/src/modules/user/entities/user-module-config.entity.intf.ts new file mode 100644 index 0000000000..8ea6fa2204 --- /dev/null +++ b/packages/stark-core/src/modules/user/entities/user-module-config.entity.intf.ts @@ -0,0 +1,18 @@ +import { InjectionToken } from "@angular/core"; + +/** + * The InjectionToken version of the config value + */ +export const STARK_USER_PROFILE_RESOURCE_PATH = new InjectionToken( + "StarkUserProfileResourcePath" +); + +/** + * Definition of the configuration object for the Stark User Module + */ +export interface StarkUserModuleConfig { + /** + * Base path to be used in the Http requests to fetch the user profile resource + */ + userProfileResourcePath: string; +} diff --git a/packages/stark-core/src/modules/user/repository/user.repository.spec.ts b/packages/stark-core/src/modules/user/repository/user.repository.spec.ts new file mode 100644 index 0000000000..b6e82942ed --- /dev/null +++ b/packages/stark-core/src/modules/user/repository/user.repository.spec.ts @@ -0,0 +1,141 @@ +import { StarkUserRepository } from "./user.repository.intf"; +import { DEFAULT_USER_PROFILE_RESOURCE_PATH, StarkUserRepositoryImpl } from "./user.repository"; +import { MockStarkHttpService } from "../../http/testing"; +import { StarkBackend, StarkBackendImpl, StarkHttpRequestType, StarkSingleItemResponseWrapper } from "../../http/entities"; +import { MockStarkLoggingService } from "../../logging/testing"; +import { StarkApplicationConfig, StarkApplicationConfigImpl } from "../../../configuration"; +import { Observer, of } from "rxjs"; +import SpyObj = jasmine.SpyObj; +import createSpyObj = jasmine.createSpyObj; + +describe("StarkUserRepository", () => { + let userRepository: StarkUserRepository; + let mockHttpService: MockStarkHttpService; + const mockLoggingService = new MockStarkLoggingService(); + let mockAppConfig: StarkApplicationConfig; + let mockObserver: SpyObj>; + const userProfileBackend: StarkBackend = { + name: "userProfile", + url: "http://localhost:5000", + authenticationType: 1, + devAuthenticationEnabled: true, + devAuthenticationRolePrefix: "" + }; + + describe("on initialization", () => { + it("should throw an error in case there are no backends defined in the STARK_APP_CONFIG", () => { + const appConfigNoBackends = new StarkApplicationConfigImpl(); + + expect(() => new StarkUserRepositoryImpl(mockHttpService, mockLoggingService, appConfigNoBackends)).toThrowError( + /backends.*map should not be empty/ + ); + }); + + it("should throw an error in case any of the backends defined in the STARK_APP_CONFIG is invalid", () => { + const invalidBackend = new StarkBackendImpl(); + const appConfigInvalidBackends = new StarkApplicationConfigImpl(); + appConfigInvalidBackends.backends.set("someBackend", invalidBackend); + + expect(() => new StarkUserRepositoryImpl(mockHttpService, mockLoggingService, appConfigInvalidBackends)).toThrowError( + /name|url|authentication type/g + ); + }); + + it("should throw an error in case there is no 'userProfile' backend defined in the STARK_APP_CONFIG", () => { + const appConfigNoUserProfileBackend = new StarkApplicationConfigImpl(); + appConfigNoUserProfileBackend.addBackend({ + name: "someBackend", + url: "http://localhost:8888", + authenticationType: 1, + devAuthenticationEnabled: true, + devAuthenticationRolePrefix: "" + }); + + expect(() => new StarkUserRepositoryImpl(mockHttpService, mockLoggingService, appConfigNoUserProfileBackend)).toThrowError( + /userProfile.*undefined/ + ); + }); + }); + + describe("getUser", () => { + beforeEach(() => { + mockHttpService = new MockStarkHttpService(); + mockAppConfig = new StarkApplicationConfigImpl(); + mockAppConfig.addBackend(userProfileBackend); + + userRepository = new StarkUserRepositoryImpl(mockHttpService, mockLoggingService, mockAppConfig); + + mockObserver = createSpyObj>("observerSpy", ["next", "error", "complete"]); + }); + + it("should trigger a HTTP GET request targeting the 'userProfile' backend from STARK_APP_CONFIG via the StarkHttpService to fetch the user", () => { + const dummySuccessResponse: StarkSingleItemResponseWrapper = { + data: "success", + starkHttpStatusCode: 200, + starkHttpHeaders: new Map() + }; + + mockHttpService.executeSingleItemRequest.and.returnValue(of(dummySuccessResponse)); + + userRepository.getUser().subscribe(mockObserver); + + expect(mockHttpService.executeCollectionRequest).not.toHaveBeenCalled(); + expect(mockHttpService.executeSingleItemRequest).toHaveBeenCalledTimes(1); + const httpRequest = mockHttpService.executeSingleItemRequest.calls.argsFor(0)[0]; + expect(httpRequest.requestType).toBe(StarkHttpRequestType.GET); + expect(httpRequest.backend).toBe(userProfileBackend); + expect(httpRequest.queryParameters).toEqual(new Map([["style", "full-details"]])); + expect(httpRequest.retryCount).toBe(5); + + expect(mockObserver.next).toHaveBeenCalledTimes(1); + expect(mockObserver.next).toHaveBeenCalledWith(dummySuccessResponse); + expect(mockObserver.error).not.toHaveBeenCalled(); + expect(mockObserver.complete).toHaveBeenCalled(); + }); + + it("should trigger the HTTP request using the resource path defined in the STARK_USER_PROFILE_RESOURCE_PATH or the default path in case it is undefined", () => { + let starkUserProfileResourcePath: string | undefined; + userRepository = new StarkUserRepositoryImpl(mockHttpService, mockLoggingService, mockAppConfig, starkUserProfileResourcePath); + + const dummySuccessResponse: StarkSingleItemResponseWrapper = { + data: "success", + starkHttpStatusCode: 200, + starkHttpHeaders: new Map() + }; + + mockHttpService.executeSingleItemRequest.and.returnValue(of(dummySuccessResponse)); + + userRepository.getUser().subscribe(mockObserver); + + expect(mockHttpService.executeCollectionRequest).not.toHaveBeenCalled(); + expect(mockHttpService.executeSingleItemRequest).toHaveBeenCalledTimes(1); + let httpRequest = mockHttpService.executeSingleItemRequest.calls.argsFor(0)[0]; + expect(httpRequest.resourcePath).toBe(DEFAULT_USER_PROFILE_RESOURCE_PATH); + + expect(mockObserver.next).toHaveBeenCalledTimes(1); + expect(mockObserver.next).toHaveBeenCalledWith(dummySuccessResponse); + expect(mockObserver.error).not.toHaveBeenCalled(); + expect(mockObserver.complete).toHaveBeenCalled(); + + mockObserver.next.calls.reset(); + mockObserver.complete.calls.reset(); + mockHttpService.executeSingleItemRequest.calls.reset(); + + starkUserProfileResourcePath = "dummy-resource-path"; + userRepository = new StarkUserRepositoryImpl(mockHttpService, mockLoggingService, mockAppConfig, starkUserProfileResourcePath); + + userRepository.getUser().subscribe(mockObserver); + + expect(mockHttpService.executeCollectionRequest).not.toHaveBeenCalled(); + expect(mockHttpService.executeSingleItemRequest).toHaveBeenCalledTimes(1); + httpRequest = mockHttpService.executeSingleItemRequest.calls.argsFor(0)[0]; + expect(httpRequest.resourcePath).toBe(starkUserProfileResourcePath); + expect(httpRequest.resourcePath).not.toBe(DEFAULT_USER_PROFILE_RESOURCE_PATH); + + expect(mockObserver.next).toHaveBeenCalledTimes(1); + expect(mockObserver.next).toHaveBeenCalledWith(dummySuccessResponse); + expect(mockObserver.error).not.toHaveBeenCalled(); + expect(mockObserver.complete).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/stark-core/src/modules/user/repository/user.repository.ts b/packages/stark-core/src/modules/user/repository/user.repository.ts index e049e6fd4c..741bc6dbc6 100644 --- a/packages/stark-core/src/modules/user/repository/user.repository.ts +++ b/packages/stark-core/src/modules/user/repository/user.repository.ts @@ -1,8 +1,8 @@ /* tslint:disable:completed-docs*/ -import { Inject, Injectable } from "@angular/core"; +import { Inject, Injectable, Optional } from "@angular/core"; import { Observable } from "rxjs"; import { StarkSerializable } from "../../../serialization"; -import { StarkUser } from "../entities"; +import { STARK_USER_PROFILE_RESOURCE_PATH, StarkUser } from "../entities"; import { StarkHttpRequest, StarkSingleItemResponseWrapper } from "../../http/entities"; import { STARK_APP_CONFIG, StarkApplicationConfig } from "../../../configuration/entities/application"; import { StarkUserRepository, starkUserRepositoryName } from "./user.repository.intf"; @@ -11,22 +11,31 @@ import { STARK_HTTP_SERVICE, StarkHttpService } from "../../http/services"; import { AbstractStarkHttpRepository } from "../../http/repository"; import { StarkConfigurationUtil } from "../../../util/configuration.util"; +/** + * The resource path to be used for the User profile fetching if no other resource path is specified. + */ +export const DEFAULT_USER_PROFILE_RESOURCE_PATH = "security/userprofile"; + /** * @ignore - * @ngdoc service - * @description Repository to fetch user profile from the backend. */ @Injectable() export class StarkUserRepositoryImpl extends AbstractStarkHttpRepository implements StarkUserRepository { public constructor( @Inject(STARK_HTTP_SERVICE) starkHttpService: StarkHttpService, @Inject(STARK_LOGGING_SERVICE) logger: StarkLoggingService, - @Inject(STARK_APP_CONFIG) appConfig: StarkApplicationConfig + @Inject(STARK_APP_CONFIG) appConfig: StarkApplicationConfig, + @Optional() @Inject(STARK_USER_PROFILE_RESOURCE_PATH) userProfileResourcePath?: string ) { // ensuring that the app config is valid before doing anything StarkConfigurationUtil.validateConfig(appConfig, ["http"], starkUserRepositoryName); - super(starkHttpService, logger, appConfig.getBackend("userProfile"), "security/userprofile"); + super( + starkHttpService, + logger, + appConfig.getBackend("userProfile"), + userProfileResourcePath ? userProfileResourcePath : DEFAULT_USER_PROFILE_RESOURCE_PATH + ); this.logger.debug(starkUserRepositoryName + " loaded"); } diff --git a/packages/stark-core/src/modules/user/services/user.service.intf.ts b/packages/stark-core/src/modules/user/services/user.service.intf.ts index 4eeb844f66..f46b839b91 100644 --- a/packages/stark-core/src/modules/user/services/user.service.intf.ts +++ b/packages/stark-core/src/modules/user/services/user.service.intf.ts @@ -14,7 +14,7 @@ export const STARK_USER_SERVICE: InjectionToken = new Injectio /** * Stark User Service. * Service to fetch the user profile from the REST API. - * In Development, it can also be used to set the user profile manually. + * In Development, it can also be used to set the user profile manually and to retrieve a list of profiles from a mock data file. */ export interface StarkUserService { /** diff --git a/packages/stark-core/src/modules/user/services/user.service.ts b/packages/stark-core/src/modules/user/services/user.service.ts index 965501b8d3..592e5dd32d 100644 --- a/packages/stark-core/src/modules/user/services/user.service.ts +++ b/packages/stark-core/src/modules/user/services/user.service.ts @@ -30,9 +30,6 @@ const userErrorMessagePrefix: string = starkUserServiceName + ": invalid user pr /** * @ignore - * @ngdoc service - * @description Service to fetch the user profile from the REST API. In Development, it can also be used to - * set the user profile manually and to retrieve a list of profiles from a mock data file. */ @Injectable() export class StarkUserServiceImpl implements StarkUserService { diff --git a/packages/stark-core/src/modules/user/user.module.ts b/packages/stark-core/src/modules/user/user.module.ts index 423a7ab47d..9a8b7a7db6 100644 --- a/packages/stark-core/src/modules/user/user.module.ts +++ b/packages/stark-core/src/modules/user/user.module.ts @@ -1,4 +1,5 @@ import { ModuleWithProviders, NgModule, Optional, SkipSelf } from "@angular/core"; +import { STARK_USER_PROFILE_RESOURCE_PATH, StarkUserModuleConfig } from "./entities"; import { STARK_USER_SERVICE, StarkUserServiceImpl } from "./services"; import { STARK_USER_REPOSITORY, StarkUserRepositoryImpl } from "./repository"; @@ -8,14 +9,16 @@ export class StarkUserModule { * Instantiates the services only once since they should be singletons * so the forRoot() should be called only by the AppModule * @link https://angular.io/guide/singleton-services#forroot + * @param userModuleConfig - Object containing the configuration (if any) for the User Module * @returns a module with providers */ - public static forRoot(): ModuleWithProviders { + public static forRoot(userModuleConfig?: StarkUserModuleConfig): ModuleWithProviders { return { ngModule: StarkUserModule, providers: [ { provide: STARK_USER_SERVICE, useClass: StarkUserServiceImpl }, - { provide: STARK_USER_REPOSITORY, useClass: StarkUserRepositoryImpl } + { provide: STARK_USER_REPOSITORY, useClass: StarkUserRepositoryImpl }, + userModuleConfig ? { provide: STARK_USER_PROFILE_RESOURCE_PATH, useValue: userModuleConfig.userProfileResourcePath } : [] ] }; }