Added test for ResourcePoliciesComponent

This commit is contained in:
Giuseppe Digilio
2020-04-15 16:12:05 +02:00
parent e7d0c96a26
commit ee38de488c
4 changed files with 529 additions and 22 deletions

View File

@@ -0,0 +1,10 @@
import { ResourcePolicyService } from '../../core/resource-policy/resource-policy.service';
export function getMockResourcePolicyService(): ResourcePolicyService {
return jasmine.createSpyObj('resourcePolicyService', {
searchByResource: jasmine.createSpy('searchByResource'),
create: jasmine.createSpy('create'),
delete: jasmine.createSpy('delete'),
update: jasmine.createSpy('update')
});
}

View File

@@ -0,0 +1,469 @@
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { of as observableOf } from 'rxjs';
import { TestScheduler } from 'rxjs/testing';
import { TranslateModule } from '@ngx-translate/core';
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
import { Bitstream } from '../../core/shared/bitstream.model';
import { Bundle } from '../../core/shared/bundle.model';
import { createMockRDPaginatedObs } from '../../+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec';
import { Item } from '../../core/shared/item.model';
import { LinkService } from '../../core/cache/builders/link.service';
import { getMockLinkService } from '../mocks/mock-link-service';
import { createSuccessfulRemoteDataObject, createTestComponent } from '../testing/utils';
import { EPersonDataService } from '../../core/eperson/eperson-data.service';
import { NotificationsService } from '../notifications/notifications.service';
import { NotificationsServiceStub } from '../testing/notifications-service-stub';
import { ResourcePolicyService } from '../../core/resource-policy/resource-policy.service';
import { getMockResourcePolicyService } from '../mocks/mock-resource-policy-service';
import { GroupDataService } from '../../core/eperson/group-data.service';
import { RequestService } from '../../core/data/request.service';
import { getMockRequestService } from '../mocks/mock-request.service';
import { RouterStub } from '../testing/router-stub';
import { PaginatedList } from '../../core/data/paginated-list';
import { PageInfo } from '../../core/shared/page-info.model';
import { ResourcePoliciesComponent } from './resource-policies.component';
import { PolicyType } from '../../core/resource-policy/models/policy-type.model';
import { ActionType } from '../../core/resource-policy/models/action-type.model';
import { EPersonMock } from '../testing/eperson-mock';
import { GroupMock } from '../testing/group-mock';
describe('ResourcePoliciesComponent test suite', () => {
let comp: ResourcePoliciesComponent;
let compAsAny: any;
let fixture: ComponentFixture<ResourcePoliciesComponent>;
let de;
let routerStub: any;
let scheduler: TestScheduler;
const notificationsServiceStub = new NotificationsServiceStub();
const resourcePolicyService: any = getMockResourcePolicyService();
const linkService: any = getMockLinkService();
const resourcePolicy: any = {
id: '1',
name: null,
description: null,
policyType: PolicyType.TYPE_SUBMISSION,
action: ActionType.READ,
startDate: null,
endDate: null,
type: 'resourcepolicy',
uuid: 'resource-policy-1',
_links: {
eperson: {
href: 'https://rest.api/rest/api/eperson'
},
group: {
href: 'https://rest.api/rest/api/group'
},
self: {
href: 'https://rest.api/rest/api/resourcepolicies/1'
},
},
eperson: observableOf(createSuccessfulRemoteDataObject({})),
group: observableOf(createSuccessfulRemoteDataObject(GroupMock))
};
const anotherResourcePolicy: any = {
id: '2',
name: null,
description: null,
policyType: PolicyType.TYPE_SUBMISSION,
action: ActionType.WRITE,
startDate: null,
endDate: null,
type: 'resourcepolicy',
uuid: 'resource-policy-2',
_links: {
eperson: {
href: 'https://rest.api/rest/api/eperson'
},
group: {
href: 'https://rest.api/rest/api/group'
},
self: {
href: 'https://rest.api/rest/api/resourcepolicies/1'
},
},
eperson: observableOf(createSuccessfulRemoteDataObject(EPersonMock)),
group: observableOf(createSuccessfulRemoteDataObject({}))
};
const bitstream1 = Object.assign(new Bitstream(), {
id: 'bitstream1',
uuid: 'bitstream1'
});
const bitstream2 = Object.assign(new Bitstream(), {
id: 'bitstream2',
uuid: 'bitstream2'
});
const bitstream3 = Object.assign(new Bitstream(), {
id: 'bitstream3',
uuid: 'bitstream3'
});
const bitstream4 = Object.assign(new Bitstream(), {
id: 'bitstream4',
uuid: 'bitstream4'
});
const bundle1 = Object.assign(new Bundle(), {
id: 'bundle1',
uuid: 'bundle1',
_links: {
self: { href: 'bundle1-selflink' }
},
bitstreams: createMockRDPaginatedObs([bitstream1, bitstream2])
});
const bundle2 = Object.assign(new Bundle(), {
id: 'bundle2',
uuid: 'bundle2',
_links: {
self: { href: 'bundle2-selflink' }
},
bitstreams: createMockRDPaginatedObs([bitstream3, bitstream4])
});
const item = Object.assign(new Item(), {
uuid: 'itemUUID',
id: 'itemUUID',
_links: {
self: { href: 'item-selflink' }
},
bundles: createMockRDPaginatedObs([bundle1, bundle2])
});
const routeStub = {
data: observableOf({
item: createSuccessfulRemoteDataObject(item)
})
};
const epersonService = jasmine.createSpyObj('epersonService', {
findByHref: jasmine.createSpy('findByHref'),
});
const groupService = jasmine.createSpyObj('groupService', {
findByHref: jasmine.createSpy('findByHref'),
});
routerStub = Object.assign(new RouterStub(), {
url: `url/edit`
});
const resourcePolicyEntries = [
{
id: resourcePolicy.id,
policy: resourcePolicy,
checked: false
},
{
id: anotherResourcePolicy.id,
policy: anotherResourcePolicy,
checked: false
}
];
const resourcePolicySelectedEntries = [
{
id: resourcePolicy.id,
policy: resourcePolicy,
checked: true
},
{
id: anotherResourcePolicy.id,
policy: anotherResourcePolicy,
checked: false
}
];
const pageInfo = new PageInfo();
const array = [resourcePolicy, anotherResourcePolicy];
const paginatedList = new PaginatedList(pageInfo, array);
const resourcePolicyRD = createSuccessfulRemoteDataObject(resourcePolicy);
const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList);
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
TranslateModule.forRoot()
],
declarations: [
ResourcePoliciesComponent,
TestComponent
],
providers: [
{ provide: LinkService, useValue: linkService },
{ provide: ActivatedRoute, useValue: routeStub },
{ provide: EPersonDataService, useValue: epersonService },
{ provide: GroupDataService, useValue: groupService },
{ provide: NotificationsService, useValue: notificationsServiceStub },
{ provide: ResourcePolicyService, useValue: resourcePolicyService },
{ provide: RequestService, useValue: getMockRequestService() },
{ provide: Router, useValue: routerStub },
ChangeDetectorRef,
ResourcePoliciesComponent
], schemas: [
NO_ERRORS_SCHEMA
]
}).compileComponents();
}));
describe('', () => {
let testComp: TestComponent;
let testFixture: ComponentFixture<TestComponent>;
// synchronous beforeEach
beforeEach(() => {
const html = `
<ds-resource-policies [resourceUUID]="resourceUUID" [resourceType]="resourceType"></ds-resource-policies>`;
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
testComp = testFixture.componentInstance;
});
afterEach(() => {
testFixture.destroy();
});
it('should create ResourcePoliciesComponent', inject([ResourcePoliciesComponent], (app: ResourcePoliciesComponent) => {
expect(app).toBeDefined();
}));
});
describe('', () => {
beforeEach(() => {
fixture = TestBed.createComponent(ResourcePoliciesComponent);
comp = fixture.componentInstance;
compAsAny = fixture.componentInstance;
linkService.resolveLink.and.callFake((object, link) => object);
});
afterEach(() => {
comp = null;
compAsAny = null;
de = null;
fixture.destroy();
});
it('should init component properly', () => {
spyOn(comp, 'initResourcePolicyLIst');
fixture.detectChanges();
expect(compAsAny.isActive).toBeTruthy();
expect(comp.initResourcePolicyLIst).toHaveBeenCalled();
});
it('should init resource policies list properly', () => {
compAsAny.isActive = true;
resourcePolicyService.searchByResource.and.returnValue(hot('a|', {
a: paginatedListRD
}));
scheduler = getTestScheduler();
scheduler.schedule(() => comp.initResourcePolicyLIst());
scheduler.flush();
expect(compAsAny.resourcePoliciesEntries$.value).toEqual(resourcePolicyEntries);
});
});
describe('', () => {
beforeEach(() => {
fixture = TestBed.createComponent(ResourcePoliciesComponent);
comp = fixture.componentInstance;
compAsAny = fixture.componentInstance;
linkService.resolveLink.and.callFake((object, link) => object);
compAsAny.isActive = true;
compAsAny.resourcePoliciesEntries$.next(resourcePolicyEntries);
resourcePolicyService.searchByResource.and.returnValue(observableOf({}));
spyOn(comp, 'initResourcePolicyLIst').and.callFake(() => ({}));
fixture.detectChanges();
});
afterEach(() => {
comp = null;
compAsAny = null;
de = null;
fixture.destroy();
});
it('should render a table with a row for each policy', () => {
const rows = fixture.debugElement.queryAll(By.css('table > tbody > tr'));
expect(rows.length).toBe(2);
});
describe('canDelete', () => {
it('should return false when no row is selected', () => {
expect(comp.canDelete()).toBeObservable(cold('(a|)', {
a: false
}));
});
it('should return true when al least is selected', () => {
const checkbox = fixture.debugElement.query(By.css('table > tbody > tr:nth-child(1) input'));
const event = { target: { checked: true } };
checkbox.triggerEventHandler('change', event);
expect(comp.canDelete()).toBeObservable(cold('(a|)', {
a: true
}));
});
});
describe('deleteSelectedResourcePolicies', () => {
beforeEach(() => {
compAsAny.resourcePoliciesEntries$.next(resourcePolicySelectedEntries);
fixture.detectChanges();
});
it('should notify success when delete is successful', () => {
resourcePolicyService.delete.and.returnValue(observableOf(true));
scheduler = getTestScheduler();
scheduler.schedule(() => comp.deleteSelectedResourcePolicies());
scheduler.flush();
expect(notificationsServiceStub.success).toHaveBeenCalled();
expect(comp.initResourcePolicyLIst).toHaveBeenCalled();
});
it('should notify error when delete is not successful', () => {
resourcePolicyService.delete.and.returnValue(observableOf(false));
scheduler = getTestScheduler();
scheduler.schedule(() => comp.deleteSelectedResourcePolicies());
scheduler.flush();
expect(notificationsServiceStub.error).toHaveBeenCalled();
expect(comp.initResourcePolicyLIst).toHaveBeenCalled();
});
});
it('should get the resource\'s policy list', () => {
expect(comp.getResourcePolicies()).toBeObservable(cold('a', {
a: resourcePolicyEntries
}));
});
describe('hasEPerson', () => {
it('should true when policy is link to the eperson', () => {
expect(comp.hasEPerson(anotherResourcePolicy)).toBeObservable(cold('(ab|)', {
a: false,
b: true
}));
});
it('should false when policy is not link to the eperson', () => {
expect(comp.hasEPerson(resourcePolicy)).toBeObservable(cold('(aa|)', {
a: false
}));
});
});
describe('hasGroup', () => {
it('should true when policy is link to the group', () => {
expect(comp.hasGroup(resourcePolicy)).toBeObservable(cold('(ab|)', {
a: false,
b: true
}));
});
it('should false when policy is not link to the group', () => {
expect(comp.hasGroup(anotherResourcePolicy)).toBeObservable(cold('(aa|)', {
a: false
}));
});
});
describe('getEPersonName', () => {
it('should return the eperson name', () => {
expect(comp.getEPersonName(anotherResourcePolicy)).toBeObservable(cold('(ab|)', {
a: '',
b: 'User Test'
}));
});
});
describe('getGroupName', () => {
it('should return the group name', () => {
expect(comp.getGroupName(resourcePolicy)).toBeObservable(cold('(ab|)', {
a: '',
b: 'testgroupname'
}));
});
});
it('should format date properly', () => {
expect(comp.formatDate('2020-04-14T12:00:00Z')).toBe('2020-04-14');
});
it('should select All Checkbox', () => {
spyOn(comp, 'selectAllCheckbox').and.callThrough();
const checkbox = fixture.debugElement.query(By.css('table > thead > tr:nth-child(2) input'));
const event = { target: { checked: true } };
checkbox.triggerEventHandler('change', event);
expect(comp.selectAllCheckbox).toHaveBeenCalled();
});
it('should select a Checkbox', () => {
spyOn(comp, 'selectCheckbox').and.callThrough();
const checkbox = fixture.debugElement.query(By.css('table > tbody > tr:nth-child(1) input'));
const event = { target: { checked: true } };
checkbox.triggerEventHandler('change', event);
expect(comp.selectCheckbox).toHaveBeenCalled();
});
it('should redirect to create resource policy page', () => {
comp.redirectToResourcePolicyCreatePage();
expect(compAsAny.router.navigate).toHaveBeenCalled();
});
it('should redirect to resource policy edit page', () => {
comp.redirectToResourcePolicyEditPage(resourcePolicy);
expect(compAsAny.router.navigate).toHaveBeenCalled();
});
it('should redirect to resource policy edit page', () => {
compAsAny.groupService.findByHref.and.returnValue(observableOf(createSuccessfulRemoteDataObject(GroupMock)));
comp.redirectToGroupEditPage(resourcePolicy);
expect(compAsAny.router.navigate).toHaveBeenCalled();
});
});
});
// declare a test component
@Component({
selector: 'ds-test-cmp',
template: ``
})
class TestComponent {
resourceUUID = 'itemUUID';
resourceType = 'item';
}

View File

@@ -21,6 +21,7 @@ import { EPersonDataService } from '../../core/eperson/eperson-data.service';
import { RequestService } from '../../core/data/request.service'; import { RequestService } from '../../core/data/request.service';
import { NotificationsService } from '../notifications/notifications.service'; import { NotificationsService } from '../notifications/notifications.service';
import { dateToString, stringToNgbDateStruct } from '../date.util'; import { dateToString, stringToNgbDateStruct } from '../date.util';
import { followLink } from '../utils/follow-link-config.model';
interface ResourcePolicyCheckboxEntry { interface ResourcePolicyCheckboxEntry {
id: string; id: string;
@@ -111,6 +112,11 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
this.initResourcePolicyLIst(); this.initResourcePolicyLIst();
} }
/**
* Check if there are any selected resource's policies to be deleted
*
* @return {Observable<boolean>}
*/
canDelete(): Observable<boolean> { canDelete(): Observable<boolean> {
return observableFrom(this.resourcePoliciesEntries$.value).pipe( return observableFrom(this.resourcePoliciesEntries$.value).pipe(
filter((entry: ResourcePolicyCheckboxEntry) => entry.checked), filter((entry: ResourcePolicyCheckboxEntry) => entry.checked),
@@ -120,25 +126,30 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
) )
} }
/**
* Delete the selected resource's policies
*/
deleteSelectedResourcePolicies(): void { deleteSelectedResourcePolicies(): void {
this.processingDelete$.next(true); this.processingDelete$.next(true);
const policiesToDelete: ResourcePolicyCheckboxEntry[] = this.resourcePoliciesEntries$.value const policiesToDelete: ResourcePolicyCheckboxEntry[] = this.resourcePoliciesEntries$.value
.filter((entry: ResourcePolicyCheckboxEntry) => entry.checked); .filter((entry: ResourcePolicyCheckboxEntry) => entry.checked);
observableFrom(policiesToDelete).pipe( this.subs.push(
concatMap((entry: ResourcePolicyCheckboxEntry) => this.resourcePolicyService.delete(entry.policy.id)), observableFrom(policiesToDelete).pipe(
scan((acc: any, value: any) => [...acc, ...value], []), concatMap((entry: ResourcePolicyCheckboxEntry) => this.resourcePolicyService.delete(entry.policy.id)),
filter((results: boolean[]) => results.length === policiesToDelete.length), scan((acc: any, value: any) => [...acc, ...value], []),
take(1), filter((results: boolean[]) => results.length === policiesToDelete.length),
).subscribe((results: boolean[]) => { take(1),
const failureResults = results.filter((result: boolean) => !result); ).subscribe((results: boolean[]) => {
if (isEmpty(failureResults)) { const failureResults = results.filter((result: boolean) => !result);
this.notificationsService.success(null, this.translate.get('resource-policies.delete.success.content')); if (isEmpty(failureResults)) {
} else { this.notificationsService.success(null, this.translate.get('resource-policies.delete.success.content'));
this.notificationsService.error(null, this.translate.get('resource-policies.delete.failure.content')); } else {
} this.notificationsService.error(null, this.translate.get('resource-policies.delete.failure.content'));
this.initResourcePolicyLIst(); }
this.processingDelete$.next(false); this.initResourcePolicyLIst();
}) this.processingDelete$.next(false);
})
)
} }
/** /**
@@ -158,7 +169,8 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
*/ */
getEPersonName(policy: ResourcePolicy): Observable<string> { getEPersonName(policy: ResourcePolicy): Observable<string> {
// TODO to be reviewed when https://github.com/DSpace/dspace-angular/issues/644 will be resolved // TODO to be reviewed when https://github.com/DSpace/dspace-angular/issues/644 will be resolved
return this.ePersonService.findByHref(policy._links.eperson.href).pipe( // return this.ePersonService.findByHref(policy._links.eperson.href).pipe(
return policy.eperson.pipe(
filter(() => this.isActive), filter(() => this.isActive),
getFirstSucceededRemoteDataWithNotEmptyPayload(), getFirstSucceededRemoteDataWithNotEmptyPayload(),
map((eperson: EPerson) => this.dsoNameService.getName(eperson)), map((eperson: EPerson) => this.dsoNameService.getName(eperson)),
@@ -173,7 +185,8 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
*/ */
getGroupName(policy: ResourcePolicy): Observable<string> { getGroupName(policy: ResourcePolicy): Observable<string> {
// TODO to be reviewed when https://github.com/DSpace/dspace-angular/issues/644 will be resolved // TODO to be reviewed when https://github.com/DSpace/dspace-angular/issues/644 will be resolved
return this.groupService.findByHref(policy._links.group.href).pipe( // return this.groupService.findByHref(policy._links.group.href).pipe(
return policy.group.pipe(
filter(() => this.isActive), filter(() => this.isActive),
getFirstSucceededRemoteDataWithNotEmptyPayload(), getFirstSucceededRemoteDataWithNotEmptyPayload(),
map((group: Group) => this.dsoNameService.getName(group)), map((group: Group) => this.dsoNameService.getName(group)),
@@ -198,10 +211,12 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
*/ */
hasEPerson(policy): Observable<boolean> { hasEPerson(policy): Observable<boolean> {
// TODO to be reviewed when https://github.com/DSpace/dspace-angular/issues/644 will be resolved // TODO to be reviewed when https://github.com/DSpace/dspace-angular/issues/644 will be resolved
return this.ePersonService.findByHref(policy._links.eperson.href).pipe( // return this.ePersonService.findByHref(policy._links.eperson.href).pipe(
return policy.eperson.pipe(
filter(() => this.isActive), filter(() => this.isActive),
getFirstSucceededRemoteDataPayload(), getFirstSucceededRemoteDataPayload(),
map((eperson: EPerson) => isNotEmpty(eperson)) map((eperson: EPerson) => isNotEmpty(eperson)),
startWith(false)
) )
} }
@@ -213,17 +228,23 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
*/ */
hasGroup(policy): Observable<boolean> { hasGroup(policy): Observable<boolean> {
// TODO to be reviewed when https://github.com/DSpace/dspace-angular/issues/644 will be resolved // TODO to be reviewed when https://github.com/DSpace/dspace-angular/issues/644 will be resolved
return this.groupService.findByHref(policy._links.group.href).pipe( // return this.groupService.findByHref(policy._links.group.href).pipe(
return policy.group.pipe(
filter(() => this.isActive), filter(() => this.isActive),
getFirstSucceededRemoteDataPayload(), getFirstSucceededRemoteDataPayload(),
map((group: Group) => isNotEmpty(group)) map((group: Group) => isNotEmpty(group)),
startWith(false)
) )
} }
/**
* Initialize the resource's policies list
*/
initResourcePolicyLIst() { initResourcePolicyLIst() {
// TODO to be reviewed when https://github.com/DSpace/dspace-angular/issues/644 will be resolved // TODO to be reviewed when https://github.com/DSpace/dspace-angular/issues/644 will be resolved
this.requestService.removeByHrefSubstring(this.resourceUUID); this.requestService.removeByHrefSubstring(this.resourceUUID);
this.resourcePolicyService.searchByResource(this.resourceUUID).pipe( this.resourcePolicyService.searchByResource(this.resourceUUID, null,
followLink('eperson'), followLink('group')).pipe(
filter(() => this.isActive), filter(() => this.isActive),
getSucceededRemoteData(), getSucceededRemoteData(),
take(1) take(1)
@@ -238,6 +259,11 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
}); });
} }
/**
* Return a boolean representing if a delete operation is pending
*
* @return {Observable<boolean>}
*/
isProcessingDelete(): Observable<boolean> { isProcessingDelete(): Observable<boolean> {
return this.processingDelete$.asObservable(); return this.processingDelete$.asObservable();
} }
@@ -305,6 +331,7 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
*/ */
ngOnDestroy(): void { ngOnDestroy(): void {
this.isActive = false; this.isActive = false;
this.resourcePoliciesEntries$ = null;
this.subs this.subs
.filter((subscription) => hasValue(subscription)) .filter((subscription) => hasValue(subscription))
.forEach((subscription) => subscription.unsubscribe()) .forEach((subscription) => subscription.unsubscribe())

View File

@@ -10,6 +10,7 @@ export const GroupMock: Group = Object.assign(new Group(), {
}, },
groups: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/groups' } groups: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/groups' }
}, },
_name: 'testgroupname',
id: 'testgroupid', id: 'testgroupid',
uuid: 'testgroupid', uuid: 'testgroupid',
type: 'group', type: 'group',