[CST-5307] Refactoring and adding missing unit tests

This commit is contained in:
Giuseppe Digilio
2022-05-10 18:14:00 +02:00
parent 0b664431af
commit 853dcecfb8
10 changed files with 876 additions and 286 deletions

View File

@@ -78,15 +78,32 @@ describe(`DSONameService`, () => {
}); });
describe(`factories.Person`, () => { describe(`factories.Person`, () => {
beforeEach(() => { describe(`with person.familyName and person.givenName`, () => {
spyOn(mockPerson, 'firstMetadataValue').and.returnValues(...mockPersonName.split(', ')); beforeEach(() => {
spyOn(mockPerson, 'firstMetadataValue').and.returnValues(...mockPersonName.split(', '));
});
it(`should return 'person.familyName, person.givenName'`, () => {
const result = (service as any).factories.Person(mockPerson);
expect(result).toBe(mockPersonName);
expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('person.familyName');
expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('person.givenName');
expect(mockPerson.firstMetadataValue).not.toHaveBeenCalledWith('dc.title');
});
}); });
it(`should return 'person.familyName, person.givenName'`, () => { describe(`without person.familyName and person.givenName`, () => {
const result = (service as any).factories.Person(mockPerson); beforeEach(() => {
expect(result).toBe(mockPersonName); spyOn(mockPerson, 'firstMetadataValue').and.returnValues(undefined, undefined, mockPersonName);
expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('person.familyName'); });
expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('person.givenName');
it(`should return dc.title`, () => {
const result = (service as any).factories.Person(mockPerson);
expect(result).toBe(mockPersonName);
expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('person.familyName');
expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('person.givenName');
expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('dc.title');
});
}); });
}); });

View File

@@ -0,0 +1,290 @@
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
import { of as observableOf } from 'rxjs';
import { TestScheduler } from 'rxjs/testing';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ObjectCacheService } from '../cache/object-cache.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { RequestService } from '../data/request.service';
import { PageInfo } from '../shared/page-info.model';
import { buildPaginatedList } from '../data/paginated-list.model';
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { RestResponse } from '../cache/response.models';
import { RequestEntry } from '../data/request-entry.model';
import { ResearcherProfileService } from './researcher-profile.service';
import { RouterMock } from '../../shared/mocks/router.mock';
import { ResearcherProfile } from './model/researcher-profile.model';
import { Item } from '../shared/item.model';
import { ReplaceOperation } from 'fast-json-patch';
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
import { PostRequest } from '../data/request.models';
describe('ResearcherProfileService', () => {
let scheduler: TestScheduler;
let service: ResearcherProfileService;
let serviceAsAny: any;
let requestService: RequestService;
let rdbService: RemoteDataBuildService;
let objectCache: ObjectCacheService;
let halService: HALEndpointService;
let responseCacheEntry: RequestEntry;
const researcherProfileId = 'beef9946-rt56-479e-8f11-b90cbe9f7241';
const itemId = 'beef9946-rt56-479e-8f11-b90cbe9f7241';
const researcherProfileItem: Item = Object.assign(new Item(), {
id: itemId,
_links: {
self: {
href: `https://rest.api/rest/api/items/${itemId}`
},
}
});
const researcherProfile: ResearcherProfile = Object.assign(new ResearcherProfile(), {
id: researcherProfileId,
visible: false,
type: 'profile',
_links: {
item: {
href: `https://rest.api/rest/api/profiles/${researcherProfileId}/item`
},
self: {
href: `https://rest.api/rest/api/profiles/${researcherProfileId}`
},
}
});
const researcherProfilePatched: ResearcherProfile = Object.assign(new ResearcherProfile(), {
id: researcherProfileId,
visible: true,
type: 'profile',
_links: {
item: {
href: `https://rest.api/rest/api/profiles/${researcherProfileId}/item`
},
self: {
href: `https://rest.api/rest/api/profiles/${researcherProfileId}`
},
}
});
const researcherProfileId2 = 'agbf9946-f4ce-479e-8f11-b90cbe9f7241';
const anotherResearcherProfile: ResearcherProfile = Object.assign(new ResearcherProfile(), {
id: researcherProfileId2,
visible: false,
type: 'profile',
_links: {
self: {
href: `https://rest.api/rest/api/profiles/${researcherProfileId2}`
},
}
});
const endpointURL = `https://rest.api/rest/api/profiles`;
const sourceUri = `https://rest.api/rest/api/external-source/profile`;
const requestURL = `https://rest.api/rest/api/profiles/${researcherProfileId}`;
const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a';
const pageInfo = new PageInfo();
const array = [researcherProfile, anotherResearcherProfile];
const paginatedList = buildPaginatedList(pageInfo, array);
const researcherProfileRD = createSuccessfulRemoteDataObject(researcherProfile);
const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList);
beforeEach(() => {
scheduler = getTestScheduler();
halService = jasmine.createSpyObj('halService', {
getEndpoint: cold('a', { a: endpointURL })
});
responseCacheEntry = new RequestEntry();
responseCacheEntry.request = { href: 'https://rest.api/' } as any;
responseCacheEntry.response = new RestResponse(true, 200, 'Success');
requestService = jasmine.createSpyObj('requestService', {
generateRequestId: requestUUID,
send: true,
removeByHrefSubstring: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: observableOf(responseCacheEntry),
setStaleByHrefSubstring: jasmine.createSpy('setStaleByHrefSubstring')
});
rdbService = jasmine.createSpyObj('rdbService', {
buildSingle: hot('a|', {
a: researcherProfileRD
}),
buildList: hot('a|', {
a: paginatedListRD
}),
buildFromRequestUUID: hot('a|', {
a: researcherProfileRD
})
});
objectCache = {} as ObjectCacheService;
const notificationsService = {} as NotificationsService;
const http = {} as HttpClient;
const comparator = {} as any;
const routerStub: any = new RouterMock();
const itemService = jasmine.createSpyObj('ItemService', {
findByHref: jasmine.createSpy('findByHref')
});
service = new ResearcherProfileService(
requestService,
rdbService,
objectCache,
halService,
notificationsService,
http,
routerStub,
comparator,
itemService
);
serviceAsAny = service;
spyOn((service as any).dataService, 'create').and.callThrough();
spyOn((service as any).dataService, 'delete').and.callThrough();
spyOn((service as any).dataService, 'update').and.callThrough();
spyOn((service as any).dataService, 'findById').and.callThrough();
spyOn((service as any).dataService, 'findByHref').and.callThrough();
spyOn((service as any).dataService, 'searchBy').and.callThrough();
spyOn((service as any).dataService, 'getLinkPath').and.returnValue(observableOf(endpointURL));
});
describe('findById', () => {
it('should proxy the call to dataservice.findById with eperson UUID', () => {
scheduler.schedule(() => service.findById(researcherProfileId));
scheduler.flush();
expect((service as any).dataService.findById).toHaveBeenCalledWith(researcherProfileId, true, true);
});
it('should return a ResearcherProfile object with the given id', () => {
const result = service.findById(researcherProfileId);
const expected = cold('a|', {
a: researcherProfileRD
});
expect(result).toBeObservable(expected);
});
});
describe('create', () => {
it('should proxy the call to dataservice.create with eperson UUID', () => {
scheduler.schedule(() => service.create());
scheduler.flush();
expect((service as any).dataService.create).toHaveBeenCalled();
});
it('should return the RemoteData<ResearcherProfile> created', () => {
const result = service.create();
const expected = cold('a|', {
a: researcherProfileRD
});
expect(result).toBeObservable(expected);
});
});
describe('delete', () => {
it('should proxy the call to dataservice.delete', () => {
scheduler.schedule(() => service.delete(researcherProfile));
scheduler.flush();
expect((service as any).dataService.delete).toHaveBeenCalledWith(researcherProfile.id);
});
});
describe('findRelatedItemId', () => {
describe('with a related item', () => {
beforeEach(() => {
(service as any).itemService.findByHref.and.returnValue(createSuccessfulRemoteDataObject$(researcherProfileItem));
});
it('should proxy the call to dataservice.findById with eperson UUID', () => {
scheduler.schedule(() => service.findRelatedItemId(researcherProfile));
scheduler.flush();
expect((service as any).itemService.findByHref).toHaveBeenCalledWith(researcherProfile._links.item.href, false);
});
it('should return a ResearcherProfile object with the given id', () => {
const result = service.findRelatedItemId(researcherProfile);
const expected = cold('(a|)', {
a: itemId
});
expect(result).toBeObservable(expected);
});
});
describe('without a related item', () => {
beforeEach(() => {
(service as any).itemService.findByHref.and.returnValue(createSuccessfulRemoteDataObject$(null));
});
it('should proxy the call to dataservice.findById with eperson UUID', () => {
scheduler.schedule(() => service.findRelatedItemId(researcherProfile));
scheduler.flush();
expect((service as any).itemService.findByHref).toHaveBeenCalledWith(researcherProfile._links.item.href, false);
});
it('should return a ResearcherProfile object with the given id', () => {
const result = service.findRelatedItemId(researcherProfile);
const expected = cold('(a|)', {
a: undefined
});
expect(result).toBeObservable(expected);
});
});
});
describe('setVisibility', () => {
let patchSpy;
beforeEach(() => {
spyOn((service as any), 'patch').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched));
spyOn((service as any), 'findById').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched));
});
it('should proxy the call to dataservice.patch', () => {
const replaceOperation: ReplaceOperation<boolean> = {
path: '/visible',
op: 'replace',
value: true
};
scheduler.schedule(() => service.setVisibility(researcherProfile, true));
scheduler.flush();
expect((service as any).patch).toHaveBeenCalledWith(researcherProfile, [replaceOperation]);
});
});
describe('createFromExternalSource', () => {
let patchSpy;
beforeEach(() => {
spyOn((service as any), 'patch').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched));
spyOn((service as any), 'findById').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched));
});
it('should proxy the call to dataservice.patch', () => {
const options: HttpOptions = Object.create({});
let headers = new HttpHeaders();
headers = headers.append('Content-Type', 'text/uri-list');
options.headers = headers;
const request = new PostRequest(requestUUID, endpointURL, sourceUri, options);
scheduler.schedule(() => service.createFromExternalSource(sourceUri));
scheduler.flush();
expect((service as any).requestService.send).toHaveBeenCalledWith(request);
expect((service as any).rdbService.buildFromRequestUUID).toHaveBeenCalledWith(requestUUID);
});
});
});

View File

@@ -2,27 +2,25 @@
import { HttpClient, HttpHeaders } from '@angular/common/http'; import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Operation, RemoveOperation, ReplaceOperation } from 'fast-json-patch'; import { Operation, ReplaceOperation } from 'fast-json-patch';
import { combineLatest, Observable, of as observableOf } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
import { catchError, find, map, switchMap, tap } from 'rxjs/operators'; import { catchError, find, map, switchMap, tap } from 'rxjs/operators';
import { environment } from '../../../environments/environment';
import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsService } from '../../shared/notifications/notifications.service';
import { dataService } from '../cache/builders/build-decorators'; import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ObjectCacheService } from '../cache/object-cache.service'; import { ObjectCacheService } from '../cache/object-cache.service';
import { ConfigurationDataService } from '../data/configuration-data.service';
import { DataService } from '../data/data.service'; import { DataService } from '../data/data.service';
import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service'; import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service';
import { ItemDataService } from '../data/item-data.service'; import { ItemDataService } from '../data/item-data.service';
import { RemoteData } from '../data/remote-data'; import { RemoteData } from '../data/remote-data';
import { RequestService } from '../data/request.service'; import { RequestService } from '../data/request.service';
import { ConfigurationProperty } from '../shared/configuration-property.model';
import { HALEndpointService } from '../shared/hal-endpoint.service'; import { HALEndpointService } from '../shared/hal-endpoint.service';
import { Item } from '../shared/item.model';
import { NoContent } from '../shared/NoContent.model'; import { NoContent } from '../shared/NoContent.model';
import { import {
getFinishedRemoteData, getAllCompletedRemoteData,
getFirstCompletedRemoteData, getFirstCompletedRemoteData,
getFirstSucceededRemoteDataPayload getFirstSucceededRemoteDataPayload
} from '../shared/operators'; } from '../shared/operators';
@@ -31,25 +29,26 @@ import { RESEARCHER_PROFILE } from './model/researcher-profile.resource-type';
import { HttpOptions } from '../dspace-rest/dspace-rest.service'; import { HttpOptions } from '../dspace-rest/dspace-rest.service';
import { PostRequest } from '../data/request.models'; import { PostRequest } from '../data/request.models';
import { hasValue } from '../../shared/empty.util'; import { hasValue } from '../../shared/empty.util';
import {CoreState} from '../core-state.model'; import { CoreState } from '../core-state.model';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
/** /**
* A private DataService implementation to delegate specific methods to. * A private DataService implementation to delegate specific methods to.
*/ */
class ResearcherProfileServiceImpl extends DataService<ResearcherProfile> { class ResearcherProfileServiceImpl extends DataService<ResearcherProfile> {
protected linkPath = 'profiles'; protected linkPath = 'profiles';
constructor( constructor(
protected requestService: RequestService, protected requestService: RequestService,
protected rdbService: RemoteDataBuildService, protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>, protected store: Store<CoreState>,
protected objectCache: ObjectCacheService, protected objectCache: ObjectCacheService,
protected halService: HALEndpointService, protected halService: HALEndpointService,
protected notificationsService: NotificationsService, protected notificationsService: NotificationsService,
protected http: HttpClient, protected http: HttpClient,
protected comparator: DefaultChangeAnalyzer<ResearcherProfile>) { protected comparator: DefaultChangeAnalyzer<ResearcherProfile>) {
super(); super();
} }
} }
@@ -60,100 +59,102 @@ class ResearcherProfileServiceImpl extends DataService<ResearcherProfile> {
@dataService(RESEARCHER_PROFILE) @dataService(RESEARCHER_PROFILE)
export class ResearcherProfileService { export class ResearcherProfileService {
dataService: ResearcherProfileServiceImpl; dataService: ResearcherProfileServiceImpl;
responseMsToLive: number = 10 * 1000; responseMsToLive: number = 10 * 1000;
constructor( constructor(
protected requestService: RequestService, protected requestService: RequestService,
protected rdbService: RemoteDataBuildService, protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>, protected objectCache: ObjectCacheService,
protected objectCache: ObjectCacheService, protected halService: HALEndpointService,
protected halService: HALEndpointService, protected notificationsService: NotificationsService,
protected notificationsService: NotificationsService, protected http: HttpClient,
protected http: HttpClient, protected router: Router,
protected router: Router, protected comparator: DefaultChangeAnalyzer<ResearcherProfile>,
protected comparator: DefaultChangeAnalyzer<ResearcherProfile>, protected itemService: ItemDataService) {
protected itemService: ItemDataService,
protected configurationService: ConfigurationDataService ) {
this.dataService = new ResearcherProfileServiceImpl(requestService, rdbService, store, objectCache, halService, this.dataService = new ResearcherProfileServiceImpl(requestService, rdbService, null, objectCache, halService,
notificationsService, http, comparator); notificationsService, http, comparator);
} }
/** /**
* Find the researcher profile with the given uuid. * Find the researcher profile with the given uuid.
* *
* @param uuid the profile uuid * @param uuid the profile uuid
*/ * @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
findById(uuid: string): Observable<ResearcherProfile> { * no valid cached version. Defaults to true
return this.dataService.findById(uuid, false) * @param reRequestOnStale Whether or not the request should automatically be re-
.pipe ( getFinishedRemoteData(), * requested after the response becomes stale
map((remoteData) => remoteData.payload)); * @param linksToFollow List of {@link FollowLinkConfig} that indicate which
} * {@link HALLink}s should be automatically resolved
*/
public findById(uuid: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<ResearcherProfile>[]): Observable<RemoteData<ResearcherProfile>> {
return this.dataService.findById(uuid, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow).pipe(
getAllCompletedRemoteData(),
);
}
/** /**
* Create a new researcher profile for the current user. * Create a new researcher profile for the current user.
*/ */
create(): Observable<RemoteData<ResearcherProfile>> { public create(): Observable<RemoteData<ResearcherProfile>> {
return this.dataService.create( new ResearcherProfile()); return this.dataService.create(new ResearcherProfile());
} }
/** /**
* Delete a researcher profile. * Delete a researcher profile.
* *
* @param researcherProfile the profile to delete * @param researcherProfile the profile to delete
*/ */
delete(researcherProfile: ResearcherProfile): Observable<boolean> { public delete(researcherProfile: ResearcherProfile): Observable<boolean> {
return this.dataService.delete(researcherProfile.id).pipe( return this.dataService.delete(researcherProfile.id).pipe(
getFirstCompletedRemoteData(), getFirstCompletedRemoteData(),
tap((response: RemoteData<NoContent>) => { tap((response: RemoteData<NoContent>) => {
if (response.isSuccess) { if (response.isSuccess) {
this.requestService.setStaleByHrefSubstring(researcherProfile._links.self.href); this.requestService.setStaleByHrefSubstring(researcherProfile._links.self.href);
} }
}),
map((response: RemoteData<NoContent>) => response.isSuccess)
);
}
/**
* Find the item id related to the given researcher profile.
*
* @param researcherProfile the profile to find for
*/
public findRelatedItemId(researcherProfile: ResearcherProfile): Observable<string> {
return this.itemService.findByHref(researcherProfile._links.item.href, false)
.pipe(getFirstSucceededRemoteDataPayload(),
catchError((error) => {
console.debug(error);
return observableOf(null);
}), }),
map((response: RemoteData<NoContent>) => response.isSuccess) map((item) => item?.id)
); );
} }
/** /**
* Find the item id related to the given researcher profile. * Change the visibility of the given researcher profile setting the given value.
* *
* @param researcherProfile the profile to find for * @param researcherProfile the profile to update
*/ * @param visible the visibility value to set
findRelatedItemId( researcherProfile: ResearcherProfile ): Observable<string> { */
return this.itemService.findByHref(researcherProfile._links.item.href, false) public setVisibility(researcherProfile: ResearcherProfile, visible: boolean): Observable<ResearcherProfile> {
.pipe (getFirstSucceededRemoteDataPayload(),
catchError((error) => {
console.debug(error);
return observableOf(null);
}),
map((item) => item != null ? item.id : null ));
}
/** const replaceOperation: ReplaceOperation<boolean> = {
* Change the visibility of the given researcher profile setting the given value. path: '/visible',
* op: 'replace',
* @param researcherProfile the profile to update value: visible
* @param visible the visibility value to set };
*/
setVisibility(researcherProfile: ResearcherProfile, visible: boolean): Observable<ResearcherProfile> {
const replaceOperation: ReplaceOperation<boolean> = { return this.patch(researcherProfile, [replaceOperation]).pipe(
path: '/visible', switchMap(() => this.findById(researcherProfile.id)),
op: 'replace', getFirstSucceededRemoteDataPayload()
value: visible );
}; }
return this.patch(researcherProfile, [replaceOperation]).pipe (
switchMap( ( ) => this.findById(researcherProfile.id))
);
}
patch(researcherProfile: ResearcherProfile, operations: Operation[]): Observable<RemoteData<ResearcherProfile>> {
return this.dataService.patch(researcherProfile, operations);
}
/** /**
* Creates a researcher profile starting from an external source URI * Creates a researcher profile starting from an external source URI
@@ -179,4 +180,7 @@ export class ResearcherProfileService {
return this.rdbService.buildFromRequestUUID(requestId); return this.rdbService.buildFromRequestUUID(requestId);
} }
private patch(researcherProfile: ResearcherProfile, operations: Operation[]): Observable<RemoteData<ResearcherProfile>> {
return this.dataService.patch(researcherProfile, operations);
}
} }

View File

@@ -0,0 +1,215 @@
import { cold, getTestScheduler } from 'jasmine-marbles';
import { of as observableOf } from 'rxjs';
import { TestScheduler } from 'rxjs/testing';
import { ProfileClaimService } from './profile-claim.service';
import { SearchService } from '../../core/shared/search/search.service';
import { ItemSearchResult } from '../../shared/object-collection/shared/item-search-result.model';
import { SearchObjects } from '../../shared/search/models/search-objects.model';
import { Item } from '../../core/shared/item.model';
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
import { EPerson } from '../../core/eperson/models/eperson.model';
describe('ProfileClaimService', () => {
let scheduler: TestScheduler;
let service: ProfileClaimService;
let serviceAsAny: any;
let searchService: jasmine.SpyObj<SearchService>;
const eperson: EPerson = Object.assign(new EPerson(), {
id: 'id',
metadata: {
'eperson.firstname': [
{
value: 'John'
}
],
'eperson.lastname': [
{
value: 'Doe'
},
],
},
email: 'fake@email.com'
});
const item1: Item = Object.assign(new Item(), {
uuid: 'e1c51c69-896d-42dc-8221-1d5f2ad5516e',
metadata: {
'person.email': [
{
value: 'fake@email.com'
}
],
'person.familyName': [
{
value: 'Doe'
}
],
'person.givenName': [
{
value: 'John'
}
]
},
_links: {
self: {
href: 'item-href'
}
}
});
const item2: Item = Object.assign(new Item(), {
uuid: 'c8279647-1acc-41ae-b036-951d5f65649b',
metadata: {
'person.email': [
{
value: 'fake2@email.com'
}
],
'dc.title': [
{
value: 'John, Doe'
}
]
},
_links: {
self: {
href: 'item-href'
}
}
});
const item3: Item = Object.assign(new Item(), {
uuid: 'c8279647-1acc-41ae-b036-951d5f65649b',
metadata: {
'person.email': [
{
value: 'fake3@email.com'
}
],
'dc.title': [
{
value: 'John, Doe'
}
]
},
_links: {
self: {
href: 'item-href'
}
}
});
const searchResult1 = Object.assign(new ItemSearchResult(), { indexableObject: item1 });
const searchResult2 = Object.assign(new ItemSearchResult(), { indexableObject: item2 });
const searchResult3 = Object.assign(new ItemSearchResult(), { indexableObject: item3 });
const searchResult = Object.assign(new SearchObjects(), {
page: [searchResult1, searchResult2, searchResult3]
});
const emptySearchResult = Object.assign(new SearchObjects(), {
page: []
});
const searchResultRD = createSuccessfulRemoteDataObject(searchResult);
const emptyRearchResultRD = createSuccessfulRemoteDataObject(emptySearchResult);
beforeEach(() => {
scheduler = getTestScheduler();
searchService = jasmine.createSpyObj('SearchService', {
search: jasmine.createSpy('search')
});
service = new ProfileClaimService(searchService);
serviceAsAny = service;
});
describe('hasProfilesToSuggest', () => {
describe('when has suggestions', () => {
beforeEach(() => {
spyOn(service, 'search').and.returnValue(observableOf(searchResultRD));
});
it('should return true', () => {
const result = service.hasProfilesToSuggest(eperson);
const expected = cold('(a|)', {
a: true
});
expect(result).toBeObservable(expected);
});
});
describe('when has not suggestions', () => {
beforeEach(() => {
spyOn(service, 'search').and.returnValue(observableOf(emptyRearchResultRD));
});
it('should return false', () => {
const result = service.hasProfilesToSuggest(eperson);
const expected = cold('(a|)', {
a: false
});
expect(result).toBeObservable(expected);
});
});
describe('when has not valid eperson', () => {
it('should return false', () => {
const result = service.hasProfilesToSuggest(null);
const expected = cold('(a|)', {
a: false
});
expect(result).toBeObservable(expected);
});
});
});
describe('search', () => {
describe('when has search results', () => {
beforeEach(() => {
searchService.search.and.returnValue(observableOf(searchResultRD));
});
it('should return the proper search object', () => {
const result = service.search(eperson);
const expected = cold('(a|)', {
a: searchResultRD
});
expect(result).toBeObservable(expected);
});
});
describe('when has not suggestions', () => {
beforeEach(() => {
searchService.search.and.returnValue(observableOf(emptyRearchResultRD));
});
it('should return null', () => {
const result = service.search(eperson);
const expected = cold('(a|)', {
a: emptyRearchResultRD
});
expect(result).toBeObservable(expected);
});
});
describe('when has not valid eperson', () => {
it('should return null', () => {
const result = service.search(null);
const expected = cold('(a|)', {
a: null
});
expect(result).toBeObservable(expected);
});
});
});
});

View File

@@ -1,16 +1,16 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { mergeMap, take } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { ConfigurationDataService } from '../../core/data/configuration-data.service';
import { PaginatedList } from '../../core/data/paginated-list.model';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { EPerson } from '../../core/eperson/models/eperson.model'; import { EPerson } from '../../core/eperson/models/eperson.model';
import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { SearchService } from '../../core/shared/search/search.service'; import { SearchService } from '../../core/shared/search/search.service';
import { hasValue } from '../../shared/empty.util'; import { isEmpty, isNotEmpty } from '../../shared/empty.util';
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model'; import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
import { SearchResult } from '../../shared/search/models/search-result.model'; import { getFirstCompletedRemoteData } from '../../core/shared/operators';
import { getFirstSucceededRemoteData } from './../../core/shared/operators'; import { SearchObjects } from '../../shared/search/models/search-objects.model';
/** /**
* Service that handle profiles claim. * Service that handle profiles claim.
@@ -18,8 +18,7 @@ import { getFirstSucceededRemoteData } from './../../core/shared/operators';
@Injectable() @Injectable()
export class ProfileClaimService { export class ProfileClaimService {
constructor(private searchService: SearchService, constructor(private searchService: SearchService) {
private configurationService: ConfigurationDataService) {
} }
/** /**
@@ -27,27 +26,21 @@ export class ProfileClaimService {
* *
* @param eperson the eperson * @param eperson the eperson
*/ */
canClaimProfiles(eperson: EPerson): Observable<boolean> { hasProfilesToSuggest(eperson: EPerson): Observable<boolean> {
return this.search(eperson).pipe(
const query = this.personQueryData(eperson); map((rd: RemoteData<SearchObjects<DSpaceObject>>) => {
return isNotEmpty(rd) && rd.hasSucceeded && rd.payload?.page?.length > 0;
if (!hasValue(query) || query.length === 0) { })
return of(false);
}
return this.lookup(query).pipe(
mergeMap((rd: RemoteData<PaginatedList<SearchResult<DSpaceObject>>>) => of(rd.payload.totalElements > 0))
); );
} }
/** /**
* Returns profiles that could be associated with the given user. * Returns profiles that could be associated with the given user.
* @param eperson the user * @param eperson the user
*/ */
search(eperson: EPerson): Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> { search(eperson: EPerson): Observable<RemoteData<SearchObjects<DSpaceObject>>> {
const query = this.personQueryData(eperson); const query = this.personQueryData(eperson);
if (!hasValue(query) || query.length === 0) { if (isEmpty(query)) {
return of(null); return of(null);
} }
return this.lookup(query); return this.lookup(query);
@@ -57,21 +50,31 @@ export class ProfileClaimService {
* Search object by the given query. * Search object by the given query.
* @param query the query for the search * @param query the query for the search
*/ */
private lookup(query: string): Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> { private lookup(query: string): Observable<RemoteData<SearchObjects<DSpaceObject>>> {
if (!hasValue(query)) { if (isEmpty(query)) {
return of(null); return of(null);
} }
return this.searchService.search(new PaginatedSearchOptions({ return this.searchService.search(new PaginatedSearchOptions({
configuration: 'eperson_claims', configuration: 'eperson_claims',
query: query query: query
})) })).pipe(
.pipe( getFirstCompletedRemoteData()
getFirstSucceededRemoteData(), );
take(1));
} }
/**
* Return the search query for person lookup, from the given eperson
*
* @param eperson The eperson to use for the lookup
*/
private personQueryData(eperson: EPerson): string { private personQueryData(eperson: EPerson): string {
return 'dc.title:' + eperson.name; if (eperson) {
const firstname = eperson.firstMetadataValue('eperson.firstname');
const lastname = eperson.firstMetadataValue('eperson.lastname');
return 'dc.title:' + eperson.name + ' OR (person.familyName:' + lastname + ' AND person.givenName:' + firstname + ')';
} else {
return null;
}
} }
} }

View File

@@ -16,6 +16,7 @@ import { ProfilePageResearcherFormComponent } from './profile-page-researcher-fo
import { ProfileClaimService } from '../profile-claim/profile-claim.service'; import { ProfileClaimService } from '../profile-claim/profile-claim.service';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { AuthService } from 'src/app/core/auth/auth.service'; import { AuthService } from 'src/app/core/auth/auth.service';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
describe('ProfilePageResearcherFormComponent', () => { describe('ProfilePageResearcherFormComponent', () => {
@@ -51,7 +52,7 @@ describe('ProfilePageResearcherFormComponent', () => {
}); });
researcherProfileService = jasmine.createSpyObj('researcherProfileService', { researcherProfileService = jasmine.createSpyObj('researcherProfileService', {
findById: observableOf(profile), findById: createSuccessfulRemoteDataObject$(profile),
create: observableOf(profile), create: observableOf(profile),
setVisibility: observableOf(profile), setVisibility: observableOf(profile),
delete: observableOf(true), delete: observableOf(true),
@@ -61,7 +62,7 @@ describe('ProfilePageResearcherFormComponent', () => {
notificationsServiceStub = new NotificationsServiceStub(); notificationsServiceStub = new NotificationsServiceStub();
profileClaimService = jasmine.createSpyObj('profileClaimService', { profileClaimService = jasmine.createSpyObj('profileClaimService', {
canClaimProfiles: observableOf(false), hasProfilesToSuggest: observableOf(false),
}); });
} }
@@ -91,7 +92,7 @@ describe('ProfilePageResearcherFormComponent', () => {
}); });
it('should search the researcher profile for the current user', () => { it('should search the researcher profile for the current user', () => {
expect(researcherProfileService.findById).toHaveBeenCalledWith(user.id); expect(researcherProfileService.findById).toHaveBeenCalledWith(user.id, false);
}); });
describe('createProfile', () => { describe('createProfile', () => {

View File

@@ -4,17 +4,18 @@ import { Router } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { filter, mergeMap, switchMap, take, tap } from 'rxjs/operators'; import { mergeMap, switchMap, take, tap } from 'rxjs/operators';
import { getFirstCompletedRemoteData } from '../../core/shared/operators'; import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../core/shared/operators';
import { ClaimItemSelectorComponent } from '../../shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component'; import {
ClaimItemSelectorComponent
} from '../../shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component';
import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsService } from '../../shared/notifications/notifications.service';
import { AuthService } from '../../core/auth/auth.service'; import { AuthService } from '../../core/auth/auth.service';
import { EPerson } from '../../core/eperson/models/eperson.model'; import { EPerson } from '../../core/eperson/models/eperson.model';
import { ResearcherProfile } from '../../core/profile/model/researcher-profile.model'; import { ResearcherProfile } from '../../core/profile/model/researcher-profile.model';
import { ResearcherProfileService } from '../../core/profile/researcher-profile.service'; import { ResearcherProfileService } from '../../core/profile/researcher-profile.service';
import { ProfileClaimService } from '../profile-claim/profile-claim.service'; import { ProfileClaimService } from '../profile-claim/profile-claim.service';
import { isNotEmpty } from '../../shared/empty.util';
@Component({ @Component({
selector: 'ds-profile-page-researcher-form', selector: 'ds-profile-page-researcher-form',
@@ -77,10 +78,10 @@ export class ProfilePageResearcherFormComponent implements OnInit {
this.processingCreate$.next(true); this.processingCreate$.next(true);
this.authService.getAuthenticatedUserFromStore().pipe( this.authService.getAuthenticatedUserFromStore().pipe(
switchMap((currentUser) => this.profileClaimService.canClaimProfiles(currentUser))) switchMap((currentUser) => this.profileClaimService.hasProfilesToSuggest(currentUser)))
.subscribe((canClaimProfiles) => { .subscribe((hasProfilesToSuggest) => {
if (canClaimProfiles) { if (hasProfilesToSuggest) {
this.processingCreate$.next(false); this.processingCreate$.next(false);
const modal = this.modalService.open(ClaimItemSelectorComponent); const modal = this.modalService.open(ClaimItemSelectorComponent);
modal.componentInstance.dso = this.user; modal.componentInstance.dso = this.user;
@@ -174,9 +175,8 @@ export class ProfilePageResearcherFormComponent implements OnInit {
* Initializes the researcherProfile and researcherProfileItemId attributes using the profile of the current user. * Initializes the researcherProfile and researcherProfileItemId attributes using the profile of the current user.
*/ */
private initResearchProfile(): void { private initResearchProfile(): void {
this.researcherProfileService.findById(this.user.id).pipe( this.researcherProfileService.findById(this.user.id, false).pipe(
take(1), getFirstSucceededRemoteDataPayload(),
filter((researcherProfile) => isNotEmpty(researcherProfile)),
tap((researcherProfile) => this.researcherProfile$.next(researcherProfile)), tap((researcherProfile) => this.researcherProfile$.next(researcherProfile)),
mergeMap((researcherProfile) => this.researcherProfileService.findRelatedItemId(researcherProfile)), mergeMap((researcherProfile) => this.researcherProfileService.findRelatedItemId(researcherProfile)),
).subscribe((itemId: string) => { ).subscribe((itemId: string) => {

View File

@@ -11,17 +11,17 @@ import { AuthTokenInfo } from '../core/auth/models/auth-token-info.model';
import { EPersonDataService } from '../core/eperson/eperson-data.service'; import { EPersonDataService } from '../core/eperson/eperson-data.service';
import { NotificationsService } from '../shared/notifications/notifications.service'; import { NotificationsService } from '../shared/notifications/notifications.service';
import { authReducer } from '../core/auth/auth.reducer'; import { authReducer } from '../core/auth/auth.reducer';
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
import { createPaginatedList } from '../shared/testing/utils.test'; import { createPaginatedList } from '../shared/testing/utils.test';
import { BehaviorSubject, of as observableOf } from 'rxjs'; import { BehaviorSubject, of as observableOf } from 'rxjs';
import { AuthService } from '../core/auth/auth.service'; import { AuthService } from '../core/auth/auth.service';
import { RestResponse } from '../core/cache/response.models'; import { RestResponse } from '../core/cache/response.models';
import { provideMockStore } from '@ngrx/store/testing'; import { provideMockStore } from '@ngrx/store/testing';
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service'; import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
import { getTestScheduler } from 'jasmine-marbles'; import { cold, getTestScheduler } from 'jasmine-marbles';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import {ConfigurationDataService} from '../core/data/configuration-data.service'; import { ConfigurationDataService } from '../core/data/configuration-data.service';
import {ConfigurationProperty} from '../core/shared/configuration-property.model'; import { ConfigurationProperty } from '../core/shared/configuration-property.model';
describe('ProfilePageComponent', () => { describe('ProfilePageComponent', () => {
let component: ProfilePageComponent; let component: ProfilePageComponent;
@@ -30,16 +30,28 @@ describe('ProfilePageComponent', () => {
let initialState: any; let initialState: any;
let authService; let authService;
let authorizationService;
let epersonService; let epersonService;
let notificationsService; let notificationsService;
let configurationService;
const canChangePassword = new BehaviorSubject(true); const canChangePassword = new BehaviorSubject(true);
const validConfiguration = Object.assign(new ConfigurationProperty(), {
name: 'researcher-profile.entity-type',
values: [
'Person'
]
});
const emptyConfiguration = Object.assign(new ConfigurationProperty(), {
name: 'researcher-profile.entity-type',
values: []
});
function init() { function init() {
user = Object.assign(new EPerson(), { user = Object.assign(new EPerson(), {
id: 'userId', id: 'userId',
groups: createSuccessfulRemoteDataObject$(createPaginatedList([])), groups: createSuccessfulRemoteDataObject$(createPaginatedList([])),
_links: {self: {href: 'test.com/uuid/1234567654321'}} _links: { self: { href: 'test.com/uuid/1234567654321' } }
}); });
initialState = { initialState = {
core: { core: {
@@ -54,7 +66,7 @@ describe('ProfilePageComponent', () => {
} }
} }
}; };
authorizationService = jasmine.createSpyObj('authorizationService', { isAuthorized: canChangePassword });
authService = jasmine.createSpyObj('authService', { authService = jasmine.createSpyObj('authService', {
getAuthenticatedUserFromStore: observableOf(user) getAuthenticatedUserFromStore: observableOf(user)
}); });
@@ -67,6 +79,9 @@ describe('ProfilePageComponent', () => {
error: {}, error: {},
warning: {} warning: {}
}); });
configurationService = jasmine.createSpyObj('configurationDataService', {
findByPropertyName: jasmine.createSpy('findByPropertyName')
});
} }
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
@@ -82,15 +97,8 @@ describe('ProfilePageComponent', () => {
{ provide: EPersonDataService, useValue: epersonService }, { provide: EPersonDataService, useValue: epersonService },
{ provide: NotificationsService, useValue: notificationsService }, { provide: NotificationsService, useValue: notificationsService },
{ provide: AuthService, useValue: authService }, { provide: AuthService, useValue: authService },
{ provide: ConfigurationDataService, useValue: jasmine.createSpyObj('configurationDataService', { { provide: ConfigurationDataService, useValue: configurationService },
findByPropertyName: createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), { { provide: AuthorizationDataService, useValue: authorizationService },
name: 'researcher-profile.entity-type',
values: [
'Person'
]
}))
})},
{ provide: AuthorizationDataService, useValue: jasmine.createSpyObj('authorizationService', { isAuthorized: canChangePassword }) },
provideMockStore({ initialState }), provideMockStore({ initialState }),
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
@@ -100,148 +108,206 @@ describe('ProfilePageComponent', () => {
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(ProfilePageComponent); fixture = TestBed.createComponent(ProfilePageComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges();
}); });
describe('updateProfile', () => { describe('', () => {
describe('when the metadata form returns false and the security form returns true', () => {
beforeEach(() => { beforeEach(() => {
component.metadataForm = jasmine.createSpyObj('metadataForm', { configurationService.findByPropertyName.and.returnValue(createSuccessfulRemoteDataObject$(validConfiguration));
updateProfile: false fixture.detectChanges();
});
describe('updateProfile', () => {
describe('when the metadata form returns false and the security form returns true', () => {
beforeEach(() => {
component.metadataForm = jasmine.createSpyObj('metadataForm', {
updateProfile: false
});
spyOn(component, 'updateSecurity').and.returnValue(true);
component.updateProfile();
});
it('should not display a warning', () => {
expect(notificationsService.warning).not.toHaveBeenCalled();
}); });
spyOn(component, 'updateSecurity').and.returnValue(true);
component.updateProfile();
}); });
it('should not display a warning', () => { describe('when the metadata form returns true and the security form returns false', () => {
expect(notificationsService.warning).not.toHaveBeenCalled(); beforeEach(() => {
component.metadataForm = jasmine.createSpyObj('metadataForm', {
updateProfile: true
});
component.updateProfile();
});
it('should not display a warning', () => {
expect(notificationsService.warning).not.toHaveBeenCalled();
});
});
describe('when the metadata form returns true and the security form returns true', () => {
beforeEach(() => {
component.metadataForm = jasmine.createSpyObj('metadataForm', {
updateProfile: true
});
component.updateProfile();
});
it('should not display a warning', () => {
expect(notificationsService.warning).not.toHaveBeenCalled();
});
});
describe('when the metadata form returns false and the security form returns false', () => {
beforeEach(() => {
component.metadataForm = jasmine.createSpyObj('metadataForm', {
updateProfile: false
});
component.updateProfile();
});
it('should display a warning', () => {
expect(notificationsService.warning).toHaveBeenCalled();
});
}); });
}); });
describe('when the metadata form returns true and the security form returns false', () => { describe('updateSecurity', () => {
beforeEach(() => { describe('when no password value present', () => {
component.metadataForm = jasmine.createSpyObj('metadataForm', { let result;
updateProfile: true
beforeEach(() => {
component.setPasswordValue('');
result = component.updateSecurity();
});
it('should return false', () => {
expect(result).toEqual(false);
});
it('should not call epersonService.patch', () => {
expect(epersonService.patch).not.toHaveBeenCalled();
}); });
component.updateProfile();
}); });
it('should not display a warning', () => { describe('when password is filled in, but the password is invalid', () => {
expect(notificationsService.warning).not.toHaveBeenCalled(); let result;
beforeEach(() => {
component.setPasswordValue('test');
component.setInvalid(true);
result = component.updateSecurity();
});
it('should return true', () => {
expect(result).toEqual(true);
expect(epersonService.patch).not.toHaveBeenCalled();
});
});
describe('when password is filled in, and is valid', () => {
let result;
let operations;
beforeEach(() => {
component.setPasswordValue('testest');
component.setInvalid(false);
operations = [{ op: 'add', path: '/password', value: 'testest' }];
result = component.updateSecurity();
});
it('should return true', () => {
expect(result).toEqual(true);
});
it('should return call epersonService.patch', () => {
expect(epersonService.patch).toHaveBeenCalledWith(user, operations);
});
}); });
}); });
describe('when the metadata form returns true and the security form returns true', () => { describe('canChangePassword$', () => {
beforeEach(() => { describe('when the user is allowed to change their password', () => {
component.metadataForm = jasmine.createSpyObj('metadataForm', { beforeEach(() => {
updateProfile: true canChangePassword.next(true);
}); });
component.updateProfile();
});
it('should not display a warning', () => { it('should contain true', () => {
expect(notificationsService.warning).not.toHaveBeenCalled(); getTestScheduler().expectObservable(component.canChangePassword$).toBe('(a)', { a: true });
}); });
});
it('should show the security section on the page', () => {
describe('when the metadata form returns false and the security form returns false', () => { fixture.detectChanges();
beforeEach(() => { expect(fixture.debugElement.query(By.css('.security-section'))).not.toBeNull();
component.metadataForm = jasmine.createSpyObj('metadataForm', {
updateProfile: false
}); });
component.updateProfile();
}); });
it('should display a warning', () => { describe('when the user is not allowed to change their password', () => {
expect(notificationsService.warning).toHaveBeenCalled(); beforeEach(() => {
canChangePassword.next(false);
});
it('should contain false', () => {
getTestScheduler().expectObservable(component.canChangePassword$).toBe('(a)', { a: false });
});
it('should not show the security section on the page', () => {
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('.security-section'))).toBeNull();
});
}); });
}); });
}); });
describe('updateSecurity', () => { describe('isResearcherProfileEnabled', () => {
describe('when no password value present', () => {
let result; describe('when configuration service return values', () => {
beforeEach(() => { beforeEach(() => {
component.setPasswordValue(''); configurationService.findByPropertyName.and.returnValue(createSuccessfulRemoteDataObject$(validConfiguration));
fixture.detectChanges();
});
result = component.updateSecurity(); it('should return true', () => {
const result = component.isResearcherProfileEnabled();
const expected = cold('a', {
a: true
});
expect(result).toBeObservable(expected);
});
});
describe('when configuration service return no values', () => {
beforeEach(() => {
configurationService.findByPropertyName.and.returnValue(createSuccessfulRemoteDataObject$(emptyConfiguration));
fixture.detectChanges();
}); });
it('should return false', () => { it('should return false', () => {
expect(result).toEqual(false); const result = component.isResearcherProfileEnabled();
}); const expected = cold('a', {
a: false
it('should not call epersonService.patch', () => { });
expect(epersonService.patch).not.toHaveBeenCalled(); expect(result).toBeObservable(expected);
}); });
}); });
describe('when password is filled in, but the password is invalid', () => { describe('when configuration service return an error', () => {
let result;
beforeEach(() => { beforeEach(() => {
component.setPasswordValue('test'); configurationService.findByPropertyName.and.returnValue(createFailedRemoteDataObject$());
component.setInvalid(true);
result = component.updateSecurity();
});
it('should return true', () => {
expect(result).toEqual(true);
expect(epersonService.patch).not.toHaveBeenCalled();
});
});
describe('when password is filled in, and is valid', () => {
let result;
let operations;
beforeEach(() => {
component.setPasswordValue('testest');
component.setInvalid(false);
operations = [{ op: 'add', path: '/password', value: 'testest' }];
result = component.updateSecurity();
});
it('should return true', () => {
expect(result).toEqual(true);
});
it('should return call epersonService.patch', () => {
expect(epersonService.patch).toHaveBeenCalledWith(user, operations);
});
});
});
describe('canChangePassword$', () => {
describe('when the user is allowed to change their password', () => {
beforeEach(() => {
canChangePassword.next(true);
});
it('should contain true', () => {
getTestScheduler().expectObservable(component.canChangePassword$).toBe('(a)', { a: true });
});
it('should show the security section on the page', () => {
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.debugElement.query(By.css('.security-section'))).not.toBeNull();
});
});
describe('when the user is not allowed to change their password', () => {
beforeEach(() => {
canChangePassword.next(false);
}); });
it('should contain false', () => { it('should return false', () => {
getTestScheduler().expectObservable(component.canChangePassword$).toBe('(a)', { a: false }); const result = component.isResearcherProfileEnabled();
}); const expected = cold('a', {
a: false
it('should not show the security section on the page', () => { });
fixture.detectChanges(); expect(result).toBeObservable(expected);
expect(fixture.debugElement.query(By.css('.security-section'))).toBeNull();
}); });
}); });
}); });

View File

@@ -1,5 +1,5 @@
import { Component, OnInit, ViewChild } from '@angular/core'; import { Component, OnInit, ViewChild } from '@angular/core';
import {BehaviorSubject, Observable} from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { EPerson } from '../core/eperson/models/eperson.model'; import { EPerson } from '../core/eperson/models/eperson.model';
import { ProfilePageMetadataFormComponent } from './profile-page-metadata-form/profile-page-metadata-form.component'; import { ProfilePageMetadataFormComponent } from './profile-page-metadata-form/profile-page-metadata-form.component';
import { NotificationsService } from '../shared/notifications/notifications.service'; import { NotificationsService } from '../shared/notifications/notifications.service';
@@ -9,18 +9,15 @@ import { RemoteData } from '../core/data/remote-data';
import { PaginatedList } from '../core/data/paginated-list.model'; import { PaginatedList } from '../core/data/paginated-list.model';
import { filter, switchMap, tap } from 'rxjs/operators'; import { filter, switchMap, tap } from 'rxjs/operators';
import { EPersonDataService } from '../core/eperson/eperson-data.service'; import { EPersonDataService } from '../core/eperson/eperson-data.service';
import { import { getAllSucceededRemoteData, getFirstCompletedRemoteData, getRemoteDataPayload } from '../core/shared/operators';
getAllSucceededRemoteData,
getRemoteDataPayload,
getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload
} from '../core/shared/operators';
import { hasValue, isNotEmpty } from '../shared/empty.util'; import { hasValue, isNotEmpty } from '../shared/empty.util';
import { followLink } from '../shared/utils/follow-link-config.model'; import { followLink } from '../shared/utils/follow-link-config.model';
import { AuthService } from '../core/auth/auth.service'; import { AuthService } from '../core/auth/auth.service';
import { Operation } from 'fast-json-patch'; import { Operation } from 'fast-json-patch';
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service'; import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../core/data/feature-authorization/feature-id'; import { FeatureID } from '../core/data/feature-authorization/feature-id';
import {ConfigurationDataService} from '../core/data/configuration-data.service'; import { ConfigurationDataService } from '../core/data/configuration-data.service';
import { ConfigurationProperty } from '../core/shared/configuration-property.model';
@Component({ @Component({
selector: 'ds-profile-page', selector: 'ds-profile-page',
@@ -94,8 +91,10 @@ export class ProfilePageComponent implements OnInit {
this.canChangePassword$ = this.user$.pipe(switchMap((user: EPerson) => this.authorizationService.isAuthorized(FeatureID.CanChangePassword, user._links.self.href))); this.canChangePassword$ = this.user$.pipe(switchMap((user: EPerson) => this.authorizationService.isAuthorized(FeatureID.CanChangePassword, user._links.self.href)));
this.configurationService.findByPropertyName('researcher-profile.entity-type').pipe( this.configurationService.findByPropertyName('researcher-profile.entity-type').pipe(
getFirstSucceededRemoteDataPayload() getFirstCompletedRemoteData()
).subscribe(() => this.isResearcherProfileEnabled$.next(true)); ).subscribe((configRD: RemoteData<ConfigurationProperty>) => {
this.isResearcherProfileEnabled$.next(configRD.hasSucceeded && configRD.payload.values.length > 0);
});
} }
/** /**
@@ -175,8 +174,8 @@ export class ProfilePageComponent implements OnInit {
/** /**
* Returns true if the researcher profile feature is enabled, false otherwise. * Returns true if the researcher profile feature is enabled, false otherwise.
*/ */
isResearcherProfileEnabled(){ isResearcherProfileEnabled(): Observable<boolean> {
return this.isResearcherProfileEnabled$; return this.isResearcherProfileEnabled$.asObservable();
} }
} }

View File

@@ -8884,11 +8884,6 @@ ngx-ui-switch@^11.0.1:
resolved "https://registry.yarnpkg.com/ngx-ui-switch/-/ngx-ui-switch-11.0.1.tgz#c7f1e97ebe698f827a26f49951b50492b22c7839" resolved "https://registry.yarnpkg.com/ngx-ui-switch/-/ngx-ui-switch-11.0.1.tgz#c7f1e97ebe698f827a26f49951b50492b22c7839"
integrity sha512-N8QYT/wW+xJdyh/aeebTSLPA6Sgrwp69H6KAcW0XZueg/LF+FKiqyG6Po/gFHq2gDhLikwyJEMpny8sudTI08w== integrity sha512-N8QYT/wW+xJdyh/aeebTSLPA6Sgrwp69H6KAcW0XZueg/LF+FKiqyG6Po/gFHq2gDhLikwyJEMpny8sudTI08w==
nice-try@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
nice-napi@^1.0.2: nice-napi@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/nice-napi/-/nice-napi-1.0.2.tgz#dc0ab5a1eac20ce548802fc5686eaa6bc654927b" resolved "https://registry.yarnpkg.com/nice-napi/-/nice-napi-1.0.2.tgz#dc0ab5a1eac20ce548802fc5686eaa6bc654927b"