diff --git a/src/app/core/resource-policy/resource-policy.service.spec.ts b/src/app/core/resource-policy/resource-policy.service.spec.ts index b788a16520..e4d3358d55 100644 --- a/src/app/core/resource-policy/resource-policy.service.spec.ts +++ b/src/app/core/resource-policy/resource-policy.service.spec.ts @@ -15,12 +15,13 @@ import { ActionType } from './models/action-type.model'; import { RequestParam } from '../cache/models/request-param.model'; import { PageInfo } from '../shared/page-info.model'; import { buildPaginatedList } from '../data/paginated-list.model'; -import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; +import { createPendingRemoteDataObject, createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; import { RestResponse } from '../cache/response.models'; import { RequestEntry } from '../data/request-entry.model'; import { FindListOptions } from '../data/find-list-options.model'; import { EPersonDataService } from '../eperson/eperson-data.service'; import { GroupDataService } from '../eperson/group-data.service'; +import { RestRequestMethod } from '../data/rest-request-method'; describe('ResourcePolicyService', () => { let scheduler: TestScheduler; @@ -128,6 +129,14 @@ describe('ResourcePolicyService', () => { getBrowseEndpoint: hot('a', { a: ePersonEndpoint }), + getIDHrefObs: cold('a', { + a: 'https://rest.api/rest/api/eperson/epersons/' + epersonUUID + }), + }); + groupService = jasmine.createSpyObj('groupService', { + getIDHrefObs: cold('a', { + a: 'https://rest.api/rest/api/eperson/groups/' + groupUUID + }), }); objectCache = {} as ObjectCacheService; const notificationsService = {} as NotificationsService; @@ -153,6 +162,7 @@ describe('ResourcePolicyService', () => { spyOn((service as any).dataService, 'findByHref').and.callThrough(); spyOn((service as any).dataService, 'searchBy').and.callThrough(); spyOn((service as any).dataService, 'getSearchByHref').and.returnValue(observableOf(requestURL)); + spyOn((service as any).dataService, 'invalidateByHref').and.returnValue(observableOf(requestURL)); }); describe('create', () => { @@ -336,14 +346,56 @@ describe('ResourcePolicyService', () => { }); describe('updateTarget', () => { - it('should create a new PUT request for eperson', () => { - const targetType = 'eperson'; + beforeEach(() => { + scheduler.schedule(() => service.create(resourcePolicy, resourceUUID, epersonUUID)); + }); - const result = service.updateTarget(resourcePolicyId, requestURL, epersonUUID, targetType); - const expected = cold('a|', { - a: resourcePolicyRD - }); - expect(result).toBeObservable(expected); + it('should send a PUT request to update the EPerson', () => { + service.updateTarget(resourcePolicyId, requestURL, epersonUUID, 'eperson'); + scheduler.flush(); + + expect(requestService.send).toHaveBeenCalledWith(jasmine.objectContaining({ + method: RestRequestMethod.PUT, + uuid: requestUUID, + href: `${resourcePolicy._links.self.href}/eperson`, + body: 'https://rest.api/rest/api/eperson/epersons/' + epersonUUID, + })); + }); + + it('should send a PUT request to update the Group', () => { + service.updateTarget(resourcePolicyId, requestURL, groupUUID, 'group'); + scheduler.flush(); + + expect(requestService.send).toHaveBeenCalledWith(jasmine.objectContaining({ + method: RestRequestMethod.PUT, + uuid: requestUUID, + href: `${resourcePolicy._links.self.href}/group`, + body: 'https://rest.api/rest/api/eperson/groups/' + groupUUID, + })); + }); + + it('should invalidate the ResourcePolicy if the PUT request succeeds', () => { + service.updateTarget(resourcePolicyId, requestURL, epersonUUID, 'eperson'); + scheduler.flush(); + + expect((service as any).dataService.invalidateByHref).toHaveBeenCalledWith(resourcePolicy._links.self.href); + }); + + it('should only emit when invalidation is complete', () => { + const RD = { + p: createPendingRemoteDataObject(), + s: createSuccessfulRemoteDataObject({}), + }; + const response$ = cold('(s|)', RD); + const invalidate$ = cold('--(d|)', { d: true }); + + (rdbService.buildFromRequestUUID as any).and.returnValue(response$); + ((service as any).dataService.invalidateByHref).and.returnValue(invalidate$); + + const out$ = service.updateTarget(resourcePolicyId, requestURL, groupUUID, 'group'); + scheduler.flush(); + + expect(out$).toBeObservable(cold('--(s|)', RD)); }); }); diff --git a/src/app/core/resource-policy/resource-policy.service.ts b/src/app/core/resource-policy/resource-policy.service.ts index b0b7a6bd97..0178c460f1 100644 --- a/src/app/core/resource-policy/resource-policy.service.ts +++ b/src/app/core/resource-policy/resource-policy.service.ts @@ -3,7 +3,7 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Store } from '@ngrx/store'; -import { Observable } from 'rxjs'; +import { AsyncSubject, Observable } from 'rxjs'; import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; import { dataService } from '../cache/builders/build-decorators'; @@ -23,7 +23,7 @@ import { PaginatedList } from '../data/paginated-list.model'; import { ActionType } from './models/action-type.model'; import { RequestParam } from '../cache/models/request-param.model'; import { isNotEmpty } from '../../shared/empty.util'; -import { map, take } from 'rxjs/operators'; +import { filter, first, map, switchMap } from 'rxjs/operators'; import { NoContent } from '../shared/NoContent.model'; import { getFirstCompletedRemoteData } from '../shared/operators'; import { CoreState } from '../core-state.model'; @@ -37,7 +37,6 @@ import { HALLink } from '../shared/hal-link.model'; import { EPersonDataService } from '../eperson/eperson-data.service'; import { GroupDataService } from '../eperson/group-data.service'; - /** * A private DataService implementation to delegate specific methods to. */ @@ -241,13 +240,8 @@ export class ResourcePolicyService { * @param targetType the type of the target (eperson or group) to which the permission is being granted */ updateTarget(resourcePolicyId: string, resourcePolicyHref: string, targetUUID: string, targetType: string): Observable> { - const targetService = targetType === 'eperson' ? this.ePersonService : this.groupService; - - const targetEndpoint$ = targetService.getBrowseEndpoint().pipe( - take(1), - map((endpoint: string) =>`${endpoint}/${targetUUID}`), - ); + const targetEndpoint$ = targetService.getIDHrefObs(targetUUID); const options: HttpOptions = Object.create({}); let headers = new HttpHeaders(); @@ -256,9 +250,9 @@ export class ResourcePolicyService { const requestId = this.requestService.generateRequestId(); - this.requestService.setStaleByHrefSubstring(`${this.dataService.getLinkPath()}/${resourcePolicyId}/${targetType}`); - - targetEndpoint$.subscribe((targetEndpoint) => { + targetEndpoint$.pipe( + first(), + ).subscribe((targetEndpoint) => { const resourceEndpoint = resourcePolicyHref + '/' + targetType; const request = new PutRequest(requestId, resourceEndpoint, targetEndpoint, options); Object.assign(request, { @@ -269,8 +263,35 @@ export class ResourcePolicyService { this.requestService.send(request); }); - return this.rdbService.buildFromRequestUUID(requestId); + const response$ = this.rdbService.buildFromRequestUUID(requestId); + const invalidated$ = new AsyncSubject(); + response$.pipe( + getFirstCompletedRemoteData(), + switchMap((rd: RemoteData) => { + if (rd.hasSucceeded) { + return this.dataService.invalidateByHref(resourcePolicyHref); + } else { + return [undefined]; + } + }), + ).subscribe(() => { + invalidated$.next(true); + invalidated$.complete(); + }); + + return response$.pipe( + switchMap((rd: RemoteData) => { + if (rd.hasSucceeded) { + return invalidated$.pipe( + filter((invalidated: boolean) => invalidated), + map(() => rd) + ); + } else { + return [rd]; + } + }) + ); } }