Added tests and comments

This commit is contained in:
Giuseppe Digilio
2019-04-02 18:29:44 +02:00
parent 49aee1898b
commit 080e0bee73
14 changed files with 475 additions and 13 deletions

View File

@@ -0,0 +1,108 @@
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Store } from '@ngrx/store';
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { CoreState } from '../core.reducers';
import { ClaimedTaskDataService } from './claimed-task-data.service';
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
describe('ClaimedTaskDataService', () => {
let service: ClaimedTaskDataService;
let options: HttpOptions;
const taskEndpoint = 'https://rest.api/task';
const linkPath = 'claimedtasks';
const requestService: any = getMockRequestService();
const halService: any = new HALEndpointServiceStub(taskEndpoint);
const rdbService = {} as RemoteDataBuildService;
const notificationsService = {} as NotificationsService;
const http = {} as HttpClient;
const comparator = {} as any;
const dataBuildService = {
normalize: (object) => object
} as NormalizedObjectBuildService;
const objectCache = {
addPatch: () => {
/* empty */
},
getObjectBySelfLink: () => {
/* empty */
}
} as any;
const store = {} as Store<CoreState>;
function initTestService(): ClaimedTaskDataService {
return new ClaimedTaskDataService(
requestService,
rdbService,
dataBuildService,
store,
objectCache,
halService,
notificationsService,
http,
comparator
);
}
beforeEach(() => {
service = initTestService();
options = Object.create({});
let headers = new HttpHeaders();
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
options.headers = headers;
});
describe('approveTask', () => {
it('should call postToEndpoint method', () => {
const scopeId = '1234';
const body = {
submit_approve: 'true'
};
spyOn(service, 'postToEndpoint');
requestService.prepareBody.and.returnValue(body);
service.approveTask(scopeId);
expect(service.postToEndpoint).toHaveBeenCalledWith(linkPath, body, scopeId, options);
});
});
describe('rejectTask', () => {
it('should call postToEndpoint method', () => {
const scopeId = '1234';
const reason = 'test reject';
const body = {
submit_reject: 'true',
reason
};
spyOn(service, 'postToEndpoint');
requestService.prepareBody.and.returnValue(body);
service.rejectTask(reason, scopeId);
expect(service.postToEndpoint).toHaveBeenCalledWith(linkPath, body, scopeId, options);
});
});
describe('returnToPoolTask', () => {
it('should call deleteById method', () => {
const scopeId = '1234';
spyOn(service, 'deleteById');
service.returnToPoolTask(scopeId);
expect(service.deleteById).toHaveBeenCalledWith(linkPath, scopeId, options);
});
});
});

View File

@@ -14,12 +14,37 @@ import { NormalizedObjectBuildService } from '../cache/builders/normalized-objec
import { ObjectCacheService } from '../cache/object-cache.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
import { ProcessTaskResponse } from './models/process-task-response';
/**
* The service handling all REST requests for ClaimedTask
*/
@Injectable()
export class ClaimedTaskDataService extends TasksService<ClaimedTask> {
/**
* The endpoint link name
*/
protected linkPath = 'claimedtasks';
/**
* When true, a new request is always dispatched
*/
protected forceBypassCache = true;
/**
* Initialize instance variables
*
* @param {RequestService} requestService
* @param {RemoteDataBuildService} rdbService
* @param {NormalizedObjectBuildService} dataBuildService
* @param {Store<CoreState>} store
* @param {ObjectCacheService} objectCache
* @param {HALEndpointService} halService
* @param {NotificationsService} notificationsService
* @param {HttpClient} http
* @param {DSOChangeAnalyzer<ClaimedTask} comparator
*/
constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
@@ -33,14 +58,32 @@ export class ClaimedTaskDataService extends TasksService<ClaimedTask> {
super();
}
public approveTask(scopeId: string): Observable<any> {
/**
* Make a request to approve the given task
*
* @param scopeId
* The task id
* @return {Observable<ProcessTaskResponse>}
* Emit the server response
*/
public approveTask(scopeId: string): Observable<ProcessTaskResponse> {
const body = {
submit_approve: 'true'
};
return this.postToEndpoint(this.linkPath, this.requestService.prepareBody(body), scopeId, this.makeHttpOptions());
}
public rejectTask(reason: string, scopeId: string): Observable<any> {
/**
* Make a request to reject the given task
*
* @param reason
* The reason of reject
* @param scopeId
* The task id
* @return {Observable<ProcessTaskResponse>}
* Emit the server response
*/
public rejectTask(reason: string, scopeId: string): Observable<ProcessTaskResponse> {
const body = {
submit_reject: 'true',
reason
@@ -48,7 +91,15 @@ export class ClaimedTaskDataService extends TasksService<ClaimedTask> {
return this.postToEndpoint(this.linkPath, this.requestService.prepareBody(body), scopeId, this.makeHttpOptions());
}
public returnToPoolTask(scopeId: string): Observable<any> {
/**
* Make a request to return the given task to the pool
*
* @param scopeId
* The task id
* @return {Observable<ProcessTaskResponse>}
* Emit the server response
*/
public returnToPoolTask(scopeId: string): Observable<ProcessTaskResponse> {
return this.deleteById(this.linkPath, scopeId, this.makeHttpOptions());
}

View File

@@ -1,5 +1,8 @@
import { TaskObject } from './task-object.model';
/**
* A model class for a ClaimedTask.
*/
export class ClaimedTask extends TaskObject {
}

View File

@@ -5,7 +5,7 @@ import { ClaimedTask } from './claimed-task-object.model';
import { ResourceType } from '../../shared/resource-type';
/**
* A model class for a NormalizedClaimedTaskObject.
* A normalized model class for a ClaimedTask.
*/
@mapsTo(ClaimedTask)
@inheritSerialization(NormalizedTaskObject)

View File

@@ -5,7 +5,7 @@ import { mapsTo, relationship } from '../../cache/builders/build-decorators';
import { ResourceType } from '../../shared/resource-type';
/**
* A model class for a NormalizedPoolTaskObject.
* A normalized model class for a PoolTask.
*/
@mapsTo(PoolTask)
@inheritSerialization(NormalizedTaskObject)

View File

@@ -6,7 +6,7 @@ import { TaskObject } from './task-object.model';
import { DSpaceObject } from '../../shared/dspace-object.model';
/**
* An abstract model class for a DSpaceObject.
* An abstract normalized model class for a TaskObject.
*/
@mapsTo(TaskObject)
@inheritSerialization(NormalizedDSpaceObject)

View File

@@ -1,5 +1,8 @@
import { TaskObject } from './task-object.model';
/**
* A model class for a PoolTask.
*/
export class PoolTask extends TaskObject {
}

View File

@@ -6,6 +6,9 @@ import { ListableObject } from '../../../shared/object-collection/shared/listabl
import { RemoteData } from '../../data/remote-data';
import { Workflowitem } from '../../submission/models/workflowitem.model';
/**
* An abstract model class for a TaskObject.
*/
export class TaskObject extends DSpaceObject implements CacheableObject, ListableObject {
/**

View File

@@ -0,0 +1,70 @@
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Store } from '@ngrx/store';
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { CoreState } from '../core.reducers';
import { PoolTaskDataService } from './pool-task-data.service';
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
describe('PoolTaskDataService', () => {
let service: PoolTaskDataService;
let options: HttpOptions;
const taskEndpoint = 'https://rest.api/task';
const linkPath = 'pooltasks';
const requestService = getMockRequestService();
const halService: any = new HALEndpointServiceStub(taskEndpoint);
const rdbService = {} as RemoteDataBuildService;
const notificationsService = {} as NotificationsService;
const http = {} as HttpClient;
const comparator = {} as any;
const dataBuildService = {
normalize: (object) => object
} as NormalizedObjectBuildService;
const objectCache = {
addPatch: () => {
/* empty */
},
getObjectBySelfLink: () => {
/* empty */
}
} as any;
const store = {} as Store<CoreState>;
function initTestService(): PoolTaskDataService {
return new PoolTaskDataService(
requestService,
rdbService,
dataBuildService,
store,
objectCache,
halService,
notificationsService,
http,
comparator
);
}
beforeEach(() => {
service = initTestService();
options = Object.create({});
let headers = new HttpHeaders();
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
options.headers = headers;
});
describe('claimTask', () => {
it('should call postToEndpoint method', () => {
spyOn(service, 'postToEndpoint');
const scopeId = '1234';
service.claimTask(scopeId);
expect(service.postToEndpoint).toHaveBeenCalledWith(linkPath, {}, scopeId, options);
});
});
});

View File

@@ -14,12 +14,37 @@ import { NormalizedObjectBuildService } from '../cache/builders/normalized-objec
import { ObjectCacheService } from '../cache/object-cache.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
import { ProcessTaskResponse } from './models/process-task-response';
/**
* The service handling all REST requests for PoolTask
*/
@Injectable()
export class PoolTaskDataService extends TasksService<PoolTask> {
/**
* The endpoint link name
*/
protected linkPath = 'pooltasks';
/**
* When true, a new request is always dispatched
*/
protected forceBypassCache = true;
/**
* Initialize instance variables
*
* @param {RequestService} requestService
* @param {RemoteDataBuildService} rdbService
* @param {NormalizedObjectBuildService} dataBuildService
* @param {Store<CoreState>} store
* @param {ObjectCacheService} objectCache
* @param {HALEndpointService} halService
* @param {NotificationsService} notificationsService
* @param {HttpClient} http
* @param {DSOChangeAnalyzer<ClaimedTask} comparator
*/
constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
@@ -33,7 +58,15 @@ export class PoolTaskDataService extends TasksService<PoolTask> {
super();
}
public claimTask(scopeId: string): Observable<any> {
/**
* Make a request to claim the given task
*
* @param scopeId
* The task id
* @return {Observable<ProcessTaskResponse>}
* Emit the server response
*/
public claimTask(scopeId: string): Observable<ProcessTaskResponse> {
return this.postToEndpoint(this.linkPath, {}, scopeId, this.makeHttpOptions());
}
}

View File

@@ -11,17 +11,33 @@ import { ObjectCacheService } from '../cache/object-cache.service';
import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory';
import { ErrorResponse, RestResponse, TaskResponse } from '../cache/response.models';
/**
* Provides methods to parse response for a task request.
*/
@Injectable()
export class TaskResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
protected objectFactory = NormalizedObjectFactory;
protected toCache = false;
/**
* Initialize instance variables
*
* @param {GlobalConfig} EnvConfig
* @param {ObjectCacheService} objectCache
*/
constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
protected objectCache: ObjectCacheService,) {
super();
}
/**
* Parses data from the tasks endpoints
*
* @param {RestRequest} request
* @param {DSpaceRESTV2Response} data
* @returns {RestResponse}
*/
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
if (this.isSuccessStatus(data.statusCode)) {
return new TaskResponse( data.statusCode, data.statusText);

View File

@@ -0,0 +1,130 @@
import { getTestScheduler } from 'jasmine-marbles';
import { TestScheduler } from 'rxjs/testing';
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
import { TasksService } from './tasks.service';
import { RequestService } from '../data/request.service';
import { TaskDeleteRequest, TaskPostRequest } from '../data/request.models';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
import { TaskObject } from './models/task-object.model';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { Store } from '@ngrx/store';
import { CoreState } from '../core.reducers';
import { ObjectCacheService } from '../cache/object-cache.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
import { ChangeAnalyzer } from '../data/change-analyzer';
import { compare, Operation } from 'fast-json-patch';
import { NormalizedTaskObject } from './models/normalized-task-object.model';
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
const LINK_NAME = 'test';
/* tslint:disable:max-classes-per-file */
class TestTask extends TaskObject {
}
class TestService extends TasksService<TestTask> {
protected linkPath = LINK_NAME;
protected forceBypassCache = true;
constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected dataBuildService: NormalizedObjectBuildService,
protected store: Store<CoreState>,
protected objectCache: ObjectCacheService,
protected halService: HALEndpointService,
protected notificationsService: NotificationsService,
protected http: HttpClient,
protected comparator: DSOChangeAnalyzer<TestTask>) {
super();
}
}
class NormalizedTestTaskObject extends NormalizedTaskObject<TestTask> {
}
class DummyChangeAnalyzer implements ChangeAnalyzer<NormalizedTestTaskObject> {
diff(object1: NormalizedTestTaskObject, object2: NormalizedTestTaskObject): Operation[] {
return compare((object1 as any).metadata, (object2 as any).metadata);
}
}
/* tslint:enable:max-classes-per-file */
describe('TasksService', () => {
let scheduler: TestScheduler;
let service: TestService;
const taskEndpoint = 'https://rest.api/task';
const linkPath = 'testTask';
const requestService = getMockRequestService();
const halService: any = new HALEndpointServiceStub(taskEndpoint);
const rdbService = {} as RemoteDataBuildService;
const notificationsService = {} as NotificationsService;
const http = {} as HttpClient;
const comparator = new DummyChangeAnalyzer() as any;
const dataBuildService = {
normalize: (object) => object
} as NormalizedObjectBuildService;
const objectCache = {
addPatch: () => {
/* empty */
},
getObjectBySelfLink: () => {
/* empty */
}
} as any;
const store = {} as Store<CoreState>;
function initTestService(): TestService {
return new TestService(
requestService,
rdbService,
dataBuildService,
store,
objectCache,
halService,
notificationsService,
http,
comparator
);
}
beforeEach(() => {
scheduler = getTestScheduler();
service = initTestService();
const options: HttpOptions = Object.create({});
let headers = new HttpHeaders();
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
options.headers = headers;
});
describe('postToEndpoint', () => {
it('should configure a new TaskPostRequest', () => {
const expected = new TaskPostRequest(requestService.generateRequestId(), `${taskEndpoint}/${linkPath}`, {});
scheduler.schedule(() => service.postToEndpoint('testTask', {}).subscribe());
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(expected);
});
});
describe('deleteById', () => {
it('should configure a new TaskPostRequest', () => {
const scopeId = '1234';
const expected = new TaskDeleteRequest(requestService.generateRequestId(), `${taskEndpoint}/${linkPath}/${scopeId}`, null);
scheduler.schedule(() => service.deleteById('testTask', scopeId).subscribe());
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(expected);
});
});
});

View File

@@ -13,12 +13,23 @@ import { getResponseFromEntry } from '../shared/operators';
import { ErrorResponse, MessageResponse, RestResponse } from '../cache/response.models';
import { CacheableObject } from '../cache/object-cache.reducer';
/**
* An abstract class that provides methods to handle task requests.
*/
export abstract class TasksService<T extends CacheableObject> extends DataService<T> {
public getBrowseEndpoint(options: FindAllOptions): Observable<string> {
return this.halService.getEndpoint(this.linkPath);
}
/**
* Fetch a RestRequest
*
* @param requestId
* The base endpoint for the type of object
* @return Observable<ProcessTaskResponse>
* server response
*/
protected fetchRequest(requestId: string): Observable<ProcessTaskResponse> {
const responses = this.requestService.getByUUID(requestId).pipe(
getResponseFromEntry()
@@ -39,14 +50,32 @@ export abstract class TasksService<T extends CacheableObject> extends DataServic
return observableMerge(errorResponses, successResponses);
}
/**
* Create the HREF for a specific submission object based on its identifier
*
* @param endpoint
* The base endpoint for the type of object
* @param resourceID
* The identifier for the object
*/
protected getEndpointByIDHref(endpoint, resourceID): string {
return isNotEmpty(resourceID) ? `${endpoint}/${resourceID}` : `${endpoint}`;
}
protected getEndpointByMethod(endpoint: string, method: string): string {
return isNotEmpty(method) ? `${endpoint}/${method}` : `${endpoint}`;
}
/**
* Make a new post request
*
* @param linkPath
* The endpoint link name
* @param body
* The request body
* @param scopeId
* The task id to be removed
* @param options
* The HttpOptions object
* @return Observable<SubmitDataResponseDefinitionObject>
* server response
*/
public postToEndpoint(linkPath: string, body: any, scopeId?: string, options?: HttpOptions): Observable<ProcessTaskResponse> {
const requestId = this.requestService.generateRequestId();
return this.halService.getEndpoint(linkPath).pipe(
@@ -59,9 +88,21 @@ export abstract class TasksService<T extends CacheableObject> extends DataServic
distinctUntilChanged());
}
public deleteById(linkName: string, scopeId: string, options?: HttpOptions): Observable<ProcessTaskResponse> {
/**
* Delete an existing task on the server
*
* @param linkPath
* The endpoint link name
* @param scopeId
* The task id to be removed
* @param options
* The HttpOptions object
* @return Observable<SubmitDataResponseDefinitionObject>
* server response
*/
public deleteById(linkPath: string, scopeId: string, options?: HttpOptions): Observable<ProcessTaskResponse> {
const requestId = this.requestService.generateRequestId();
return this.halService.getEndpoint(linkName || this.linkPath).pipe(
return this.halService.getEndpoint(linkPath || this.linkPath).pipe(
filter((href: string) => isNotEmpty(href)),
distinctUntilChanged(),
map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, scopeId)),
@@ -71,6 +112,9 @@ export abstract class TasksService<T extends CacheableObject> extends DataServic
distinctUntilChanged());
}
/**
* Create a new HttpOptions
*/
protected makeHttpOptions() {
const options: HttpOptions = Object.create({});
let headers = new HttpHeaders();

View File

@@ -8,6 +8,7 @@ export function getMockRequestService(requestEntry$: Observable<RequestEntry> =
generateRequestId: 'clients/b186e8ce-e99c-4183-bc9a-42b4821bdb78',
getByHref: requestEntry$,
getByUUID: requestEntry$,
prepareBody: jasmine.createSpy('prepareBody'),
/* tslint:disable:no-empty */
removeByHrefSubstring: () => {}
/* tslint:enable:no-empty */