From 2dd54a20599f6c9b5e226894730c6c7846bc1881 Mon Sep 17 00:00:00 2001 From: David de Boer Date: Wed, 31 Jul 2024 19:13:30 +0200 Subject: [PATCH] feat: Handle zero iterator results --- jest.config.js | 8 +++---- src/generator.ts | 4 +++- src/iterator.ts | 10 +++++++- test/iterator.test.ts | 55 ++++++++++++++++++++++++++++++------------- 4 files changed, 54 insertions(+), 23 deletions(-) diff --git a/jest.config.js b/jest.config.js index f25faf0..3efee3b 100644 --- a/jest.config.js +++ b/jest.config.js @@ -11,10 +11,10 @@ export default { coverageReporters: ['json-summary', 'text'], coverageThreshold: { global: { - lines: 70.65, - statements: 70.77, - branches: 66.66, - functions: 76.05, + lines: 71.03, + statements: 71.14, + branches: 67, + functions: 76.76, }, }, transform: { diff --git a/src/generator.ts b/src/generator.ts index 6be7457..e25132f 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -118,7 +118,9 @@ export default class Generator extends EventEmitter { } private async flush(): Promise { - await this.runBatch(this.$thisList); + if (this.$thisList.length > 0) { + await this.runBatch(this.$thisList); + } } } diff --git a/src/iterator.ts b/src/iterator.ts index b454144..79c31f4 100644 --- a/src/iterator.ts +++ b/src/iterator.ts @@ -37,7 +37,7 @@ export default class Iterator extends EventEmitter { const error = (e: unknown): Error => new Error( `The Iterator did not run successfully, it could not get the results from the endpoint ${ - this.source + this.source?.value } (offset: ${this.offset}, limit ${this.query.limit}): ${ (e as Error).message }` @@ -62,6 +62,14 @@ export default class Iterator extends EventEmitter { }); stream.on('end', () => { this.totalResults += resultsPerPage; + if (this.totalResults === 0) { + this.emit( + 'error', + error( + new Error(`no results for query:\n ${this.query.toString()}`) + ) + ); + } this.offset += this.query.limit; if (resultsPerPage < this.query.limit!) { this.emit('end', this.totalResults); diff --git a/test/iterator.test.ts b/test/iterator.test.ts index 1ef3252..4eb6604 100644 --- a/test/iterator.test.ts +++ b/test/iterator.test.ts @@ -11,6 +11,7 @@ import removeDirectory from '../src/utils/removeDir.js'; import {NamedNode} from '@rdfjs/types'; import getSPARQLQuery from '../src/utils/getSPARQLQuery.js'; import getEndpoint from '../src/utils/getEndpoint.js'; +import File from '../src/file.js'; chai.use(chaiAsPromised); describe('Iterator Class', () => { @@ -111,24 +112,9 @@ describe('Iterator Class', () => { Query.from(getSPARQLQuery(stageConfig.iterator.query, 'select')), getEndpoint(new Stage(pipeline, stageConfig)) ); - const emittedEvents: {event: string; bindings?: NamedNode}[] = []; - async function runIteratorWithPromise(): Promise { - return new Promise((resolve, reject) => { - iterator.addListener('data', bindings => { - emittedEvents.push({event: 'data', bindings}); - }); - iterator.addListener('end', () => { - emittedEvents.push({event: 'end'}); - resolve(true); - }); - iterator.addListener('error', error => { - reject(error); - }); - iterator.run(); - }); - } - await runIteratorWithPromise(); + const emittedEvents = await runIterator(iterator); + chai.expect(emittedEvents).to.have.lengthOf(154); chai.expect(emittedEvents[0].event).to.equal('data'); chai.expect(emittedEvents[0].bindings?.termType).to.equal('NamedNode'); @@ -139,6 +125,22 @@ describe('Iterator Class', () => { .expect(emittedEvents[emittedEvents.length - 1].event) .to.equal('end'); }); + + it('throws error if query returns no results', async () => { + const query = Query.from( + getSPARQLQuery( + 'SELECT $this WHERE { $this a }', + 'select' + ) + ); + const iterator = new Iterator( + query, + new File('file://static/tests/iris-small.nt') + ); + await expect(async () => await runIterator(iterator)).rejects.toThrow( + 'no results for query:\n SELECT ?this WHERE { ?this . }' + ); + }); }); }); @@ -175,3 +177,22 @@ describe('Query', () => { ); }); }); + +async function runIterator( + iterator: Iterator +): Promise<{event: string; bindings?: NamedNode}[]> { + const emittedEvents: {event: string; bindings?: NamedNode}[] = []; + return new Promise((resolve, reject) => { + iterator.on('data', bindings => { + emittedEvents.push({event: 'data', bindings}); + }); + iterator.on('end', () => { + emittedEvents.push({event: 'end'}); + resolve(emittedEvents); + }); + iterator.on('error', error => { + reject(error); + }); + iterator.run(); + }); +}