83707: Implement tests

This commit is contained in:
Yana De Pauw
2021-09-27 11:11:54 +02:00
parent 100166023a
commit 4e9bd86012
3 changed files with 253 additions and 6 deletions

View File

@@ -0,0 +1,219 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ContentSource } from '../../../../core/shared/content-source.model';
import { Collection } from '../../../../core/shared/collection.model';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
import { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { CollectionDataService } from '../../../../core/data/collection-data.service';
import { RequestService } from '../../../../core/data/request.service';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ProcessDataService } from '../../../../core/data/processes/process-data.service';
import { ScriptDataService } from '../../../../core/data/processes/script-data.service';
import { HttpClient } from '@angular/common/http';
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
import { Process } from '../../../../process-page/processes/process.model';
import { of as observableOf } from 'rxjs';
import { CollectionSourceControlsComponent } from './collection-source-controls.component';
import { Bitstream } from '../../../../core/shared/bitstream.model';
import { getTestScheduler } from 'jasmine-marbles';
import { TestScheduler } from 'rxjs/testing';
import { By } from '@angular/platform-browser';
import { VarDirective } from '../../../../shared/utils/var.directive';
describe('CollectionSourceControlsComponent', () => {
let comp: CollectionSourceControlsComponent;
let fixture: ComponentFixture<CollectionSourceControlsComponent>;
const uuid = '29481ed7-ae6b-409a-8c51-34dd347a0ce4';
let contentSource: ContentSource;
let collection: Collection;
let process: Process;
let bitstream: Bitstream;
let scriptDataService: ScriptDataService;
let processDataService: ProcessDataService;
let requestService: RequestService;
let notificationsService;
let collectionService: CollectionDataService;
let httpClient: HttpClient;
let bitstreamService: BitstreamDataService;
let scheduler: TestScheduler;
beforeEach(waitForAsync(() => {
scheduler = getTestScheduler();
contentSource = Object.assign(new ContentSource(), {
uuid: uuid,
metadataConfigs: [
{
id: 'dc',
label: 'Simple Dublin Core',
nameSpace: 'http://www.openarchives.org/OAI/2.0/oai_dc/'
},
{
id: 'qdc',
label: 'Qualified Dublin Core',
nameSpace: 'http://purl.org/dc/terms/'
},
{
id: 'dim',
label: 'DSpace Intermediate Metadata',
nameSpace: 'http://www.dspace.org/xmlns/dspace/dim'
}
],
oaiSource: 'oai-harvest-source',
oaiSetId: 'oai-set-id',
_links: {self: {href: 'contentsource-selflink'}}
});
process = Object.assign(new Process(), {
processId: 'process-id', processStatus: 'COMPLETED',
_links: {output: {href: 'output-href'}}
});
bitstream = Object.assign(new Bitstream(), {_links: {content: {href: 'content-href'}}});
collection = Object.assign(new Collection(), {
uuid: 'fake-collection-id',
_links: {self: {href: 'collection-selflink'}}
});
notificationsService = new NotificationsServiceStub();
collectionService = jasmine.createSpyObj('collectionService', {
getContentSource: createSuccessfulRemoteDataObject$(contentSource),
findByHref: createSuccessfulRemoteDataObject$(collection)
});
scriptDataService = jasmine.createSpyObj('scriptDataService', {
invoke: createSuccessfulRemoteDataObject$(process),
});
processDataService = jasmine.createSpyObj('processDataService', {
findById: createSuccessfulRemoteDataObject$(process),
});
bitstreamService = jasmine.createSpyObj('bitstreamService', {
findByHref: createSuccessfulRemoteDataObject$(bitstream),
});
httpClient = jasmine.createSpyObj('httpClient', {
get: observableOf('Script text'),
});
requestService = jasmine.createSpyObj('requestService', ['removeByHrefSubstring', 'setStaleByHrefSubstring']);
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), RouterTestingModule],
declarations: [CollectionSourceControlsComponent, VarDirective],
providers: [
{provide: ScriptDataService, useValue: scriptDataService},
{provide: ProcessDataService, useValue: processDataService},
{provide: RequestService, useValue: requestService},
{provide: NotificationsService, useValue: notificationsService},
{provide: CollectionDataService, useValue: collectionService},
{provide: HttpClient, useValue: httpClient},
{provide: BitstreamDataService, useValue: bitstreamService}
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CollectionSourceControlsComponent);
comp = fixture.componentInstance;
comp.isEnabled = true;
comp.collection = collection;
comp.shouldShow = true;
fixture.detectChanges();
});
describe('init', () => {
it('should', () => {
expect(comp).toBeTruthy();
});
});
describe('testConfiguration', () => {
it('should invoke a script and ping the resulting process until completed and show the resulting info', () => {
comp.testConfiguration(contentSource);
scheduler.flush();
expect(scriptDataService.invoke).toHaveBeenCalledWith('harvest', [
{name: '-g', value: null},
{name: '-a', value: contentSource.oaiSource},
{name: '-i', value: contentSource.oaiSetId},
], []);
expect(processDataService.findById).toHaveBeenCalledWith(process.processId, false);
expect(bitstreamService.findByHref).toHaveBeenCalledWith(process._links.output.href);
expect(notificationsService.info).toHaveBeenCalledWith(jasmine.anything() as any, 'Script text');
});
});
describe('importNow', () => {
it('should invoke a script that will start the harvest', () => {
comp.importNow();
scheduler.flush();
expect(scriptDataService.invoke).toHaveBeenCalledWith('harvest', [
{name: '-r', value: null},
{name: '-e', value: 'dspacedemo+admin@gmail.com'},
{name: '-c', value: collection.uuid},
], []);
expect(processDataService.findById).toHaveBeenCalledWith(process.processId, false);
expect(notificationsService.success).toHaveBeenCalled();
});
});
describe('the controls', () => {
it('should be shown when shouldShow is true', () => {
comp.shouldShow = true;
fixture.detectChanges();
const buttons = fixture.debugElement.queryAll(By.css('button'));
expect(buttons.length).toEqual(3);
});
it('should be shown when shouldShow is false', () => {
comp.shouldShow = false;
fixture.detectChanges();
const buttons = fixture.debugElement.queryAll(By.css('button'));
expect(buttons.length).toEqual(0);
});
it('should be disabled when isEnabled is false', () => {
comp.shouldShow = true;
comp.isEnabled = false;
fixture.detectChanges();
const buttons = fixture.debugElement.queryAll(By.css('button'));
expect(buttons[0].nativeElement.disabled).toBeTrue();
expect(buttons[1].nativeElement.disabled).toBeTrue();
expect(buttons[2].nativeElement.disabled).toBeTrue();
});
it('should be enabled when isEnabled is true', () => {
comp.shouldShow = true;
comp.isEnabled = true;
fixture.detectChanges();
const buttons = fixture.debugElement.queryAll(By.css('button'));
expect(buttons[0].nativeElement.disabled).toBeFalse();
expect(buttons[1].nativeElement.disabled).toBeFalse();
expect(buttons[2].nativeElement.disabled).toBeFalse();
});
it('should call the corresponding button when clicked', () => {
spyOn(comp, 'testConfiguration');
spyOn(comp, 'importNow');
spyOn(comp, 'resetAndReimport');
comp.shouldShow = true;
comp.isEnabled = true;
fixture.detectChanges();
const buttons = fixture.debugElement.queryAll(By.css('button'));
buttons[0].triggerEventHandler('click', null);
expect(comp.testConfiguration).toHaveBeenCalled();
buttons[1].triggerEventHandler('click', null);
expect(comp.importNow).toHaveBeenCalled();
buttons[2].triggerEventHandler('click', null);
expect(comp.resetAndReimport).toHaveBeenCalled();
});
});
});

View File

@@ -22,14 +22,28 @@ import { TranslateService } from '@ngx-translate/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
/**
* Component that contains the controls to run, reset and test the harvest
*/
@Component({ @Component({
selector: 'ds-collection-source-controls', selector: 'ds-collection-source-controls',
templateUrl: './collection-source-controls.component.html', templateUrl: './collection-source-controls.component.html',
}) })
export class CollectionSourceControlsComponent implements OnDestroy { export class CollectionSourceControlsComponent implements OnDestroy {
/**
* Should the controls be enabled.
*/
@Input() isEnabled: boolean; @Input() isEnabled: boolean;
/**
* The current collection
*/
@Input() collection: Collection; @Input() collection: Collection;
/**
* Should the control section be shown
*/
@Input() shouldShow: boolean; @Input() shouldShow: boolean;
contentSource$: Observable<ContentSource>; contentSource$: Observable<ContentSource>;
@@ -47,6 +61,7 @@ export class CollectionSourceControlsComponent implements OnDestroy {
} }
ngOnInit() { ngOnInit() {
// ensure the contentSource gets updated after being set to stale
this.contentSource$ = this.collectionService.findByHref(this.collection._links.self.href, false).pipe( this.contentSource$ = this.collectionService.findByHref(this.collection._links.self.href, false).pipe(
getAllSucceededRemoteDataPayload(), getAllSucceededRemoteDataPayload(),
switchMap((collection) => this.collectionService.getContentSource(collection.uuid, false)), switchMap((collection) => this.collectionService.getContentSource(collection.uuid, false)),
@@ -54,6 +69,10 @@ export class CollectionSourceControlsComponent implements OnDestroy {
); );
} }
/**
* Tests the provided content source's configuration.
* @param contentSource - The content source to be tested
*/
testConfiguration(contentSource) { testConfiguration(contentSource) {
this.subs.push(this.scriptDataService.invoke('harvest', [ this.subs.push(this.scriptDataService.invoke('harvest', [
{name: '-g', value: null}, {name: '-g', value: null},
@@ -63,9 +82,11 @@ export class CollectionSourceControlsComponent implements OnDestroy {
getFirstCompletedRemoteData(), getFirstCompletedRemoteData(),
tap((rd) => { tap((rd) => {
if (rd.hasFailed) { if (rd.hasFailed) {
// show a notification when the script invocation fails
this.notificationsService.error(this.translateService.get('collection.source.controls.test.submit.error')); this.notificationsService.error(this.translateService.get('collection.source.controls.test.submit.error'));
} }
}), }),
// filter out responses that aren't successful since the pinging of the process only needs to happen when the invocation was successful.
filter((rd) => rd.hasSucceeded && hasValue(rd.payload)), filter((rd) => rd.hasSucceeded && hasValue(rd.payload)),
switchMap((rd) => this.processDataService.findById(rd.payload.processId, false)), switchMap((rd) => this.processDataService.findById(rd.payload.processId, false)),
getAllCompletedRemoteData(), getAllCompletedRemoteData(),
@@ -75,6 +96,7 @@ export class CollectionSourceControlsComponent implements OnDestroy {
).subscribe((process: Process) => { ).subscribe((process: Process) => {
if (process.processStatus.toString() !== ProcessStatus[ProcessStatus.COMPLETED].toString() && if (process.processStatus.toString() !== ProcessStatus[ProcessStatus.COMPLETED].toString() &&
process.processStatus.toString() !== ProcessStatus[ProcessStatus.FAILED].toString()) { process.processStatus.toString() !== ProcessStatus[ProcessStatus.FAILED].toString()) {
// Ping the current process state every 5s
setTimeout(() => { setTimeout(() => {
this.requestService.setStaleByHrefSubstring(process._links.self.href); this.requestService.setStaleByHrefSubstring(process._links.self.href);
}, 5000); }, 5000);
@@ -83,12 +105,12 @@ export class CollectionSourceControlsComponent implements OnDestroy {
this.notificationsService.error(this.translateService.get('collection.source.controls.test.failed')); this.notificationsService.error(this.translateService.get('collection.source.controls.test.failed'));
} }
if (process.processStatus.toString() === ProcessStatus[ProcessStatus.COMPLETED].toString()) { if (process.processStatus.toString() === ProcessStatus[ProcessStatus.COMPLETED].toString()) {
this.bitstreamService.findByHref(process._links.output.href, false).pipe(getFirstSucceededRemoteDataPayload()).subscribe((bitstream) => { this.bitstreamService.findByHref(process._links.output.href).pipe(getFirstSucceededRemoteDataPayload()).subscribe((bitstream) => {
this.httpClient.get(bitstream._links.content.href, {responseType: 'text'}).subscribe((data: any) => { this.httpClient.get(bitstream._links.content.href, {responseType: 'text'}).subscribe((data: any) => {
const output = data.replaceAll(new RegExp('.*\\@(.*)', 'g'), '$1') const output = data.replaceAll(new RegExp('.*\\@(.*)', 'g'), '$1')
.replaceAll('The script has started', '') .replaceAll('The script has started', '')
.replaceAll('The script has completed', ''); .replaceAll('The script has completed', '');
this.notificationsService.success(this.translateService.get('collection.source.controls.test.completed'), output); this.notificationsService.info(this.translateService.get('collection.source.controls.test.completed'), output);
}); });
}); });
} }
@@ -96,6 +118,9 @@ export class CollectionSourceControlsComponent implements OnDestroy {
)); ));
} }
/**
* Start the harvest for the current collection
*/
importNow() { importNow() {
this.subs.push(this.scriptDataService.invoke('harvest', [ this.subs.push(this.scriptDataService.invoke('harvest', [
{name: '-r', value: null}, {name: '-r', value: null},
@@ -118,6 +143,7 @@ export class CollectionSourceControlsComponent implements OnDestroy {
).subscribe((process) => { ).subscribe((process) => {
if (process.processStatus.toString() !== ProcessStatus[ProcessStatus.COMPLETED].toString() && if (process.processStatus.toString() !== ProcessStatus[ProcessStatus.COMPLETED].toString() &&
process.processStatus.toString() !== ProcessStatus[ProcessStatus.FAILED].toString()) { process.processStatus.toString() !== ProcessStatus[ProcessStatus.FAILED].toString()) {
// Ping the current process state every 5s
setTimeout(() => { setTimeout(() => {
this.requestService.setStaleByHrefSubstring(process._links.self.href); this.requestService.setStaleByHrefSubstring(process._links.self.href);
this.requestService.setStaleByHrefSubstring(this.collection._links.self.href); this.requestService.setStaleByHrefSubstring(this.collection._links.self.href);
@@ -128,14 +154,15 @@ export class CollectionSourceControlsComponent implements OnDestroy {
} }
if (process.processStatus.toString() === ProcessStatus[ProcessStatus.COMPLETED].toString()) { if (process.processStatus.toString() === ProcessStatus[ProcessStatus.COMPLETED].toString()) {
this.notificationsService.success(this.translateService.get('collection.source.controls.import.completed')); this.notificationsService.success(this.translateService.get('collection.source.controls.import.completed'));
setTimeout(() => {
this.requestService.setStaleByHrefSubstring(this.collection._links.self.href); this.requestService.setStaleByHrefSubstring(this.collection._links.self.href);
}, 5000);
} }
} }
)); ));
} }
/**
* Reset and reimport the current collection
*/
resetAndReimport() { resetAndReimport() {
// TODO implement when a single option is present // TODO implement when a single option is present
} }

View File

@@ -62,7 +62,8 @@ describe('CollectionSourceComponent', () => {
label: 'DSpace Intermediate Metadata', label: 'DSpace Intermediate Metadata',
nameSpace: 'http://www.dspace.org/xmlns/dspace/dim' nameSpace: 'http://www.dspace.org/xmlns/dspace/dim'
} }
] ],
_links: { self: { href: 'contentsource-selflink' } }
}); });
fieldUpdate = { fieldUpdate = {
field: contentSource, field: contentSource,
@@ -115,7 +116,7 @@ describe('CollectionSourceComponent', () => {
updateContentSource: observableOf(contentSource), updateContentSource: observableOf(contentSource),
getHarvesterEndpoint: observableOf('harvester-endpoint') getHarvesterEndpoint: observableOf('harvester-endpoint')
}); });
requestService = jasmine.createSpyObj('requestService', ['removeByHrefSubstring']); requestService = jasmine.createSpyObj('requestService', ['removeByHrefSubstring', 'setStaleByHrefSubstring']);
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), RouterTestingModule], imports: [TranslateModule.forRoot(), RouterTestingModule],