From 3fae13690fb8e226e9faf36ba195bb26ed04cb18 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 11 Mar 2020 16:49:21 +0100 Subject: [PATCH] 69110: EPerson Service tests --- src/app/core/data/data.service.ts | 2 +- .../core/eperson/eperson-data.service.spec.ts | 308 ++++++++++++++++++ src/app/core/eperson/eperson-data.service.ts | 11 +- src/app/shared/testing/eperson-mock.ts | 52 ++- src/app/shared/testing/group-mock.ts | 16 + 5 files changed, 378 insertions(+), 11 deletions(-) create mode 100644 src/app/core/eperson/eperson-data.service.spec.ts create mode 100644 src/app/shared/testing/group-mock.ts diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 3e67675290..5226b1a9f6 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -306,7 +306,7 @@ export abstract class DataService { * @return {Observable>} * Return an observable that emits response from the server */ - protected searchBy(searchMethod: string, options: FindListOptions = {}, ...linksToFollow: Array>): Observable>> { + searchBy(searchMethod: string, options: FindListOptions = {}, ...linksToFollow: Array>): Observable>> { const hrefObs = this.getSearchByHref(searchMethod, options, ...linksToFollow); diff --git a/src/app/core/eperson/eperson-data.service.spec.ts b/src/app/core/eperson/eperson-data.service.spec.ts new file mode 100644 index 0000000000..d301f9759d --- /dev/null +++ b/src/app/core/eperson/eperson-data.service.spec.ts @@ -0,0 +1,308 @@ +import { CommonModule } from '@angular/common'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { async, TestBed } from '@angular/core/testing'; +import { Store, StoreModule } from '@ngrx/store'; +import { compare, Operation } from 'fast-json-patch'; +import { getTestScheduler } from 'jasmine-marbles'; +import { of as observableOf } from 'rxjs'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { Observable } from 'rxjs/internal/Observable'; +import { TestScheduler } from 'rxjs/testing'; +import { + EPeopleRegistryCancelEPersonAction, + EPeopleRegistryEditEPersonAction +} from '../../+admin/admin-access-control/epeople-registry/epeople-registry.actions'; +import { getMockRemoteDataBuildServiceHrefMap } from '../../shared/mocks/mock-remote-data-build.service'; +import { getMockRequestService } from '../../shared/mocks/mock-request.service'; +import { MockTranslateLoader } from '../../shared/mocks/mock-translate-loader'; +import { EPersonMock, EPersonMock2 } from '../../shared/testing/eperson-mock'; +import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils'; +import { SearchParam } from '../cache/models/search-param.model'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { RestResponse } from '../cache/response.models'; +import { CoreState } from '../core.reducers'; +import { ChangeAnalyzer } from '../data/change-analyzer'; +import { PaginatedList } from '../data/paginated-list'; +import { RemoteData } from '../data/remote-data'; +import { DeleteByIDRequest, FindListOptions, PatchRequest } from '../data/request.models'; +import { RequestEntry } from '../data/request.reducer'; +import { RequestService } from '../data/request.service'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { Item } from '../shared/item.model'; +import { PageInfo } from '../shared/page-info.model'; +import { EPersonDataService } from './eperson-data.service'; +import { EPerson } from './models/eperson.model'; + +describe('EPersonDataService', () => { + let service: EPersonDataService; + let store: Store; + let requestService: RequestService; + let scheduler: TestScheduler; + + const responseCacheEntry = new RequestEntry(); + responseCacheEntry.response = new RestResponse(true, 200, 'Success'); + responseCacheEntry.completed = true; + + const epeople = [EPersonMock, EPersonMock2]; + + const restEndpointURL = 'https://dspace.4science.it/dspace-spring-rest/api/eperson'; + const epersonsEndpoint = `${restEndpointURL}/epersons`; + let halService: any = new HALEndpointServiceStub(restEndpointURL); + const epeople$ = createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [epeople])); + const rdbService = getMockRemoteDataBuildServiceHrefMap(undefined, { 'https://dspace.4science.it/dspace-spring-rest/api/eperson/epersons': epeople$ }); + const objectCache = Object.assign({ + /* tslint:disable:no-empty */ + remove: () => { + }, + hasBySelfLinkObservable: () => observableOf(false) + /* tslint:enable:no-empty */ + }) as ObjectCacheService; + + TestBed.configureTestingModule({ + imports: [ + CommonModule, + StoreModule.forRoot({}), + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: MockTranslateLoader + } + }), + ], + declarations: [], + providers: [], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }); + + const getRequestEntry$ = (successful: boolean) => { + return observableOf({ + response: { isSuccessful: successful, payload: epeople } as any + } as RequestEntry) + }; + + function initTestService() { + return new EPersonDataService( + requestService, + rdbService, + store, + null, + halService, + null, + null, + new DummyChangeAnalyzer() as any + ); + } + + beforeEach(() => { + requestService = getMockRequestService(getRequestEntry$(true)); + store = new Store(undefined, undefined, undefined); + service = initTestService(); + spyOn(store, 'dispatch'); + }); + + describe('searchByScope', () => { + beforeEach(() => { + spyOn(service, 'searchBy'); + }); + + it('search by default scope (byMetadata) and no query', () => { + service.searchByScope(null, ''); + const options = Object.assign(new FindListOptions(), { + searchParams: [Object.assign(new SearchParam('query', ''))] + }); + expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options); + }); + + it('search metadata scope and no query', () => { + service.searchByScope('metadata', ''); + const options = Object.assign(new FindListOptions(), { + searchParams: [Object.assign(new SearchParam('query', ''))] + }); + expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options); + }); + + it('search metadata scope and with query', () => { + service.searchByScope('metadata', 'test'); + const options = Object.assign(new FindListOptions(), { + searchParams: [Object.assign(new SearchParam('query', 'test'))] + }); + expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options); + }); + + it('search email scope and no query', () => { + service.searchByScope('email', ''); + const options = Object.assign(new FindListOptions(), { + searchParams: [Object.assign(new SearchParam('email', ''))] + }); + expect(service.searchBy).toHaveBeenCalledWith('byEmail', options); + }); + }); + + describe('updateEPerson', () => { + beforeEach(() => { + spyOn(service, 'findByHref').and.returnValue(createSuccessfulRemoteDataObject$(EPersonMock)); + }); + + describe('change Email', () => { + const newEmail = 'changedemail@test.com'; + beforeEach(() => { + const changedEPerson = Object.assign(new EPerson(), { + id: EPersonMock.id, + metadata: EPersonMock.metadata, + email: newEmail, + canLogIn: EPersonMock.canLogIn, + requireCertificate: EPersonMock.requireCertificate, + _links: EPersonMock._links, + }); + service.updateEPerson(changedEPerson).subscribe(); + }); + it('should send PatchRequest with replace email operation', () => { + const operations = [{ op: 'replace', path: '/email', value: newEmail }]; + const expected = new PatchRequest(requestService.generateRequestId(), epersonsEndpoint + '/' + EPersonMock.uuid, operations); + expect(requestService.configure).toHaveBeenCalledWith(expected); + }); + }); + + describe('change certificate', () => { + beforeEach(() => { + const changedEPerson = Object.assign(new EPerson(), { + id: EPersonMock.id, + metadata: EPersonMock.metadata, + email: EPersonMock.email, + canLogIn: EPersonMock.canLogIn, + requireCertificate: !EPersonMock.requireCertificate, + _links: EPersonMock._links, + }); + service.updateEPerson(changedEPerson).subscribe(); + }); + it('should send PatchRequest with replace certificate operation', () => { + const operations = [{ op: 'replace', path: '/certificate', value: !EPersonMock.requireCertificate }]; + const expected = new PatchRequest(requestService.generateRequestId(), epersonsEndpoint + '/' + EPersonMock.uuid, operations); + expect(requestService.configure).toHaveBeenCalledWith(expected); + }); + }); + + describe('change canLogin', () => { + beforeEach(() => { + const changedEPerson = Object.assign(new EPerson(), { + id: EPersonMock.id, + metadata: EPersonMock.metadata, + email: EPersonMock.email, + canLogIn: !EPersonMock.canLogIn, + requireCertificate: EPersonMock.requireCertificate, + _links: EPersonMock._links, + }); + service.updateEPerson(changedEPerson).subscribe(); + }); + it('should send PatchRequest with replace canLogIn operation', () => { + const operations = [{ op: 'replace', path: '/canLogIn', value: !EPersonMock.canLogIn }]; + const expected = new PatchRequest(requestService.generateRequestId(), epersonsEndpoint + '/' + EPersonMock.uuid, operations); + expect(requestService.configure).toHaveBeenCalledWith(expected); + }); + }); + + describe('change name', () => { + const newFirstName = 'changedFirst'; + const newLastName = 'changedLast'; + beforeEach(() => { + const changedEPerson = Object.assign(new EPerson(), { + id: EPersonMock.id, + metadata: { + 'eperson.firstname': [ + { + value: newFirstName, + } + ], + 'eperson.lastname': [ + { + value: newLastName, + }, + ], + }, + email: EPersonMock.email, + canLogIn: EPersonMock.canLogIn, + requireCertificate: EPersonMock.requireCertificate, + _links: EPersonMock._links, + }); + service.updateEPerson(changedEPerson).subscribe(); + }); + it('should send PatchRequest with replace name metadata operations', () => { + const operations = [ + { op: 'replace', path: '/eperson.lastname/0/value', value: newLastName }, + { op: 'replace', path: '/eperson.firstname/0/value', value: newFirstName }]; + const expected = new PatchRequest(requestService.generateRequestId(), epersonsEndpoint + '/' + EPersonMock.uuid, operations); + expect(requestService.configure).toHaveBeenCalledWith(expected); + }); + }); + }); + + describe('clearEPersonRequests', () => { + beforeEach(async(() => { + scheduler = getTestScheduler(); + halService = { + getEndpoint(linkPath: string): Observable { + return observableOf(restEndpointURL + '/' + linkPath); + } + } as HALEndpointService; + initTestService(); + service.clearEPersonRequests(); + })); + it('should remove the eperson hrefs in the request service', () => { + expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(epersonsEndpoint); + }); + }); + + describe('getActiveEPerson', () => { + it('should retrieve the ePerson currently getting edited, if any', () => { + service.editEPerson(EPersonMock); + + service.getActiveEPerson().subscribe((activeEPerson: EPerson) => { + expect(activeEPerson).toEqual(EPersonMock); + }) + }); + + it('should retrieve the ePerson currently getting edited, null if none being edited', () => { + service.getActiveEPerson().subscribe((activeEPerson: EPerson) => { + expect(activeEPerson).toEqual(null); + }) + }) + }); + + describe('cancelEditEPerson', () => { + it('should dispatch a CANCEL_EDIT_EPERSON action', () => { + service.cancelEditEPerson(); + expect(store.dispatch).toHaveBeenCalledWith(new EPeopleRegistryCancelEPersonAction()); + }); + }); + + describe('editEPerson', () => { + it('should dispatch a EDIT_EPERSON action with the EPerson to start editing', () => { + service.editEPerson(EPersonMock); + expect(store.dispatch).toHaveBeenCalledWith(new EPeopleRegistryEditEPersonAction(EPersonMock)); + }); + }); + + describe('deleteEPerson', () => { + beforeEach(() => { + spyOn(service, 'findById').and.returnValue(getRemotedataObservable(EPersonMock)); + service.deleteEPerson(EPersonMock).subscribe(); + }); + + it('should send DeleteRequest', () => { + const expected = new DeleteByIDRequest(requestService.generateRequestId(), epersonsEndpoint + '/' + EPersonMock.uuid, EPersonMock.uuid); + expect(requestService.configure).toHaveBeenCalledWith(expected); + }); + }); + +}); + +function getRemotedataObservable(obj: any): Observable> { + return observableOf(new RemoteData(false, false, true, undefined, obj)); +} + +class DummyChangeAnalyzer implements ChangeAnalyzer { + diff(object1: Item, object2: Item): Operation[] { + return compare((object1 as any).metadata, (object2 as any).metadata); + } +} diff --git a/src/app/core/eperson/eperson-data.service.ts b/src/app/core/eperson/eperson-data.service.ts index 79c7ba6e19..687e89d412 100644 --- a/src/app/core/eperson/eperson-data.service.ts +++ b/src/app/core/eperson/eperson-data.service.ts @@ -78,7 +78,7 @@ export class EPersonDataService extends DataService { * @param query Query of search * @param options Options of search request */ - public searchByScope(scope: string, query: string, options: FindListOptions = {}) { + public searchByScope(scope: string, query: string, options: FindListOptions = {}): Observable>> { switch (scope) { case 'metadata': return this.getEpeopleByMetadata(query.trim(), options); @@ -95,7 +95,7 @@ export class EPersonDataService extends DataService { * @param options * @param linksToFollow */ - public getEpeopleByEmail(query: string, options?: FindListOptions, ...linksToFollow: Array>): Observable>> { + private getEpeopleByEmail(query: string, options?: FindListOptions, ...linksToFollow: Array>): Observable>> { const searchParams = [new SearchParam('email', query)]; return this.getEPeopleBy(searchParams, this.searchByEmailPath, options, ...linksToFollow); } @@ -106,7 +106,7 @@ export class EPersonDataService extends DataService { * @param options * @param linksToFollow */ - public getEpeopleByMetadata(query: string, options?: FindListOptions, ...linksToFollow: Array>): Observable>> { + private getEpeopleByMetadata(query: string, options?: FindListOptions, ...linksToFollow: Array>): Observable>> { const searchParams = [new SearchParam('query', query)]; return this.getEPeopleBy(searchParams, this.searchByMetadataPath, options, ...linksToFollow); } @@ -173,11 +173,6 @@ export class EPersonDataService extends DataService { op: 'replace', path: '/canLogIn', value: newEPerson.canLogIn }] } - if (hasValue(oldEPerson.netid) && oldEPerson.netid !== newEPerson.netid) { - operations = [...operations, { - op: 'replace', path: '/netid', value: newEPerson.netid - }] - } return operations; } diff --git a/src/app/shared/testing/eperson-mock.ts b/src/app/shared/testing/eperson-mock.ts index 44585f278f..73bdd56b0b 100644 --- a/src/app/shared/testing/eperson-mock.ts +++ b/src/app/shared/testing/eperson-mock.ts @@ -1,6 +1,7 @@ import { EPerson } from '../../core/eperson/models/eperson.model'; +import { GroupMock } from './group-mock'; -export const EPersonMock: EPerson = Object.assign(new EPerson(),{ +export const EPersonMock: EPerson = Object.assign(new EPerson(), { handle: null, groups: [], netid: 'test@test.com', @@ -12,7 +13,8 @@ export const EPersonMock: EPerson = Object.assign(new EPerson(),{ _links: { self: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/epersons/testid', - } + }, + groups: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/epersons/testid/groups' } }, id: 'testid', uuid: 'testid', @@ -44,3 +46,49 @@ export const EPersonMock: EPerson = Object.assign(new EPerson(),{ ] } }); + +export const EPersonMock2: EPerson = Object.assign(new EPerson(), { + handle: null, + groups: [GroupMock], + netid: 'test2@test.com', + lastActive: '2019-05-14T12:25:42.411+0000', + canLogIn: false, + email: 'test2@test.com', + requireCertificate: false, + selfRegistered: true, + _links: { + self: { + href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/epersons/testid2', + }, + groups: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/epersons/testid2/groups' } + }, + id: 'testid2', + uuid: 'testid2', + type: 'eperson', + metadata: { + 'dc.title': [ + { + language: null, + value: 'User Test 2' + } + ], + 'eperson.firstname': [ + { + language: null, + value: 'User2' + } + ], + 'eperson.lastname': [ + { + language: null, + value: 'Test2' + }, + ], + 'eperson.language': [ + { + language: null, + value: 'fr' + }, + ] + } +}); diff --git a/src/app/shared/testing/group-mock.ts b/src/app/shared/testing/group-mock.ts new file mode 100644 index 0000000000..0c9abb4b7d --- /dev/null +++ b/src/app/shared/testing/group-mock.ts @@ -0,0 +1,16 @@ +import { Group } from '../../core/eperson/models/group.model'; + +export const GroupMock: Group = Object.assign(new Group(), { + handle: null, + groups: [], + selfRegistered: false, + _links: { + self: { + href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid', + }, + groups: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/groups' } + }, + id: 'testgroupid', + uuid: 'testgroupid', + type: 'group', +});