Skip to content

Commit

Permalink
feat(stark-core): allow configuring the User profile resource path us…
Browse files Browse the repository at this point in the history
…ed by the User Repository

ISSUES CLOSED: #956
  • Loading branch information
christophercr committed Apr 17, 2019
1 parent 9cbc513 commit 8698970
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ export abstract class AbstractStarkHttpRepository<T extends StarkResource> {
* 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<T>,
Expand Down
1 change: 1 addition & 0 deletions packages/stark-core/src/modules/user/entities.ts
Original file line number Diff line number Diff line change
@@ -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";
Original file line number Diff line number Diff line change
@@ -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<StarkUserModuleConfig["userProfileResourcePath"]>(
"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;
}
Original file line number Diff line number Diff line change
@@ -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<any>;
const mockLoggingService = new MockStarkLoggingService();
let mockAppConfig: StarkApplicationConfig;
let mockObserver: SpyObj<Observer<any>>;
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<Observer<any>>("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<any> = {
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<any> = {
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();
});
});
});
21 changes: 15 additions & 6 deletions packages/stark-core/src/modules/user/repository/user.repository.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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<StarkUser> implements StarkUserRepository {
public constructor(
@Inject(STARK_HTTP_SERVICE) starkHttpService: StarkHttpService<StarkUser>,
@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");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const STARK_USER_SERVICE: InjectionToken<StarkUserService> = 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 {
/**
Expand Down
3 changes: 0 additions & 3 deletions packages/stark-core/src/modules/user/services/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 5 additions & 2 deletions packages/stark-core/src/modules/user/user.module.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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 } : []
]
};
}
Expand Down

0 comments on commit 8698970

Please sign in to comment.