From 2969b93b04b4ab491c541f028ad4091b3ffb7754 Mon Sep 17 00:00:00 2001 From: surilindur <16085353+surilindur@users.noreply.github.com> Date: Tue, 1 Oct 2024 15:35:22 +0200 Subject: [PATCH] Remove query init actor, fix configs to build --- .../config/config-base-adaptive.json | 3 +- .../config/config-base-normal-call.json | 10 - ...nfig-base-normal.json => config-base.json} | 0 .../config/config-bloom.json | 13 - .../config/config-default.json | 6 +- .../config/config-solid-base-adaptive.json | 62 + .../config/config-void.json | 11 - .../config/context-preprocess/actors.json | 4 +- .../config/rdf-join/actors.json | 2 +- .../{bloom.json => link-filter.json} | 4 +- .../config/rdf-metadata-extract/void.json | 11 +- .../config/rdf-parse/actors.json | 4 +- .../config/rdf-parse/mediators.json | 4 +- .../actors.json | 13 +- engines/query-sparql-components/package.json | 2 + eslint.config.cjs | 7 - package.json | 3 +- .../lib/ActorInitQuery-browser.ts | 9 - .../actor-init-query/lib/ActorInitQuery.ts | 118 - .../lib/ActorInitQueryBase.ts | 85 - .../lib/HttpServiceSparqlEndpoint.ts | 607 ------ .../actor-init-query/lib/QueryEngineBase.ts | 267 --- .../lib/QueryEngineFactoryBase.ts | 57 - .../lib/cli/CliArgsHandlerBase.ts | 267 --- .../lib/cli/CliArgsHandlerHttp.ts | 76 - .../lib/cli/CliArgsHandlerQuery.ts | 140 -- .../actor-init-query/lib/index-browser.ts | 5 - packages/actor-init-query/lib/index.ts | 11 - packages/actor-init-query/package.json | 39 - .../test/ActorInitQuery-browser-test.ts | 69 - .../test/ActorInitQuery-noSources-test.ts | 135 -- .../test/ActorInitQuery-test.ts | 1056 --------- .../test/ActorInitQueryBase-test.ts | 95 - .../test/HttpServiceSparqlEndpoint-2-test.ts | 220 -- .../test/HttpServiceSparqlEndpoint-test.ts | 1930 ----------------- .../test/QueryEngineBase-test.ts | 502 ----- .../test/QueryEngineFactoryBase-test.ts | 69 - .../test/assets/all-100.sparql | 4 - .../actor-init-query/test/assets/config.json | 3 - .../test/cli/CliArgsHandlerBase-test.ts | 53 - yarn.lock | 62 +- 41 files changed, 108 insertions(+), 5930 deletions(-) delete mode 100644 engines/config-query-sparql-components/config/config-base-normal-call.json rename engines/config-query-sparql-components/config/{config-base-normal.json => config-base.json} (100%) delete mode 100644 engines/config-query-sparql-components/config/config-bloom.json create mode 100644 engines/config-query-sparql-components/config/config-solid-base-adaptive.json delete mode 100644 engines/config-query-sparql-components/config/config-void.json rename engines/config-query-sparql-components/config/rdf-metadata-extract/{bloom.json => link-filter.json} (84%) delete mode 100644 packages/actor-init-query/lib/ActorInitQuery-browser.ts delete mode 100644 packages/actor-init-query/lib/ActorInitQuery.ts delete mode 100644 packages/actor-init-query/lib/ActorInitQueryBase.ts delete mode 100644 packages/actor-init-query/lib/HttpServiceSparqlEndpoint.ts delete mode 100644 packages/actor-init-query/lib/QueryEngineBase.ts delete mode 100644 packages/actor-init-query/lib/QueryEngineFactoryBase.ts delete mode 100644 packages/actor-init-query/lib/cli/CliArgsHandlerBase.ts delete mode 100644 packages/actor-init-query/lib/cli/CliArgsHandlerHttp.ts delete mode 100644 packages/actor-init-query/lib/cli/CliArgsHandlerQuery.ts delete mode 100644 packages/actor-init-query/lib/index-browser.ts delete mode 100644 packages/actor-init-query/lib/index.ts delete mode 100644 packages/actor-init-query/package.json delete mode 100644 packages/actor-init-query/test/ActorInitQuery-browser-test.ts delete mode 100644 packages/actor-init-query/test/ActorInitQuery-noSources-test.ts delete mode 100644 packages/actor-init-query/test/ActorInitQuery-test.ts delete mode 100644 packages/actor-init-query/test/ActorInitQueryBase-test.ts delete mode 100644 packages/actor-init-query/test/HttpServiceSparqlEndpoint-2-test.ts delete mode 100644 packages/actor-init-query/test/HttpServiceSparqlEndpoint-test.ts delete mode 100644 packages/actor-init-query/test/QueryEngineBase-test.ts delete mode 100644 packages/actor-init-query/test/QueryEngineFactoryBase-test.ts delete mode 100644 packages/actor-init-query/test/assets/all-100.sparql delete mode 100644 packages/actor-init-query/test/assets/config.json delete mode 100644 packages/actor-init-query/test/cli/CliArgsHandlerBase-test.ts diff --git a/engines/config-query-sparql-components/config/config-base-adaptive.json b/engines/config-query-sparql-components/config/config-base-adaptive.json index a04cd52..8e93a02 100644 --- a/engines/config-query-sparql-components/config/config-base-adaptive.json +++ b/engines/config-query-sparql-components/config/config-base-adaptive.json @@ -1,9 +1,10 @@ { "@context": [ + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/config-query-sparql-components/^3.0.0/components/context.jsonld", "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/config-query-sparql-link-traversal/^0.0.0/components/context.jsonld" ], "import": [ - "ccqslt:config/config-solid-base-adaptive.json", + "ccqsc:config/config-solid-base-adaptive.json", "ccqslt:config/extract-links/actors/predicates-common.json", "ccqslt:config/extract-links/actors/predicates-ldp.json", "ccqslt:config/extract-links/actors/predicates-solidstorage.json", diff --git a/engines/config-query-sparql-components/config/config-base-normal-call.json b/engines/config-query-sparql-components/config/config-base-normal-call.json deleted file mode 100644 index cdc4004..0000000 --- a/engines/config-query-sparql-components/config/config-base-normal-call.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "@context": [ - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/config-query-sparql-link-traversal/^0.0.0/components/context.jsonld" - ], - "comment": "Non-adaptive configuration using cAll for link extraction", - "import": [ - "ccqslt:config/config-solid-base.json", - "ccqslt:config/extract-links/actors/all.json" - ] -} diff --git a/engines/config-query-sparql-components/config/config-base-normal.json b/engines/config-query-sparql-components/config/config-base.json similarity index 100% rename from engines/config-query-sparql-components/config/config-base-normal.json rename to engines/config-query-sparql-components/config/config-base.json diff --git a/engines/config-query-sparql-components/config/config-bloom.json b/engines/config-query-sparql-components/config/config-bloom.json deleted file mode 100644 index cd80f4e..0000000 --- a/engines/config-query-sparql-components/config/config-bloom.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "@context": [ - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/config-query-sparql-components/^3.0.0/components/context.jsonld" - ], - "import": [ - "ccqsc:config/config-base-normal.json", - "ccqsc:config/context-preprocess/actors.json", - "ccqsc:config/rdf-metadata-extract/bloom.json", - "ccqsc:config/rdf-parse/mediators.json", - "ccqsc:config/rdf-parse/actors.json", - "ccqsc:config/rdf-resolve-hypermedia-links-queue/actors.json" - ] -} diff --git a/engines/config-query-sparql-components/config/config-default.json b/engines/config-query-sparql-components/config/config-default.json index 52327d2..4bb3bed 100644 --- a/engines/config-query-sparql-components/config/config-default.json +++ b/engines/config-query-sparql-components/config/config-default.json @@ -3,12 +3,10 @@ "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/config-query-sparql-components/^3.0.0/components/context.jsonld" ], "import": [ - "ccqsc:config/config-base-adaptive.json", + "ccqsc:config/config-base.json", "ccqsc:config/context-preprocess/actors.json", - "ccqsc:config/rdf-metadata-extract/bloom.json", + "ccqsc:config/rdf-metadata-extract/link-filter.json", "ccqsc:config/rdf-metadata-extract/void.json", - "ccqsc:config/rdf-metadata-accumulate/actors.json", - "ccqsc:config/rdf-join/actors.json", "ccqsc:config/rdf-parse/mediators.json", "ccqsc:config/rdf-parse/actors.json", "ccqsc:config/rdf-resolve-hypermedia-links-queue/actors.json" diff --git a/engines/config-query-sparql-components/config/config-solid-base-adaptive.json b/engines/config-query-sparql-components/config/config-solid-base-adaptive.json new file mode 100644 index 0000000..ca6d9cd --- /dev/null +++ b/engines/config-query-sparql-components/config/config-solid-base-adaptive.json @@ -0,0 +1,62 @@ +{ + "@context": [ + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/config-query-sparql/^3.0.0/components/context.jsonld", + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/config-query-sparql-link-traversal/^0.0.0/components/context.jsonld", + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/config-query-sparql-solid/^3.0.0/components/context.jsonld" + ], + "import": [ + "ccqs:config/context-preprocess/actors.json", + "ccqs:config/context-preprocess/mediators.json", + "ccqslt:config/extract-links/mediators.json", + "ccqs:config/hash-bindings/actors.json", + "ccqs:config/hash-bindings/mediators.json", + "ccqs:config/hash-quads/actors.json", + "ccqs:config/hash-quads/mediators.json", + "ccqss:config/http/actors.json", + "ccqs:config/http/mediators.json", + "ccqs:config/http-invalidate/actors.json", + "ccqs:config/http-invalidate/mediators.json", + "ccqslt:config/init/actors.json", + "ccqslt:config/optimize-query-operation/actors.json", + "ccqs:config/optimize-query-operation/mediators.json", + "ccqs:config/query-operation/actors.json", + "ccqs:config/query-operation/mediators.json", + "ccqs:config/query-parse/actors.json", + "ccqs:config/query-parse/mediators.json", + "ccqs:config/query-result-serialize/actors.json", + "ccqs:config/query-result-serialize/mediators.json", + "ccqs:config/dereference/actors.json", + "ccqs:config/dereference/mediators.json", + "ccqs:config/dereference-rdf/actors.json", + "ccqs:config/dereference-rdf/mediators.json", + "ccqslt:config/rdf-join/actors-adaptive.json", + "ccqs:config/rdf-join/mediators.json", + "ccqslt:config/rdf-join-entries-sort/actors-adaptive.json", + "ccqs:config/rdf-join-entries-sort/mediators.json", + "ccqs:config/rdf-join-selectivity/actors.json", + "ccqs:config/rdf-join-selectivity/mediators.json", + "ccqs:config/rdf-metadata/actors.json", + "ccqs:config/rdf-metadata/mediators.json", + "ccqs:config/rdf-metadata-accumulate/actors.json", + "ccqs:config/rdf-metadata-accumulate/mediators.json", + "ccqs:config/rdf-metadata-extract/actors.json", + "ccqs:config/rdf-metadata-extract/mediators.json", + "ccqslt:config/rdf-metadata-extract/actors/traverse.json", + "ccqs:config/rdf-parse/actors.json", + "ccqs:config/rdf-parse/mediators.json", + "ccqs:config/rdf-parse-html/actors.json", + "ccqs:config/rdf-resolve-hypermedia-links/actors.json", + "ccqslt:config/rdf-resolve-hypermedia-links/actors/traverse.json", + "ccqs:config/rdf-resolve-hypermedia-links/mediators.json", + "ccqs:config/rdf-resolve-hypermedia-links-queue/actors.json", + "ccqs:config/rdf-resolve-hypermedia-links-queue/mediators.json", + "ccqslt:config/rdf-resolve-quad-pattern/actors.json", + "ccqs:config/rdf-resolve-quad-pattern/mediators.json", + "ccqs:config/rdf-serialize/actors.json", + "ccqs:config/rdf-serialize/mediators.json", + "ccqs:config/rdf-update-hypermedia/actors.json", + "ccqs:config/rdf-update-hypermedia/mediators.json", + "ccqs:config/rdf-update-quads/actors.json", + "ccqs:config/rdf-update-quads/mediators.json" + ] +} diff --git a/engines/config-query-sparql-components/config/config-void.json b/engines/config-query-sparql-components/config/config-void.json deleted file mode 100644 index 9fc9c4b..0000000 --- a/engines/config-query-sparql-components/config/config-void.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "@context": [ - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/config-query-sparql-components/^3.0.0/components/context.jsonld" - ], - "import": [ - "ccqsc:config/config-base-adaptive.json", - "ccqsc:config/rdf-metadata-extract/void.json", - "ccqsc:config/rdf-metadata-accumulate/actors.json", - "ccqsc:config/rdf-join/actors.json" - ] -} diff --git a/engines/config-query-sparql-components/config/context-preprocess/actors.json b/engines/config-query-sparql-components/config/context-preprocess/actors.json index b564d2f..96389a1 100644 --- a/engines/config-query-sparql-components/config/context-preprocess/actors.json +++ b/engines/config-query-sparql-components/config/context-preprocess/actors.json @@ -1,7 +1,7 @@ { "@context": [ - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/runner/^2.0.0/components/context.jsonld", - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-context-preprocess-link-filter/^0.0.0/components/context.jsonld" + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/runner/^3.0.0/components/context.jsonld", + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-context-preprocess-link-filter/^3.0.0/components/context.jsonld" ], "@id": "urn:comunica:default:Runner", "@type": "Runner", diff --git a/engines/config-query-sparql-components/config/rdf-join/actors.json b/engines/config-query-sparql-components/config/rdf-join/actors.json index cbf7721..a30c5b3 100644 --- a/engines/config-query-sparql-components/config/rdf-join/actors.json +++ b/engines/config-query-sparql-components/config/rdf-join/actors.json @@ -1,6 +1,6 @@ { "@context": [ - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-rdf-join-inner-multi-adaptive-heuristics/^0.0.0/components/context.jsonld" + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-rdf-join-inner-multi-adaptive-heuristics/^3.0.0/components/context.jsonld" ], "@graph": [ { diff --git a/engines/config-query-sparql-components/config/rdf-metadata-extract/bloom.json b/engines/config-query-sparql-components/config/rdf-metadata-extract/link-filter.json similarity index 84% rename from engines/config-query-sparql-components/config/rdf-metadata-extract/bloom.json rename to engines/config-query-sparql-components/config/rdf-metadata-extract/link-filter.json index c1bfa56..d388051 100644 --- a/engines/config-query-sparql-components/config/rdf-metadata-extract/bloom.json +++ b/engines/config-query-sparql-components/config/rdf-metadata-extract/link-filter.json @@ -1,7 +1,7 @@ { "@context": [ - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/runner/^2.0.0/components/context.jsonld", - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-rdf-metadata-extract-link-filter/^0.0.0/components/context.jsonld" + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/runner/^3.0.0/components/context.jsonld", + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-rdf-metadata-extract-link-filter/^3.0.0/components/context.jsonld" ], "@id": "urn:comunica:default:Runner", "@type": "Runner", diff --git a/engines/config-query-sparql-components/config/rdf-metadata-extract/void.json b/engines/config-query-sparql-components/config/rdf-metadata-extract/void.json index 2fa59b9..ed108b7 100644 --- a/engines/config-query-sparql-components/config/rdf-metadata-extract/void.json +++ b/engines/config-query-sparql-components/config/rdf-metadata-extract/void.json @@ -1,17 +1,14 @@ { "@context": [ - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/runner/^2.0.0/components/context.jsonld", - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-rdf-metadata-extract-void-description/^0.0.0/components/context.jsonld" + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/runner/^3.0.0/components/context.jsonld", + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-rdf-metadata-extract-void/^3.0.0/components/context.jsonld" ], "@id": "urn:comunica:default:Runner", "@type": "Runner", "actors": [ { - "@id": "urn:comunica:default:rdf-metadata-extract/actors#void-description", - "@type": "ActorRdfMetadataExtractVoIDDescription", - "mediatorDereferenceRdf": { - "@id": "urn:comunica:default:dereference-rdf/mediators#main" - } + "@id": "urn:comunica:default:rdf-metadata-extract/actors#void", + "@type": "ActorRdfMetadataExtractVoid" } ] } diff --git a/engines/config-query-sparql-components/config/rdf-parse/actors.json b/engines/config-query-sparql-components/config/rdf-parse/actors.json index 2f839cb..bab2fef 100644 --- a/engines/config-query-sparql-components/config/rdf-parse/actors.json +++ b/engines/config-query-sparql-components/config/rdf-parse/actors.json @@ -1,7 +1,7 @@ { "@context": [ - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/runner/^2.0.0/components/context.jsonld", - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-rdf-parse-link-filter-bloom/^0.0.0/components/context.jsonld" + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/runner/^3.0.0/components/context.jsonld", + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-rdf-parse-link-filter-bloom/^3.0.0/components/context.jsonld" ], "@id": "urn:comunica:default:Runner", "@type": "Runner", diff --git a/engines/config-query-sparql-components/config/rdf-parse/mediators.json b/engines/config-query-sparql-components/config/rdf-parse/mediators.json index 8d76236..be113b8 100644 --- a/engines/config-query-sparql-components/config/rdf-parse/mediators.json +++ b/engines/config-query-sparql-components/config/rdf-parse/mediators.json @@ -1,7 +1,7 @@ { "@context": [ - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/bus-rdf-parse-link-filter/^0.0.0/components/context.jsonld", - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/mediator-race/^2.0.0/components/context.jsonld" + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/bus-rdf-parse-link-filter/^3.0.0/components/context.jsonld", + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/mediator-race/^3.0.0/components/context.jsonld" ], "@graph": [ { diff --git a/engines/config-query-sparql-components/config/rdf-resolve-hypermedia-links-queue/actors.json b/engines/config-query-sparql-components/config/rdf-resolve-hypermedia-links-queue/actors.json index c404a13..0dc44c2 100644 --- a/engines/config-query-sparql-components/config/rdf-resolve-hypermedia-links-queue/actors.json +++ b/engines/config-query-sparql-components/config/rdf-resolve-hypermedia-links-queue/actors.json @@ -1,7 +1,7 @@ { "@context": [ - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/runner/^2.0.0/components/context.jsonld", - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-rdf-resolve-hypermedia-links-queue-wrapper-link-filter/^0.0.0/components/context.jsonld" + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/runner/^3.0.0/components/context.jsonld", + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-rdf-resolve-hypermedia-links-queue-wrapper-link-filter/^3.0.0/components/context.jsonld" ], "@id": "urn:comunica:default:Runner", "@type": "Runner", @@ -15,8 +15,13 @@ "mediatorRdfResolveHypermediaLinksQueue": { "@id": "urn:comunica:default:rdf-resolve-hypermedia-links-queue/mediators#main" }, - "ignorePattern": "(public|private)TypeIndex$", - "alwaysReject": "^https?://www.w3.org/" + "acceptPatterns": [ + "publicTypeIndex$", + "privateTypeIndex$" + ], + "rejectPatterns": [ + "^https?://www.w3.org/" + ] } ] } diff --git a/engines/query-sparql-components/package.json b/engines/query-sparql-components/package.json index e13d92f..2f131ea 100644 --- a/engines/query-sparql-components/package.json +++ b/engines/query-sparql-components/package.json @@ -19,11 +19,13 @@ "@comunica/actor-init-query": "^3.0.0", "@comunica/actor-rdf-join-inner-multi-adaptive-heuristics": "^3.0.0", "@comunica/actor-rdf-metadata-extract-link-filter": "^3.0.0", + "@comunica/actor-rdf-metadata-extract-void": "^3.0.0", "@comunica/actor-rdf-parse-link-filter-bloom": "^3.0.0", "@comunica/actor-rdf-resolve-hypermedia-links-queue-wrapper-link-filter": "^3.0.0", "@comunica/bus-rdf-parse-link-filter": "^3.0.0", "@comunica/config-query-sparql-components": "^3.0.0", "@comunica/query-sparql-link-traversal-solid": "^0.5.0", + "@comunica/runner": "^3.0.0", "@comunica/runner-cli": "^3.0.0" } } diff --git a/eslint.config.cjs b/eslint.config.cjs index 78d3e71..a051b78 100644 --- a/eslint.config.cjs +++ b/eslint.config.cjs @@ -40,11 +40,4 @@ module.exports = config([ 'import/extensions': 'off', }, }, - { - ignores: [ - '**/*.js', - '**/*.d.ts', - 'packages/actor-init-query/**/*.ts', - ], - }, ]); diff --git a/package.json b/package.json index d9b9f7b..5c1b333 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,13 @@ "packages/*" ], "scripts": { + "clean": "git clean -dfx", "query": "node node_modules/.bin/comunica-sparql-components", "lint": "eslint .", "build": "yarn build:ts && yarn build:components && yarn build:engines", "build:ts": "tsc", "build:components": "componentsjs-generator 'engines/*' 'packages/*'", - "build:engines": "yarn workspaces foreach --all --filter 'engines/*' run prepare" + "build:engines": "yarn workspaces foreach --all --include 'engines/*' run prepare" }, "devDependencies": { "@rdfjs/types": "*", diff --git a/packages/actor-init-query/lib/ActorInitQuery-browser.ts b/packages/actor-init-query/lib/ActorInitQuery-browser.ts deleted file mode 100644 index a981b86..0000000 --- a/packages/actor-init-query/lib/ActorInitQuery-browser.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ActorInitQueryBase } from './ActorInitQueryBase'; - -/* istanbul ignore next */ -if (typeof process === 'undefined') { - // Polyfills process.nextTick for readable-stream - globalThis.process = require('process/'); -} - -export class ActorInitQuery extends ActorInitQueryBase {} diff --git a/packages/actor-init-query/lib/ActorInitQuery.ts b/packages/actor-init-query/lib/ActorInitQuery.ts deleted file mode 100644 index aef818d..0000000 --- a/packages/actor-init-query/lib/ActorInitQuery.ts +++ /dev/null @@ -1,118 +0,0 @@ -/* eslint-disable import/no-nodejs-modules */ -import { readFileSync } from 'node:fs'; -import type { IActionInit, IActorOutputInit } from '@comunica/bus-init'; -import { KeysInitQuery } from '@comunica/context-entries'; -import type { ICliArgsHandler, IQueryContextCommon } from '@comunica/types'; -import type { Readable } from 'readable-stream'; -import yargs from 'yargs'; -import type { IActorInitQueryBaseArgs } from './ActorInitQueryBase'; -import { ActorInitQueryBase } from './ActorInitQueryBase'; -import { CliArgsHandlerBase } from './cli/CliArgsHandlerBase'; -import { CliArgsHandlerQuery } from './cli/CliArgsHandlerQuery'; -import { QueryEngineBase } from './QueryEngineBase'; - -const streamifyString = require('streamify-string'); - -/** - * A comunica Query Init Actor. - */ -// eslint-disable-next-line ts/naming-convention -export class ActorInitQuery extends ActorInitQueryBase { - public constructor(args: IActorInitQueryBaseArgs) { - super(args); - } - - public override async run(action: IActionInit): Promise { - // Wrap this actor in a query engine so we can conveniently execute queries - const queryEngine = new QueryEngineBase(this); - - const cliArgsHandlers: ICliArgsHandler[] = [ - new CliArgsHandlerBase(action.context), - new CliArgsHandlerQuery( - this.defaultQueryInputFormat, - this.queryString, - this.context, - this.allowNoSources, - ), - ...( action.context?.get(KeysInitQuery.cliArgsHandlers)) || [], - ]; - - // Populate yargs arguments object - let argumentsBuilder = yargs([]); - for (const cliArgsHandler of cliArgsHandlers) { - // eslint-disable-next-line ts/no-unsafe-assignment - argumentsBuilder = cliArgsHandler.populateYargs(argumentsBuilder); - } - - // Extract raw argument values from parsed yargs object, so that we can handle each of them hereafter - let args: Record; - try { - args = await argumentsBuilder.parse(action.argv); - } catch (error: unknown) { - return { - // eslint-disable-next-line ts/no-unsafe-assignment - stderr: streamifyString(`${await argumentsBuilder.getHelp()}\n\n${( error).message}\n`), - }; - } - - // Print supported MIME types - if (args.listformats) { - const mediaTypes: Record = await queryEngine.getResultMediaTypes(); - // eslint-disable-next-line ts/no-unsafe-assignment - return { stdout: streamifyString(`${Object.keys(mediaTypes).join('\n')}\n`) }; - } - - // Define query - // We need to do this before the cliArgsHandlers, as we may modify the sources array - let query: string | undefined; - if (args.query) { - query = args.query; - } else if (args.file) { - // eslint-disable-next-line ts/no-unsafe-argument - query = readFileSync(args.file, { encoding: 'utf8' }); - } else if (args.sources.length > 0) { - // eslint-disable-next-line ts/no-unsafe-assignment - query = args.sources.at(-1); - args.sources.pop(); - } - - // Invoke args handlers to process any remaining args - const context: Record = {}; - try { - for (const cliArgsHandler of cliArgsHandlers) { - await cliArgsHandler.handleArgs(args, context); - } - } catch (error: unknown) { - return { stderr: require('streamify-string')(( error).message) }; - } - - // Evaluate query - const queryResult = await queryEngine.queryOrExplain(query!, context); - - // Output query explanations in a different way - if ('explain' in queryResult) { - return { - stdout: streamifyString(typeof queryResult.data === 'string' ? - // eslint-disable-next-line prefer-template - queryResult.data + '\n' : - // eslint-disable-next-line prefer-template - JSON.stringify(queryResult.data, (key: string, value: any) => { - if (key === 'scopedSource') { - return value.source.toString(); - } - return value; - }, ' ') + '\n'), - }; - } - - // Serialize output according to media type - const stdout: Readable = (await queryEngine.resultToString( - queryResult, - args.outputType, - queryResult.context, - )).data; - - return { stdout }; - } -} -/* eslint-enable import/no-nodejs-modules */ diff --git a/packages/actor-init-query/lib/ActorInitQueryBase.ts b/packages/actor-init-query/lib/ActorInitQueryBase.ts deleted file mode 100644 index 24a7503..0000000 --- a/packages/actor-init-query/lib/ActorInitQueryBase.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { MediatorHttpInvalidate } from '@comunica/bus-http-invalidate'; -import type { IActionInit, IActorInitArgs, IActorOutputInit } from '@comunica/bus-init'; -import { ActorInit } from '@comunica/bus-init'; -import type { MediatorQueryProcess } from '@comunica/bus-query-process'; -import type { - MediatorQueryResultSerializeHandle, - MediatorQueryResultSerializeMediaTypes, - MediatorQueryResultSerializeMediaTypeFormats, -} from '@comunica/bus-query-result-serialize'; -import type { IActorTest } from '@comunica/core'; - -/** - * A browser-safe comunica Query Init Actor. - */ -export class ActorInitQueryBase extends ActorInit implements IActorInitQueryBaseArgs { - public readonly mediatorQueryResultSerialize: MediatorQueryResultSerializeHandle; - public readonly mediatorQueryResultSerializeMediaTypeCombiner: MediatorQueryResultSerializeMediaTypes; - public readonly mediatorQueryResultSerializeMediaTypeFormatCombiner: MediatorQueryResultSerializeMediaTypeFormats; - public readonly mediatorHttpInvalidate: MediatorHttpInvalidate; - public readonly mediatorQueryProcess: MediatorQueryProcess; - - public readonly queryString?: string; - public readonly defaultQueryInputFormat?: string; - public readonly allowNoSources?: boolean; - public readonly context?: string; - - public constructor(args: IActorInitQueryBaseArgs) { - super(args); - this.mediatorQueryResultSerialize = args.mediatorQueryResultSerialize; - this.mediatorQueryResultSerializeMediaTypeCombiner = args.mediatorQueryResultSerializeMediaTypeCombiner; - this.mediatorQueryResultSerializeMediaTypeFormatCombiner = args.mediatorQueryResultSerializeMediaTypeFormatCombiner; - this.mediatorHttpInvalidate = args.mediatorHttpInvalidate; - this.mediatorQueryProcess = args.mediatorQueryProcess; - } - - public async test(_action: IActionInit): Promise { - return true; - } - - public async run(_action: IActionInit): Promise { - throw new Error('ActorInitSparql#run is not supported in the browser.'); - } -} - -export interface IActorInitQueryBaseArgs extends IActorInitArgs { - /** - * The query process mediator - */ - mediatorQueryProcess: MediatorQueryProcess; - /** - * The query serialize mediator - */ - mediatorQueryResultSerialize: MediatorQueryResultSerializeHandle; - /** - * The query serialize media type combinator - */ - mediatorQueryResultSerializeMediaTypeCombiner: MediatorQueryResultSerializeMediaTypes; - /** - * The query serialize media type format combinator - */ - mediatorQueryResultSerializeMediaTypeFormatCombiner: MediatorQueryResultSerializeMediaTypeFormats; - /** - * The HTTP cache invalidation mediator - */ - mediatorHttpInvalidate: MediatorHttpInvalidate; - - /** - * A SPARQL query string - */ - queryString?: string; - /** - * The default query input format - * @default {sparql} - */ - defaultQueryInputFormat?: string; - /** - * If it should be allowed that the user passes no sources. - * @default {false} - */ - allowNoSources?: boolean; - /** - * A JSON string of a query operation context - */ - context?: string; -} \ No newline at end of file diff --git a/packages/actor-init-query/lib/HttpServiceSparqlEndpoint.ts b/packages/actor-init-query/lib/HttpServiceSparqlEndpoint.ts deleted file mode 100644 index d47ead6..0000000 --- a/packages/actor-init-query/lib/HttpServiceSparqlEndpoint.ts +++ /dev/null @@ -1,607 +0,0 @@ -/* eslint-disable import/no-nodejs-modules, ts/no-var-requires, ts/no-require-imports */ -import type { Worker, Cluster } from 'node:cluster'; -import { createServer } from 'node:http'; -import type { IncomingMessage, ServerResponse } from 'node:http'; -import type { Writable } from 'node:stream'; -import { KeysQueryOperation } from '@comunica/context-entries'; -import type { ICliArgsHandler, QueryType, QueryStringContext, IQueryQuadsEnhanced } from '@comunica/types'; -import { ArrayIterator } from 'asynciterator'; -import { DataFactory } from 'rdf-data-factory'; -import yargs from 'yargs'; -import { CliArgsHandlerBase } from './cli/CliArgsHandlerBase'; -import { CliArgsHandlerHttp } from './cli/CliArgsHandlerHttp'; -import { QueryEngineBase } from './QueryEngineBase'; -import { QueryEngineFactoryBase } from './QueryEngineFactoryBase'; -import type { IDynamicQueryEngineOptions } from './QueryEngineFactoryBase'; - -// Cluster has to be forced a type due to some weird inconsistency -const cluster: Cluster = require('node:cluster'); - -// The negotiate package does not even have types, and could be replaced in future -const negotiate = require('negotiate'); - -// Use require instead of import for default exports, to be compatible with variants of esModuleInterop in tsconfig. -const process: NodeJS.Process = require('process/'); - -/* eslint-enable import/no-nodejs-modules, ts/no-var-requires, ts/no-require-imports */ - -const DF = new DataFactory(); - -/** - * An HTTP service that exposes a Comunica engine as a SPARQL endpoint. - */ -export class HttpServiceSparqlEndpoint { - protected readonly port: number; - protected readonly timeout: number; - protected readonly workers: number; - protected readonly context: QueryStringContext; - protected readonly invalidateCacheBeforeQuery: boolean; - protected readonly freshWorkerPerQuery: boolean; - protected readonly allowContextOverride: boolean; - protected readonly endpointPath: string = '/sparql'; - protected readonly engineFactory: QueryEngineFactoryBase; - - public constructor(args: IHttpServiceSparqlEndpointArgs) { - this.context = args.context || {}; - this.timeout = args.timeout ?? 60_000; - this.port = args.port ?? 3_000; - this.workers = args.workers ?? 1; - this.invalidateCacheBeforeQuery = Boolean(args.invalidateCacheBeforeQuery); - this.freshWorkerPerQuery = Boolean(args.freshWorkerPerQuery); - this.allowContextOverride = Boolean(args.allowContextOverride); - this.engineFactory = new QueryEngineFactoryBase( - args.moduleRootPath, - args.defaultConfigPath, - actorInitQuery => new QueryEngineBase(actorInitQuery), - ); - } - - /** - * Start the HTTP service. - * @param {Writable} stdout The output stream to log to. - * @param {Writable} stderr The error stream to log errors to. - */ - public run(stdout: Writable, stderr: Writable): Promise { - return cluster.isPrimary ? this.runPrimary(stdout, stderr) : this.runWorker(stdout, stderr); - } - - public async handleRequest( - stdout: Writable, - stderr: Writable, - request: IncomingMessage, - response: ServerResponse, - engine: QueryEngineBase, - mediaTypes: IWeighedMediaType[], - mediaTypeUris: string[], - ): Promise { - // Attempt to reconstruct the original full request URL with protocol and host - const requestProtocol = request.headers['x-forwarded-proto'] ?? 'http'; - const requestHost = request.headers['x-forwarded-host'] ?? request.headers.host ?? 'localhost'; - const requestPath = request.url ?? this.endpointPath; - const requestUrl = new URL(requestPath, `${requestProtocol}://${requestHost}`); - - // Headers that should always be sent and will not depend on the response - response.setHeader('server', 'comunica'); - response.setHeader('access-control-allow-origin', '*'); - - try { - // Requests should only be accepted at the specificed endpoint path - if (requestUrl.pathname !== this.endpointPath) { - throw new HTTPError(404, 'Not Found'); - } - - // Attempt to parse the request, and throw an error in case of failure - const operation = await this.parseOperation(requestUrl, request); - - // Cache only needs to be invalidated when the worker is not fresh - if (this.invalidateCacheBeforeQuery && !this.freshWorkerPerQuery) { - await engine.invalidateHttpCache(); - } - - // Execute the query, although this should ideally be done AFTER media type negotiation - const result: QueryType = operation.type === 'sd' ? - this.getServiceDescription(requestUrl, mediaTypeUris) : - await engine.query(operation.queryString, operation.context); - - // Attempt to negotiate a suitable result serialization format - const resultMediaType = this.negotiateResultType(request, result, mediaTypes); - - // Everything is fine thus far, so assign the status code and set content-type header - response.statusCode = 200; - response.setHeader('content-type', resultMediaType); - - // Serialize the result and pipe the output to the response, then wait for the serialization to be done - const { data } = await engine.resultToString(result, resultMediaType, this.context); - await new Promise((resolve, reject) => { - data.on('error', reject).on('end', resolve); - data.pipe(response); - }); - - stdout.write(`Worker ${process.pid} resolved to ${result.resultType} as ${resultMediaType}\n`); - } catch (error: unknown) { - if (error instanceof HTTPError) { - stderr.write(`Worker ${process.pid} resolved to HTTP error ${error.statusCode} ${error.message}\n`); - response.statusCode = error.statusCode; - } else { - const errorObject = error; - stderr.write(`Worker ${process.pid} encountered internal error\n`); - stderr.write(errorObject.stack ? `${errorObject.stack}\n` : `${errorObject.name}: ${errorObject.message}\n`); - response.statusCode = 500; - } - } - - if (!response.closed) { - stdout.write('Ending response\n'); - response.end(); - } - } - - /** - * Resolve the media type for result serialization via content negotiation. - * @param {IncomingMessage} request The incoming HTTP request. - * @param {QueryType} result The query result. - * @param {IWeighedMediaType[]} mediaTypes The list of media types and their weighs. - * @returns {string} The negotiated media type. - */ - public negotiateResultType(request: IncomingMessage, result: QueryType, mediaTypes: IWeighedMediaType[]): string { - const negotiatedMediaType = request.headers.accept ? - negotiate.choose(mediaTypes, request).sort((first: any, second: any) => second.qts - first.qts).at(0) : - null; - - // Require qts strictly larger than 2, as 1 and 2 respectively allow * and */* matching. - // For qts 0, 1, and 2, we fallback to our built-in media type defaults, for which we pass null. - let mediaType: string = negotiatedMediaType && negotiatedMediaType.qts > 2 ? negotiatedMediaType.type : null; - - // Default to SPARQL JSON for bindings and boolean - if (!mediaType) { - switch (result.resultType) { - case 'quads': - mediaType = 'application/trig'; - break; - case 'void': - mediaType = 'simple'; - break; - default: - mediaType = 'application/sparql-results+json'; - break; - } - } - return mediaType; - } - - /** - * Extracts the SPARQL protocol operation from an incoming HTTP request. - * @param {URL} url The parsed request URL. - * @param {IncomingMessage} request The incoming HTTP request. - * @returns {ISparqlOperation} The parsed SPARQL protocol operation. - */ - public async parseOperation(url: URL, request: IncomingMessage): Promise { - switch (request.method) { - case 'HEAD': - case 'GET': - if (url.searchParams.has('query')) { - return { - type: 'query', - queryString: url.searchParams.get('query')!, - context: this.parseOperationParams(url.searchParams), - }; - } - if (url.searchParams.has('update')) { - return { - type: 'update', - queryString: url.searchParams.get('update')!, - context: this.parseOperationParams(url.searchParams), - }; - } - return { - type: 'sd', - queryString: '', - context: this.parseOperationParams(url.searchParams), - }; - case 'POST': - // eslint-disable-next-line no-case-declarations - const requestBody = await this.readRequestBody(request); - if (requestBody.contentType.includes('application/sparql-query')) { - return { - type: 'query', - queryString: requestBody.content, - context: this.parseOperationParams(url.searchParams), - }; - } - if (requestBody.contentType.includes('application/sparql-update')) { - return { - type: 'update', - queryString: requestBody.content, - context: this.parseOperationParams(url.searchParams), - }; - } - if (requestBody.contentType.includes('application/x-www-form-urlencoded')) { - const requestBodyParams = new URLSearchParams(requestBody.content); - let requestBodyContext: QueryStringContext | undefined; - if (requestBodyParams.has('context')) { - try { - requestBodyContext = JSON.parse(requestBodyParams.get('context')!); - } catch { - break; - } - } - if (requestBodyParams.has('query')) { - return { - type: 'query', - queryString: requestBodyParams.get('query')!, - context: this.parseOperationParams(url.searchParams, requestBodyContext), - }; - } - if (requestBodyParams.has('update')) { - return { - type: 'update', - queryString: requestBodyParams.get('update')!, - context: this.parseOperationParams(url.searchParams, requestBodyContext), - }; - } - } - break; - default: - throw new HTTPError(501, 'Not Implemented'); - } - // If no parsed operation has been returned, and the default switch block was not reached, - // it means that no SPARQL operation has been parsed, and the request is invalid. - throw new HTTPError(400, 'Bad Request'); - } - - /** - * Reads the incoming HTTP request body into a string, using the Content-Encoding header. - * @param {IncomingMessage} request The incoming client request. - * @returns {IParsedRequestBody} The request body. - */ - public async readRequestBody(request: IncomingMessage): Promise { - return new Promise((resolve, reject) => { - if (!request.headers['content-type']) { - throw new HTTPError(400, 'Bad Request'); - } - const chunks: Uint8Array[] = []; - const encoding = request.headers['content-encoding'] ?? 'utf-8'; - request - .on('data', (chunk: Uint8Array) => chunks.push(chunk)) - .on('error', reject) - .on('close', reject) - .on('end', () => resolve({ - content: Buffer.concat(chunks).toString(encoding), - contentType: request.headers['content-type']!, - contentEncoding: encoding, - })); - }); - } - - /** - * Parses additional operation parameters from the URL search params into the context. - * @param {URLSearchParams} params The URL search parameters from user. - * @returns {QueryStringContext} The extended query string context. - */ - public parseOperationParams(params: URLSearchParams, userContext?: QueryStringContext): QueryStringContext { - const context: QueryStringContext = { ...this.context, ...this.allowContextOverride ? userContext : {}}; - if (params.has('default-graph-uri')) { - context.defaultGraphUris = params.getAll('default-graph-uri').map(uri => DF.namedNode(uri)); - } - if (params.has('named-graph-uri')) { - context.namedGraphUris = params.getAll('named-graph-uri').map(uri => DF.namedNode(uri)); - } - if (params.has('using-graph-uri')) { - context.usingGraphUris = params.getAll('using-graph-uri').map(uri => DF.namedNode(uri)); - } - if (params.has('using-named-graph-uri')) { - context.usingNamedGraphUris = params.getAll('using-named-graph-uri').map(uri => DF.namedNode(uri)); - } - return context; - } - - /** - * Gets the SPARQL service description as a quad result format for serialization. - * @param {URL} serviceUri The URI at which this service is provided. - * @param {string[]} mediaTypeUris The supported result format URIs. - * @returns {QueryQuads} The service description as query result quads. - */ - public getServiceDescription(serviceUri: URL, mediaTypeUris: string[]): IQueryQuadsEnhanced { - const sd = 'http://www.w3.org/ns/sparql-service-description#'; - const rdf = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; - const endpoint = DF.namedNode(serviceUri.href); - const quads = [ - // Basic metadata - DF.quad(endpoint, DF.namedNode(`${rdf}type`), DF.namedNode(`${sd}Service`)), - DF.quad(endpoint, DF.namedNode(`${sd}endpoint`), endpoint), - DF.quad(endpoint, DF.namedNode(`${sd}url`), endpoint), - // Features - DF.quad(endpoint, DF.namedNode(`${sd}feature`), DF.namedNode(`${sd}BasicFederatedQuery`)), - DF.quad(endpoint, DF.namedNode(`${sd}supportedLanguage`), DF.namedNode(`${sd}SPARQL10Query`)), - DF.quad(endpoint, DF.namedNode(`${sd}supportedLanguage`), DF.namedNode(`${sd}SPARQL11Query`)), - // Supported result formats - ...mediaTypeUris.map(uri => DF.quad(endpoint, DF.namedNode(`${sd}resultFormat`), DF.namedNode(uri))), - ]; - - // Return the service description as a fake query result for serialization - return { resultType: 'quads', execute: async() => new ArrayIterator(quads), metadata: undefined }; - } - - /** - * Starts the server - * @param {string[]} argv The commandline arguments that the script was called with - * @param {module:stream.internal.Writable} stdout The output stream to log to. - * @param {module:stream.internal.Writable} stderr The error stream to log errors to. - * @param {string} moduleRootPath The path to the invoking module. - * @param {NodeJS.ProcessEnv} env The process env to get constants from. - * @param {string} defaultConfigPath The path to get the config from if none is defined in the environment. - * @param {(code: number) => void} exit The callback to invoke to stop the script. - * @param {ICliArgsHandler[]} cliArgsHandlers Enables manipulation of the CLI arguments and their processing. - * @return {Promise} A promise that resolves when the server has been started. - */ - public static async runArgsInProcess( - argv: string[], - stdout: Writable, - stderr: Writable, - moduleRootPath: string, - env: NodeJS.ProcessEnv, - defaultConfigPath: string, - exit: (code: number) => void, - cliArgsHandlers: ICliArgsHandler[] = [], - ): Promise { - const options = await HttpServiceSparqlEndpoint - .generateConstructorArguments(argv, moduleRootPath, env, defaultConfigPath, stderr, exit, cliArgsHandlers); - - return new Promise((resolve) => { - new HttpServiceSparqlEndpoint(options || {}).run(stdout, stderr) - .then(resolve) - .catch((error) => { - stderr.write(error.stack ? `${error.stack}\n` : `${error}\n`); - exit(1); - resolve(); - }); - }); - } - - /** - * Takes parsed commandline arguments and turns them into an object used in the HttpServiceSparqlEndpoint constructor - * @param {args: string[]} argv The commandline arguments that the script was called with - * @param {string} moduleRootPath The path to the invoking module. - * @param {NodeJS.ProcessEnv} env The process env to get constants from. - * @param {string} defaultConfigPath The path to get the config from if none is defined in the environment. - * @param stderr The error stream. - * @param exit An exit process callback. - * @param {ICliArgsHandler[]} cliArgsHandlers Enables manipulation of the CLI arguments and their processing. - */ - public static async generateConstructorArguments( - argv: string[], - moduleRootPath: string, - env: NodeJS.ProcessEnv, - defaultConfigPath: string, - stderr: Writable, - exit: (code: number) => void, - cliArgsHandlers: ICliArgsHandler[], - ): Promise { - // Populate yargs arguments object - cliArgsHandlers = [ - new CliArgsHandlerBase(), - new CliArgsHandlerHttp(), - ...cliArgsHandlers, - ]; - let argumentsBuilder = yargs([]); - for (const cliArgsHandler of cliArgsHandlers) { - argumentsBuilder = cliArgsHandler.populateYargs(argumentsBuilder); - } - - // Extract raw argument values from parsed yargs object, so that we can handle each of them hereafter - let args: Record; - try { - args = await argumentsBuilder.parse(argv); - } catch (error: unknown) { - stderr.write(`${await argumentsBuilder.getHelp()}\n\n${( error).message}\n`); - return exit(1); - } - - // Invoke args handlers to process any remaining args - const context: Record = {}; - try { - for (const cliArgsHandler of cliArgsHandlers) { - await cliArgsHandler.handleArgs(args, context); - } - } catch (error: unknown) { - stderr.write(`${(error).message}/n`); - exit(1); - } - - const invalidateCacheBeforeQuery: boolean = args.invalidateCache; - const freshWorkerPerQuery: boolean = args.freshWorker; - const allowContextOverride: boolean = args.allowContextOverride; - const port = args.port; - const timeout = args.timeout * 1_000; - const workers = args.workers; - context[KeysQueryOperation.readOnly.name] = !args.u; - - const configPath = env.COMUNICA_CONFIG ? env.COMUNICA_CONFIG : defaultConfigPath; - - return { - defaultConfigPath, - configPath, - context, - invalidateCacheBeforeQuery, - freshWorkerPerQuery, - allowContextOverride, - moduleRootPath, - mainModulePath: moduleRootPath, - port, - timeout, - workers, - }; - } - - /** - * Start the HTTP service as master. - * @param {Writable} stdout The output stream to log to. - * @param {Writable} stderr The error stream to log errors to. - */ - public async runPrimary(stdout: Writable, stderr: Writable): Promise { - stdout.write(`Starting SPARQL endpoint service with ${this.workers} workers at \n`); - - // The primary process is responsible for terminating workers when they reach their timeout - const workerTimeouts = new Map(); - - // Create workers - for (let i = 0; i < this.workers; i++) { - workerTimeouts.set(cluster.fork(), undefined); - } - - // Attach listeners to each new worker - cluster.on('listening', (worker: Worker) => { - // Respawn crashed workers - worker.once('exit', (code: number, signal: string) => { - if (!worker.exitedAfterDisconnect) { - if (code === 9 || signal === 'SIGKILL') { - stderr.write(`Worker ${worker.process.pid} forcefully killed with ${code || signal}, killing main process\n`); - cluster.disconnect(); - } else { - stderr.write(`Worker ${worker.process.pid} terminated with ${code || signal}, starting a new one\n`); - workerTimeouts.delete(worker); - } - } - }); - worker.on('message', (message: string) => { - switch (message) { - case 'start': - stdout.write(`Worker ${worker.process.pid} received a new request\n`); - clearTimeout(workerTimeouts.get(worker)); - workerTimeouts.set(worker, setTimeout(() => { - if (!worker.isDead()) { - stdout.write(`Worker ${worker.process.pid} timed out, terminating\n`); - worker.send('terminate'); - } - }, this.timeout)); - break; - case 'end': - stdout.write(`Worker ${worker.process.pid} finished on time\n`); - clearTimeout(workerTimeouts.get(worker)); - break; - default: - stdout.write(`Worker ${worker.process.pid} sent an unknown message: ${message}\n`); - break; - } - }); - }); - - // Disconnect from cluster on SIGINT, so that the process can cleanly terminate - process.once('SIGINT', () => { - stdout.write(`Received SIGINT, terminating SPARQL endpoint\n`); - cluster.disconnect(); - }); - } - - /** - * Start the HTTP service as worker. - * @param {Writable} stdout The output stream to log to. - * @param {Writable} stderr The error stream to log errors to. - */ - public async runWorker(stdout: Writable, stderr: Writable): Promise { - // Create the engine for this worker - const engine = await this.engineFactory.create(); - - // Determine the weighed media types to use in content negotiation - const mediaTypes: Record = await engine.getResultMediaTypes(); - const mediaTypesWeighed: IWeighedMediaType[] = Object.entries(mediaTypes).map( - ([ type, quality ]) => ({ type, quality }), - ); - - // Determine the supported result formats for use in the SPARQL service description - const mediaTypeUris: string[] = Object.values(await engine.getResultMediaTypeFormats()); - - // Keep track of all open responses, to be able to terminate then when the worker is terminated - const openResponses = new Set(); - - // Handle termination of this worker - const terminateWorker = async(code = 15): Promise => { - stderr.write(`Terminating worker ${process.pid} with code ${code} and ${openResponses.size} open connections\n`); - server.close(); - await Promise.all([ - ...openResponses.values(), - ].map(connection => new Promise(resolve => connection.end(resolve)))); - // eslint-disable-next-line unicorn/no-process-exit - process.exit(code); - }; - - // Create the server with the request handler function, that has to be synchronous - const server = createServer((request: IncomingMessage, response: ServerResponse) => { - openResponses.add(response); - response.on('close', () => { - // Inform the primary process that the worker has finished - process.send!('end'); - // Remove the connection from the tracked open list - openResponses.delete(response); - // Kill the worker if we want fresh workers per query - if (this.freshWorkerPerQuery && request.method !== 'HEAD') { - terminateWorker().then().catch(error => stderr.write(error.stack ? `${error.stack}\n` : `${error.name}: ${error.message}\n`)); - } - }); - // Inform the primary process that the worker has received a request to handle - process.send!('start'); - this.handleRequest(stdout, stderr, request, response, engine, mediaTypesWeighed, mediaTypeUris) - .then().catch((error: Error) => stderr.write(error.stack ? `${error.stack}\n` : `${error.name}: ${error.message}\n`)); - }); - - // Subscribe to shutdown messages - process.on('message', (message: string) => { - switch (message) { - case 'terminate': - terminateWorker().then().catch(error => stderr.write(error.stack ? `${error.stack}\n` : `${error.name}: ${error.message}\n`)); - break; - default: - stderr.write(`Unknown message received by worker ${process.pid}: ${message}\n`); - break; - } - }); - - // Catch global errors, and cleanly close open connections - process.on('uncaughtException', (error: Error) => { - stderr.write(error.stack ? `${error.stack}\n` : `${error.name}: ${error.message}\n`); - terminateWorker().then().catch(error => stderr.write(error.stack ? `${error.stack}\n` : `${error.name}: ${error.message}\n`)); - }); - - // Start listening on the assigned port - server.listen({ port: this.port }, () => { - stdout.write(`Worker ${process.pid} listening for requests\n`); - }); - } -} - -export interface IHttpServiceSparqlEndpointArgs extends IDynamicQueryEngineOptions { - context?: any; - timeout?: number; - port?: number; - workers?: number; - invalidateCacheBeforeQuery?: boolean; - freshWorkerPerQuery?: boolean; - allowContextOverride?: boolean; - moduleRootPath: string; - defaultConfigPath: string; -} - -class HTTPError extends Error { - public readonly statusCode: number; - public constructor(statusCode: number, message: string) { - super(message); - this.statusCode = statusCode; - } -} - -interface IWeighedMediaType { - type: string; - quality: number; -} - -interface ISparqlOperation { - type: 'query' | 'update' | 'sd'; - queryString: string; - context: QueryStringContext; -} - -interface IParsedRequestBody { - content: string; - contentType: string; - contentEncoding: string; -} diff --git a/packages/actor-init-query/lib/QueryEngineBase.ts b/packages/actor-init-query/lib/QueryEngineBase.ts deleted file mode 100644 index 1e852ae..0000000 --- a/packages/actor-init-query/lib/QueryEngineBase.ts +++ /dev/null @@ -1,267 +0,0 @@ -import type { IActionSparqlSerialize, IActorQueryResultSerializeOutput } from '@comunica/bus-query-result-serialize'; -import { KeysInitQuery } from '@comunica/context-entries'; -import { ActionContext } from '@comunica/core'; -import type { - IActionContext, - IQueryOperationResult, - IQueryEngine, - IQueryExplained, - QueryFormatType, - QueryType, - QueryExplainMode, - BindingsStream, - QueryAlgebraContext, - QueryStringContext, - IQueryBindingsEnhanced, - IQueryQuadsEnhanced, - QueryEnhanced, - IQueryContextCommon, -} from '@comunica/types'; -import type * as RDF from '@rdfjs/types'; -import type { AsyncIterator } from 'asynciterator'; -import type { ActorInitQueryBase } from './ActorInitQueryBase'; - -/** - * Base implementation of a Comunica query engine. - */ -export class QueryEngineBase< - // eslint-disable-next-line unused-imports/no-unused-vars - QueryContext extends IQueryContextCommon = IQueryContextCommon, - QueryStringContextInner extends RDF.QueryStringContext = QueryStringContext, - QueryAlgebraContextInner extends RDF.QueryAlgebraContext = QueryAlgebraContext, -> -implements IQueryEngine { - private readonly actorInitQuery: ActorInitQueryBase; - - public constructor(actorInitQuery: ActorInitQueryBase) { - this.actorInitQuery = actorInitQuery; - } - - public async queryBindings( - query: QueryFormatTypeInner, - context?: QueryFormatTypeInner extends string ? QueryStringContextInner : QueryAlgebraContextInner, - ): Promise { - return this.queryOfType(query, context, 'bindings'); - } - - public async queryQuads( - query: QueryFormatTypeInner, - context?: QueryFormatTypeInner extends string ? QueryStringContextInner : QueryAlgebraContextInner, - ): Promise & RDF.ResultStream> { - return this.queryOfType(query, context, 'quads'); - } - - public async queryBoolean( - query: QueryFormatTypeInner, - context?: QueryFormatTypeInner extends string ? QueryStringContextInner : QueryAlgebraContextInner, - ): Promise { - return this.queryOfType(query, context, 'boolean'); - } - - public async queryVoid( - query: QueryFormatTypeInner, - context?: QueryFormatTypeInner extends string ? QueryStringContextInner : QueryAlgebraContextInner, - ): Promise { - return this.queryOfType(query, context, 'void'); - } - - protected async queryOfType( - query: QueryFormatTypeInner, - context: undefined | (QueryFormatTypeInner extends string ? - QueryStringContextInner : QueryAlgebraContextInner), - expectedType: QueryTypeOut['resultType'], - ): Promise> { - const result = await this.query(query, context); - if (result.resultType === expectedType) { - return > await result.execute(); - } - throw new Error(`Query result type '${expectedType}' was expected, while '${result.resultType}' was found.`); - } - - /** - * Evaluate the given query - * @param query A query string or algebra. - * @param context An optional query context. - * @return {Promise} A promise that resolves to the query output. - */ - public async query( - query: QueryFormatTypeInner, - context?: QueryFormatTypeInner extends string ? QueryStringContextInner : QueryAlgebraContextInner, - ): Promise { - const output = await this.queryOrExplain(query, context); - if ('explain' in output) { - throw new Error(`Tried to explain a query when in query-only mode`); - } - return output; - } - - /** - * Explain the given query - * @param query A query string or algebra. - * @param context An optional query context. - * @param explainMode The explain mode. - * @return {Promise} A promise that resolves to - * the query output or explanation. - */ - public async explain( - query: QueryFormatTypeInner, - context: QueryFormatTypeInner extends string ? QueryStringContextInner : QueryAlgebraContextInner, - explainMode: QueryExplainMode, - ): Promise { - context.explain = explainMode; - const output = await this.queryOrExplain(query, context); - return output; - } - - /** - * Evaluate or explain the given query - * @param query A query string or algebra. - * @param context An optional query context. - * @return {Promise} A promise that resolves to - * the query output or explanation. - */ - public async queryOrExplain( - query: QueryFormatTypeInner, - context?: QueryFormatTypeInner extends string ? QueryStringContextInner : QueryAlgebraContextInner, - ): Promise { - const actionContext: IActionContext = ActionContext.ensureActionContext(context); - - // Invalidate caches if cache argument is set to false - if (actionContext.get(KeysInitQuery.noCache)) { - await this.invalidateHttpCache(); - } - - // Invoke query process - const { result } = await this.actorInitQuery.mediatorQueryProcess.mediate({ query, context: actionContext }); - if ('explain' in result) { - return result; - } - return QueryEngineBase.internalToFinalResult(result); - } - - /** - * @param context An optional context. - * @return {Promise<{[p: string]: number}>} All available SPARQL (weighted) result media types. - */ - public async getResultMediaTypes(context?: any): Promise> { - context = ActionContext.ensureActionContext(context); - return (await this.actorInitQuery.mediatorQueryResultSerializeMediaTypeCombiner - .mediate({ context, mediaTypes: true })).mediaTypes; - } - - /** - * @param context An optional context. - * @return {Promise<{[p: string]: number}>} All available SPARQL result media type formats. - */ - public async getResultMediaTypeFormats(context?: any): Promise> { - context = ActionContext.ensureActionContext(context); - return (await this.actorInitQuery.mediatorQueryResultSerializeMediaTypeFormatCombiner - .mediate({ context, mediaTypeFormats: true })).mediaTypeFormats; - } - - /** - * Convert a query result to a string stream based on a certain media type. - * @param {IQueryOperationResult} queryResult A query result. - * @param {string} mediaType A media type. - * @param {ActionContext} context An optional context. - * @return {Promise} A text stream. - */ - public async resultToString(queryResult: RDF.Query, mediaType?: string, context?: any): - Promise { - context = ActionContext.ensureActionContext(context); - if (!mediaType) { - switch (queryResult.resultType) { - case 'bindings': - mediaType = 'application/json'; - break; - case 'quads': - mediaType = 'application/trig'; - break; - default: - mediaType = 'simple'; - break; - } - } - const handle: IActionSparqlSerialize = { ...await QueryEngineBase.finalToInternalResult(queryResult), context }; - return (await this.actorInitQuery.mediatorQueryResultSerialize - .mediate({ context, handle, handleMediaType: mediaType })).handle; - } - - /** - * Invalidate all internal caches related to the given page URL. - * If no page URL is given, then all pages will be invalidated. - * @param {string} url The page URL to invalidate. - * @param context An optional ActionContext to pass to the actors. - * @return {Promise} A promise resolving when the caches have been invalidated. - */ - public invalidateHttpCache(url?: string, context?: any): Promise { - context = ActionContext.ensureActionContext(context); - return this.actorInitQuery.mediatorHttpInvalidate.mediate({ url, context }); - } - - /** - * Convert an internal query result to a final one. - * @param internalResult An intermediary query result. - */ - public static internalToFinalResult(internalResult: IQueryOperationResult): QueryType { - switch (internalResult.type) { - case 'bindings': - return { - resultType: 'bindings', - execute: async() => internalResult.bindingsStream, - metadata: async() => await internalResult.metadata(), - context: internalResult.context, - }; - case 'quads': - return { - resultType: 'quads', - execute: async() => internalResult.quadStream, - metadata: async() => await internalResult.metadata(), - context: internalResult.context, - }; - case 'boolean': - return { - resultType: 'boolean', - execute: async() => internalResult.execute(), - context: internalResult.context, - }; - case 'void': - return { - resultType: 'void', - execute: async() => internalResult.execute(), - context: internalResult.context, - }; - } - } - - /** - * Convert a final query result to an internal one. - * @param finalResult A final query result. - */ - public static async finalToInternalResult(finalResult: RDF.Query): Promise { - switch (finalResult.resultType) { - case 'bindings': - return { - type: 'bindings', - bindingsStream: await finalResult.execute(), - metadata: async() => await finalResult.metadata(), - }; - case 'quads': - return { - type: 'quads', - quadStream: > await finalResult.execute(), - metadata: async() => await finalResult.metadata(), - }; - case 'boolean': - return { - type: 'boolean', - execute: () => finalResult.execute(), - }; - case 'void': - return { - type: 'void', - execute: () => finalResult.execute(), - }; - } - } -} \ No newline at end of file diff --git a/packages/actor-init-query/lib/QueryEngineFactoryBase.ts b/packages/actor-init-query/lib/QueryEngineFactoryBase.ts deleted file mode 100644 index 4d66f55..0000000 --- a/packages/actor-init-query/lib/QueryEngineFactoryBase.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { ISetupProperties, Runner } from '@comunica/runner'; -import { instantiateComponent } from '@comunica/runner'; -import type { ActorInitQueryBase } from './ActorInitQueryBase'; -import type { QueryEngineBase } from './QueryEngineBase'; - -/** - * A factory that can create query engines dynamically based on a given config. - */ -export class QueryEngineFactoryBase { - /** - * @param moduleRootPath The path to the invoking module. - * @param defaultConfigPath The path to the config file. - * @param queryEngineWrapper Callback for wrapping a query init actor in a query engine. - */ - public constructor( - private readonly moduleRootPath: string, - private readonly defaultConfigPath: string, - private readonly queryEngineWrapper: (actorInitQuery: ActorInitQueryBase) => Q, - ) {} - - /** - * Create a new Comunica query engine. - * @param options Optional settings on how to instantiate the query engine. - */ - public async create(options: IDynamicQueryEngineOptions = {}): Promise { - if (!options.mainModulePath) { - // This makes sure that our configuration is found by Components.js - options.mainModulePath = this.moduleRootPath; - } - const configResourceUrl: string = options.configPath ?? this.defaultConfigPath; - const instanceUri: string = options.instanceUri ?? 'urn:comunica:default:init/actors#query'; - - // Instantiate the main runner so that all other actors are instantiated as well, - // and find the SPARQL init actor with the given name - const runnerInstanceUri: string = options.runnerInstanceUri ?? 'urn:comunica:default:Runner'; - - // This needs to happen before any promise gets generated - const runner: Runner = await instantiateComponent(configResourceUrl, runnerInstanceUri, options); - const actorInitQuery = runner.collectActors({ engine: instanceUri }).engine; - return this.queryEngineWrapper(actorInitQuery); - } -} - -export interface IDynamicQueryEngineOptions extends ISetupProperties { - /** - * The path or URL to a Components.js config file. - */ - configPath?: string; - /** - * A URI identifying the component to instantiate. - */ - instanceUri?: string; - /** - * A URI identifying the runner component. - */ - runnerInstanceUri?: string; -} diff --git a/packages/actor-init-query/lib/cli/CliArgsHandlerBase.ts b/packages/actor-init-query/lib/cli/CliArgsHandlerBase.ts deleted file mode 100644 index 81cc89d..0000000 --- a/packages/actor-init-query/lib/cli/CliArgsHandlerBase.ts +++ /dev/null @@ -1,267 +0,0 @@ -/* eslint-disable import/no-nodejs-modules */ -import { exec } from 'node:child_process'; -import { existsSync, readFileSync } from 'node:fs'; -import * as OS from 'node:os'; -import * as Path from 'node:path'; -import { KeysHttp, KeysInitQuery, KeysQueryOperation, KeysRdfUpdateQuads } from '@comunica/context-entries'; -import { ActionContext } from '@comunica/core'; -import { LoggerPretty } from '@comunica/logger-pretty'; -import type { IActionContext, ICliArgsHandler } from '@comunica/types'; -import type { Argv } from 'yargs'; - -const process: NodeJS.Process = require('process/'); - -/** - * Basic CLI arguments handler that handles common options. - */ -export class CliArgsHandlerBase implements ICliArgsHandler { - private readonly initialContext?: IActionContext; - - public constructor(initialContext?: IActionContext) { - this.initialContext = initialContext; - } - - public static getScriptOutput(command: string, fallback: string): Promise { - return new Promise((resolve) => { - exec(command, (error, stdout, stderr) => { - if (error) { - resolve(fallback); - } - resolve((stdout || stderr).trimEnd()); - }); - }); - } - - public static isDevelopmentEnvironment(): boolean { - return existsSync(Path.join(__dirname, `../../test`)); - } - - /** - * Converts an URL like 'hypermedia@http://user:passwd@example.com to an IDataSource - * @param {string} sourceString An url with possibly a type and authorization. - * @return {[id: string]: any} An IDataSource which represents the sourceString. - */ - public static getSourceObjectFromString(sourceString: string): Record { - const source: Record = {}; - const mediaTypeRegex = /^([^:]*)@/u; - const mediaTypeMatches = mediaTypeRegex.exec(sourceString); - if (mediaTypeMatches) { - source.type = mediaTypeMatches[1]; - sourceString = sourceString.slice(( source.type.length) + 1); - } - const authRegex = /\/\/(.*:.*)@/u; - const authMatches = authRegex.exec(sourceString); - if (authMatches) { - const credentials = authMatches[1]; - source.context = new ActionContext({ - [KeysHttp.auth.name]: decodeURIComponent(credentials), - }); - sourceString = sourceString.slice(0, authMatches.index + 2) + - sourceString.slice(authMatches.index + credentials.length + 3); - } - source.value = sourceString; - return source; - } - - public populateYargs(argumentsBuilder: Argv): Argv { - return argumentsBuilder - .command( - '$0 [sources...]', - 'evaluates SPARQL queries', - () => { - // Do nothing - }, - () => { - // Do nothing - }, - ) - .default('sources', []) - .hide('sources') - .wrap(160) - .version(false) - .options({ - context: { - alias: 'c', - type: 'string', - describe: 'Use the given JSON context string or file (e.g., config.json)', - }, - to: { - type: 'string', - describe: 'Destination for update queries', - }, - baseIRI: { - alias: 'b', - type: 'string', - describe: 'base IRI for the query (e.g., http://example.org/)', - }, - dateTime: { - alias: 'd', - type: 'string', - describe: 'Sets a datetime for querying Memento-enabled archives', - }, - logLevel: { - alias: 'l', - type: 'string', - describe: 'Sets the log level (e.g., debug, info, warn, ...)', - default: 'warn', - }, - lenient: { - type: 'boolean', - describe: 'If failing requests and parsing errors should be logged instead of causing a hard crash', - }, - version: { - alias: 'v', - type: 'boolean', - describe: 'Prints version information', - }, - showStackTrace: { - type: 'boolean', - describe: 'Prints the full stacktrace when errors are thrown', - }, - httpTimeout: { - type: 'number', - describe: 'HTTP requests timeout in milliseconds', - }, - httpBodyTimeout: { - type: 'boolean', - describe: 'Makes the HTTP timeout take into account the response body stream read', - }, - httpRetryCount: { - type: 'number', - describe: 'The number of retries to perform on failed fetch requests', - }, - httpRetryDelay: { - type: 'number', - describe: 'The number of milliseconds to wait between fetch retries', - }, - httpRetryOnServerError: { - type: 'boolean', - describe: 'If fetch should be retried on 5xx server error responses, instead of being resolved.', - }, - unionDefaultGraph: { - type: 'boolean', - describe: 'If the default graph should also contain the union of all named graphs', - }, - noCache: { - type: 'boolean', - describe: 'If the cache should be disabled', - }, - }) - .exitProcess(false) - .fail(false) - .help(false); - } - - public async handleArgs(args: Record, context: Record): Promise { - // Print version information - if (args.version) { - // eslint-disable-next-line ts/no-require-imports,ts/no-var-requires,import/extensions - const comunicaVersion: string = require('../../package.json').version; - const dev: string = CliArgsHandlerBase.isDevelopmentEnvironment() ? '(dev)' : ''; - const nodeVersion: string = process.version; - const npmVersion: string = await CliArgsHandlerBase.getScriptOutput('npm -v', '_NPM is unavailable_'); - const yarnVersion: string = await CliArgsHandlerBase.getScriptOutput('yarn -v', '_Yarn is unavailable_'); - const os = `${OS.platform()} (${OS.type()} ${OS.release()})`; - - const message = `| software | version -| ---------------- | ------- -| Comunica Engine | ${comunicaVersion} ${dev} -| node | ${nodeVersion} -| npm | ${npmVersion} -| yarn | ${yarnVersion} -| Operating System | ${os} -`; - - throw new Error(message); - } - - // Inherit default context options - if (this.initialContext) { - Object.assign(context, this.initialContext.toJS()); - } - - // Define context - if (args.context) { - Object.assign(context, JSON.parse(existsSync(args.context) ? readFileSync(args.c, 'utf8') : args.context)); - } else if (args.sources[0]?.startsWith('{')) { - // For backwards compatibility inline JSON - Object.assign(context, JSON.parse(args.sources[0])); - args.sources.shift(); - } - - // Add sources to context - if (args.sources.length > 0) { - context.sources = context.sources || []; - // eslint-disable-next-line unicorn/no-array-for-each - args.sources.forEach((sourceValue: string) => { - const source = CliArgsHandlerBase.getSourceObjectFromString(sourceValue); - context.sources.push(source); - }); - } - - // Add destination to context - if (args.to) { - context[KeysRdfUpdateQuads.destination.name] = args.to; - } - - // Set the logger - if (!context.log) { - context.log = new LoggerPretty({ level: args.logLevel }); - } - - // Define the base IRI - if (args.baseIRI) { - context[KeysInitQuery.baseIRI.name] = args.baseIRI; - } - - // Define lenient-mode - if (args.lenient) { - context[KeysInitQuery.lenient.name] = true; - } - - // Define HTTP timeout - if (args.httpTimeout) { - context[KeysHttp.httpTimeout.name] = args.httpTimeout; - } - - // Define HTTP body timeout - if (args.httpBodyTimeout) { - if (!args.httpTimeout) { - throw new Error('The --httpBodyTimeout option requires the --httpTimeout option to be set'); - } - context[KeysHttp.httpBodyTimeout.name] = args.httpBodyTimeout; - } - - // Define HTTP retry count - if (args.httpRetryCount) { - context[KeysHttp.httpRetryCount.name] = args.httpRetryCount; - } - - // Define HTTP delay between retries - if (args.httpRetryDelay) { - if (!args.httpRetryCount) { - throw new Error('The --httpRetryDelay option requires the --httpRetryCount option to be set'); - } - context[KeysHttp.httpRetryDelay.name] = args.httpRetryDelay; - } - - // Define HTTP retry on server error response - if (args.httpRetryOnServerError) { - if (!args.httpRetryCount) { - throw new Error('The --httpRetryOnServerError option requires the --httpRetryCount option to be set'); - } - context[KeysHttp.httpRetryOnServerError.name] = args.httpRetryOnServerError; - } - - // Define union default graph - if (args.unionDefaultGraph) { - context[KeysQueryOperation.unionDefaultGraph.name] = true; - } - - // Define if cache should be disabled - if (args.noCache) { - context[KeysInitQuery.noCache.name] = true; - } - } -} -/* eslint-enable import/no-nodejs-modules */ diff --git a/packages/actor-init-query/lib/cli/CliArgsHandlerHttp.ts b/packages/actor-init-query/lib/cli/CliArgsHandlerHttp.ts deleted file mode 100644 index 6437250..0000000 --- a/packages/actor-init-query/lib/cli/CliArgsHandlerHttp.ts +++ /dev/null @@ -1,76 +0,0 @@ -import type { ICliArgsHandler } from '@comunica/types'; -import type { Argv } from 'yargs'; - -/** - * CLI arguments handler that handles options for HTTP servers. - */ -export class CliArgsHandlerHttp implements ICliArgsHandler { - public populateYargs(argumentsBuilder: Argv): Argv { - return argumentsBuilder - .usage('$0 exposes a SPARQL endpoint') - .example([ - [ `$0 https://fragments.dbpedia.org/2016-04/en`, '' ], - [ `$0 https://fragments.dbpedia.org/2016-04/en https://query.wikidata.org/sparql`, '' ], - [ `$0 hypermedia@https://fragments.dbpedia.org/2016-04/en sparql@https://query.wikidata.org/sparql`, '' ], - ]) - .options({ - port: { - alias: 'p', - type: 'number', - describe: 'HTTP port to run on', - default: 3_000, - group: 'Recommended options:', - }, - workers: { - alias: 'w', - type: 'number', - describe: 'Number of worker threads', - default: 1, - group: 'Recommended options:', - }, - timeout: { - alias: 't', - type: 'number', - describe: 'Query execution timeout in seconds', - default: 60, - group: 'Recommended options:', - }, - update: { - alias: 'u', - type: 'boolean', - describe: 'Enable update queries (otherwise, only read queries are enabled)', - default: false, - group: 'Recommended options:', - }, - invalidateCache: { - alias: 'i', - type: 'boolean', - describe: 'Enable cache invalidation before each query execution', - default: false, - }, - freshWorker: { - type: 'boolean', - describe: 'Kills the worker after each query execution', - default: false, - }, - contextOverride: { - type: 'boolean', - describe: 'If the query context can be overridden through POST requests', - default: false, - }, - }) - .check((args) => { - if (args.version) { - return true; - } - if (args.context ? args.sources.length > 0 : args.sources.length === 0) { - throw new Error('At least one source must be provided'); - } - return true; - }); - } - - public async handleArgs(_args: Record, _context: Record): Promise { - // Do nothing - } -} \ No newline at end of file diff --git a/packages/actor-init-query/lib/cli/CliArgsHandlerQuery.ts b/packages/actor-init-query/lib/cli/CliArgsHandlerQuery.ts deleted file mode 100644 index 598bee0..0000000 --- a/packages/actor-init-query/lib/cli/CliArgsHandlerQuery.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { ProxyHandlerStatic } from '@comunica/actor-http-proxy'; -import { - KeysHttpMemento, - KeysHttpProxy, - KeysHttpWayback, - KeysInitQuery, -} from '@comunica/context-entries'; -import type { ICliArgsHandler } from '@comunica/types'; -import type { Argv } from 'yargs'; - -/** - * CLI arguments handler that handles options for query execution. - */ -export class CliArgsHandlerQuery implements ICliArgsHandler { - private readonly defaultQueryInputFormat: string | undefined; - private readonly queryString: string | undefined; - private readonly context: string | undefined; - private readonly allowNoSources: boolean | undefined; - - public constructor( - defaultQueryInputFormat: string | undefined, - queryString: string | undefined, - context: string | undefined, - allowNoSources: boolean | undefined, - ) { - this.defaultQueryInputFormat = defaultQueryInputFormat; - this.queryString = queryString; - this.context = context; - this.allowNoSources = allowNoSources; - } - - public populateYargs(argumentsBuilder: Argv): Argv { - return argumentsBuilder - .usage('$0 evaluates SPARQL queries') - .example([ - [ `$0 https://fragments.dbpedia.org/2016-04/en -q 'SELECT * { ?s ?p ?o }'`, '' ], - [ `$0 https://fragments.dbpedia.org/2016-04/en -f query.sparql`, '' ], - [ `$0 https://fragments.dbpedia.org/2016-04/en https://query.wikidata.org/sparql ...`, '' ], - [ `$0 hypermedia@https://fragments.dbpedia.org/2016-04/en sparql@https://query.wikidata.org/sparql ...`, '' ], - ]) - .options({ - query: { - alias: 'q', - type: 'string', - describe: 'Evaluate the given SPARQL query string', - default: this.queryString, - group: 'Recommended options:', - }, - file: { - alias: 'f', - type: 'string', - describe: 'Evaluate the SPARQL query in the given file', - group: 'Recommended options:', - }, - inputType: { - alias: 'i', - type: 'string', - describe: 'Query input format (e.g., graphql, sparql)', - default: this.defaultQueryInputFormat, - group: 'Recommended options:', - }, - outputType: { - alias: 't', - type: 'string', - describe: 'MIME type of the output (e.g., application/json)', - group: 'Recommended options:', - }, - proxy: { - alias: 'p', - type: 'string', - describe: 'Delegates all HTTP traffic through the given proxy (e.g. http://myproxy.org/?uri=)', - }, - listformats: { - type: 'boolean', - describe: 'Prints the supported MIME types', - }, - context: { - type: 'string', - describe: 'Use the given JSON context string or file (e.g., config.json)', - default: this.context, - }, - explain: { - type: 'string', - describe: 'Print the query plan', - choices: [ - 'parsed', - 'logical', - 'physical', - 'physical-json', - ], - }, - recoverBrokenLinks: { - alias: 'r', - type: 'boolean', - describe: 'Use the WayBack machine to recover broken links', - default: false, - }, - }) - .check((args) => { - if (args.version || args.listformats) { - return true; - } - if (this.allowNoSources) { - if (!this.queryString && !(args.query ?? args.file) && args.sources.length === 0) { - throw new Error('A query must be provided'); - } - } else if (this.queryString ? - args.sources.length < (args.context ? 0 : 1) : - !(args.query ?? args.file) && args.sources.length < (args.context ? 1 : 2)) { - throw new Error('At least one source and query must be provided'); - } - return true; - }); - } - - public async handleArgs(args: Record, context: Record): Promise { - // Define the query format - context[KeysInitQuery.queryFormat.name] = { language: args.inputType, version: '1.1' }; - - // Define the datetime - if (args.dateTime) { - context[KeysHttpMemento.datetime.name] = new Date(args.dateTime); - } - - // Set the proxy - if (args.proxy) { - context[KeysHttpProxy.httpProxyHandler.name] = new ProxyHandlerStatic(args.proxy); - } - - // Mark explain output - if (args.explain) { - context[KeysInitQuery.explain.name] = args.explain; - } - - // Set recover broken links flag - if (args.recoverBrokenLinks) { - context[KeysHttpWayback.recoverBrokenLinks.name] = args.recoverBrokenLinks; - } - } -} \ No newline at end of file diff --git a/packages/actor-init-query/lib/index-browser.ts b/packages/actor-init-query/lib/index-browser.ts deleted file mode 100644 index bcc1895..0000000 --- a/packages/actor-init-query/lib/index-browser.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* eslint-disable unicorn/filename-case */ -/* eslint-enable unicorn/filename-case */ -export * from './ActorInitQueryBase'; -export * from './ActorInitQuery-browser'; -export { QueryEngineBase } from './QueryEngineBase'; diff --git a/packages/actor-init-query/lib/index.ts b/packages/actor-init-query/lib/index.ts deleted file mode 100644 index d81122d..0000000 --- a/packages/actor-init-query/lib/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -// These exports are aligned with browser -export * from './ActorInitQueryBase'; -export * from './ActorInitQuery'; -export { QueryEngineBase } from './QueryEngineBase'; - -// These exports are not for the browser -export * from './HttpServiceSparqlEndpoint'; -export * from './cli/CliArgsHandlerBase'; -export * from './cli/CliArgsHandlerHttp'; -export * from './cli/CliArgsHandlerQuery'; -export * from './QueryEngineFactoryBase'; diff --git a/packages/actor-init-query/package.json b/packages/actor-init-query/package.json deleted file mode 100644 index 24c21a5..0000000 --- a/packages/actor-init-query/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "@comunica/actor-init-query", - "version": "3.0.0", - "lsd:module": true, - "license": "MIT", - "main": "lib/index.js", - "typings": "lib/index", - "files": [ - "components", - "lib/**/*.d.ts", - "lib/**/*.js", - "lib/**/*.js.map" - ], - "dependencies": { - "@comunica/actor-http-proxy": "^3.0.0", - "@comunica/bus-http-invalidate": "^3.0.0", - "@comunica/bus-init": "^3.0.0", - "@comunica/bus-query-process": "^3.0.0", - "@comunica/bus-query-result-serialize": "^3.0.0", - "@comunica/context-entries": "^3.0.0", - "@comunica/core": "^3.0.0", - "@comunica/logger-pretty": "^3.0.0", - "@comunica/runner": "^3.0.0", - "@comunica/types": "^3.0.0", - "@types/yargs": "^17.0.0", - "asynciterator": "^3.0.0", - "negotiate": "^1.0.0", - "process": "^0.11.0", - "rdf-data-factory": "^1.0.0", - "streamify-string": "^1.0.0", - "yargs": "^17.0.0" - }, - "devDependencies": { - "@rdfjs/types": "*" - }, - "browser": { - "./lib/index.js": "./lib/index-browser.js" - } -} diff --git a/packages/actor-init-query/test/ActorInitQuery-browser-test.ts b/packages/actor-init-query/test/ActorInitQuery-browser-test.ts deleted file mode 100644 index 01d7d54..0000000 --- a/packages/actor-init-query/test/ActorInitQuery-browser-test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Transform } from 'node:stream'; -import { ActionContext, Bus } from '@comunica/core'; -import type { IActionContext } from '@comunica/types'; -import type { IActorInitQueryBaseArgs } from '../lib'; -import { ActorInitQuery } from '../lib/ActorInitQuery-browser'; - -describe('ActorInitQuery', () => { - let bus: any; - let mediatorQueryProcess: any; - let mediatorSparqlSerialize: any; - let mediatorHttpInvalidate: any; - let context: IActionContext; - const defaultQueryInputFormat = 'sparql'; - - beforeEach(() => { - bus = new Bus({ name: 'bus' }); - mediatorQueryProcess = { - mediate: jest.fn((action: any) => { - return Promise.reject(new Error('Invalid query')); - }), - }; - mediatorSparqlSerialize = { - mediate: (arg: any) => Promise.resolve(arg.mediaTypes ? - { mediaTypes: arg } : - { - handle: { - data: arg.handle.bindingsStream - .pipe(new Transform({ - objectMode: true, - transform: (e: any, enc: any, cb: any) => cb(null, JSON.stringify(e)), - })), - }, - }), - }; - mediatorHttpInvalidate = { - mediate: (arg: any) => Promise.resolve(true), - }; - context = new ActionContext(); - }); - - describe('An ActorInitQuery instance', () => { - let actor: ActorInitQuery; - beforeEach(() => { - actor = new ActorInitQuery( { - bus, - defaultQueryInputFormat, - mediatorHttpInvalidate, - mediatorQueryProcess, - mediatorQueryResultSerialize: mediatorSparqlSerialize, - mediatorQueryResultSerializeMediaTypeCombiner: mediatorSparqlSerialize, - mediatorQueryResultSerializeMediaTypeFormatCombiner: mediatorSparqlSerialize, - name: 'actor', - }); - }); - - describe('test', () => { - it('should be true', async() => { - await expect(actor.test( {})).resolves.toBeTruthy(); - }); - }); - - describe('run', () => { - it('should throw', async() => { - await expect(actor.run( {})).rejects - .toThrow('ActorInitSparql#run is not supported in the browser.'); - }); - }); - }); -}); \ No newline at end of file diff --git a/packages/actor-init-query/test/ActorInitQuery-noSources-test.ts b/packages/actor-init-query/test/ActorInitQuery-noSources-test.ts deleted file mode 100644 index 8fd7a6a..0000000 --- a/packages/actor-init-query/test/ActorInitQuery-noSources-test.ts +++ /dev/null @@ -1,135 +0,0 @@ -import type { MediatorQueryProcess } from '@comunica/bus-query-process'; -import { KeysInitQuery } from '@comunica/context-entries'; -import { ActionContext, Bus } from '@comunica/core'; -import { LoggerPretty } from '@comunica/logger-pretty'; -import type { IActionContext } from '@comunica/types'; -import { PassThrough, Readable, Transform } from 'readable-stream'; -import { ActorInitQuery } from '../lib/ActorInitQuery'; -import { QueryEngineBase } from '../lib/QueryEngineBase'; - -// Use require instead of import for default exports, to be compatible with variants of esModuleInterop in tsconfig. -const stringifyStream = require('stream-to-string'); - -describe('ActorInitQuery', () => { - let bus: any; - let mediatorQueryProcess: MediatorQueryProcess; - let mediatorSparqlSerialize: any; - let mediatorHttpInvalidate: any; - let context: IActionContext; - let input: Readable; - - const defaultQueryInputFormat = 'sparql'; - const sourceHypermedia = 'http://example.org/'; - const queryString = 'SELECT * WHERE { ?s ?p ?o } LIMIT 100'; - - beforeEach(() => { - bus = new Bus({ name: 'bus' }); - mediatorQueryProcess = { - mediate: jest.fn((action: any) => { - if (action.context.has(KeysInitQuery.explain)) { - return Promise.resolve({ - result: { - explain: 'true', - data: 'EXPLAINED', - }, - }); - } - return action.query === 'INVALID' ? - Promise.reject(new Error('Invalid query')) : - Promise.resolve({ - result: { type: 'bindings', bindingsStream: input, metadata: () => ({}), context: action.context }, - }); - }), - }; - mediatorSparqlSerialize = { - mediate(arg: any) { - return Promise.resolve(arg.mediaTypes ? - { mediaTypes: arg } : - { - handle: { - data: arg.handle.bindingsStream - .pipe(new Transform({ - objectMode: true, - transform: (e: any, enc: any, cb: any) => cb(null, JSON.stringify(e)), - })), - }, - }); - }, - }; - mediatorHttpInvalidate = { - mediate: (arg: any) => Promise.resolve(true), - }; - context = new ActionContext(); - input = new Readable({ objectMode: true }); - input._read = () => { - const triple = { a: 'triple' }; - input.push(triple); - input.push(null); - }; - ( input).toArray = () => [ 'element' ]; - }); - - describe('An ActorInitQuery instance', () => { - let actorAllowNoSources: ActorInitQuery; - let spyResultToString: any; - let spyQueryOrExplain: any; - beforeEach(() => { - actorAllowNoSources = new ActorInitQuery({ - bus, - defaultQueryInputFormat, - mediatorHttpInvalidate, - mediatorQueryProcess, - mediatorQueryResultSerialize: mediatorSparqlSerialize, - mediatorQueryResultSerializeMediaTypeCombiner: mediatorSparqlSerialize, - mediatorQueryResultSerializeMediaTypeFormatCombiner: mediatorSparqlSerialize, - name: 'actor', - allowNoSources: true, - }); - - spyResultToString = jest.spyOn(QueryEngineBase.prototype, 'resultToString'); - spyQueryOrExplain = jest.spyOn(QueryEngineBase.prototype, 'queryOrExplain'); - }); - - describe('with allowNoSources', () => { - it('handles a single source', async() => { - const stdout = await stringifyStream((await actorAllowNoSources.run({ - argv: [ 'SOURCE', queryString ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: 'SOURCE' }], - log: expect.any(LoggerPretty), - }); - }); - - it('handles no sources', async() => { - const stdout = await stringifyStream((await actorAllowNoSources.run({ - argv: [ queryString ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - log: expect.any(LoggerPretty), - }); - }); - - it('emits to stderr for no argv', async() => { - const stderr = await stringifyStream((await actorAllowNoSources.run({ - argv: [], - env: {}, - stdin: new PassThrough(), - context, - })).stderr); - expect(stderr).toContain('evaluates SPARQL queries'); - expect(stderr).toContain('A query must be provided'); - }); - }); - }); -}); \ No newline at end of file diff --git a/packages/actor-init-query/test/ActorInitQuery-test.ts b/packages/actor-init-query/test/ActorInitQuery-test.ts deleted file mode 100644 index 7565a24..0000000 --- a/packages/actor-init-query/test/ActorInitQuery-test.ts +++ /dev/null @@ -1,1056 +0,0 @@ -import * as Path from 'node:path'; -import { ProxyHandlerStatic } from '@comunica/actor-http-proxy'; -import type { MediatorQueryProcess } from '@comunica/bus-query-process'; -import { - KeysHttp, - KeysHttpMemento, - KeysHttpProxy, - KeysHttpWayback, - KeysInitQuery, - KeysQueryOperation, - KeysRdfUpdateQuads, -} from '@comunica/context-entries'; -import { ActionContext, Bus } from '@comunica/core'; -import { LoggerPretty } from '@comunica/logger-pretty'; -import type { IActionContext, ICliArgsHandler } from '@comunica/types'; -import { PassThrough, Readable, Transform } from 'readable-stream'; - -import { CliArgsHandlerBase } from '../lib'; -import { ActorInitQuery } from '../lib/ActorInitQuery'; -import { QueryEngineBase } from '../lib/QueryEngineBase'; - -// Use require instead of import for default exports, to be compatible with variants of esModuleInterop in tsconfig. -const stringifyStream = require('stream-to-string'); - -describe('ActorInitQuery', () => { - let bus: any; - let mediatorQueryProcess: MediatorQueryProcess; - let mediatorSparqlSerialize: any; - let mediatorHttpInvalidate: any; - let context: IActionContext; - let input: Readable; - - const defaultQueryInputFormat = 'sparql'; - const sourceHypermedia = 'http://example.org/'; - const sourceSparqlTagged = 'sparql@http://example.org/'; - const sourceAuth = 'http://username:passwd@example.org/'; - const sourceSparqlTaggedAuth = 'sparql@http://username:passwd@example.org/'; - const sourceOther = 'other@http://example.org/'; - const queryString = 'SELECT * WHERE { ?s ?p ?o } LIMIT 100'; - const contextString: any = JSON.stringify({ hypermedia: sourceHypermedia }); - - beforeEach(() => { - bus = new Bus({ name: 'bus' }); - mediatorQueryProcess = { - mediate: jest.fn((action: any) => { - if (action.context.has(KeysInitQuery.explain)) { - return Promise.resolve({ - result: { - explain: 'true', - data: 'EXPLAINED', - }, - }); - } - return action.query === 'INVALID' ? - Promise.reject(new Error('Invalid query')) : - Promise.resolve({ - result: { type: 'bindings', bindingsStream: input, metadata: () => ({}), context: action.context }, - }); - }), - }; - mediatorSparqlSerialize = { - mediate(arg: any) { - return Promise.resolve(arg.mediaTypes ? - { mediaTypes: arg } : - { - handle: { - data: arg.handle.bindingsStream - .pipe(new Transform({ - objectMode: true, - transform: (e: any, enc: any, cb: any) => cb(null, JSON.stringify(e)), - })), - }, - }); - }, - }; - mediatorHttpInvalidate = { - mediate: (arg: any) => Promise.resolve(true), - }; - context = new ActionContext(); - input = new Readable({ objectMode: true }); - input._read = () => { - const triple = { a: 'triple' }; - input.push(triple); - input.push(null); - }; - ( input).toArray = () => [ 'element' ]; - }); - - describe('An ActorInitQuery instance', () => { - let actor: ActorInitQuery; - let actorFixedQuery: ActorInitQuery; - let actorFixedContext: ActorInitQuery; - let actorFixedQueryAndContext: ActorInitQuery; - let spyResultToString: any; - let spyQueryOrExplain: any; - - beforeEach(() => { - actor = new ActorInitQuery({ - bus, - defaultQueryInputFormat, - mediatorQueryProcess, - mediatorHttpInvalidate, - mediatorQueryResultSerialize: mediatorSparqlSerialize, - mediatorQueryResultSerializeMediaTypeCombiner: mediatorSparqlSerialize, - mediatorQueryResultSerializeMediaTypeFormatCombiner: mediatorSparqlSerialize, - name: 'actor', - }); - actorFixedQuery = new ActorInitQuery({ - bus, - defaultQueryInputFormat: 'sparql', - mediatorQueryProcess, - mediatorHttpInvalidate, - mediatorQueryResultSerialize: mediatorSparqlSerialize, - mediatorQueryResultSerializeMediaTypeCombiner: mediatorSparqlSerialize, - mediatorQueryResultSerializeMediaTypeFormatCombiner: mediatorSparqlSerialize, - name: 'actor', - queryString, - }); - actorFixedContext = new ActorInitQuery({ - bus, - defaultQueryInputFormat: 'sparql', - mediatorQueryProcess, - mediatorHttpInvalidate, - mediatorQueryResultSerialize: mediatorSparqlSerialize, - mediatorQueryResultSerializeMediaTypeCombiner: mediatorSparqlSerialize, - mediatorQueryResultSerializeMediaTypeFormatCombiner: mediatorSparqlSerialize, - name: 'actor', - context: contextString, - }); - actorFixedQueryAndContext = new ActorInitQuery({ - bus, - defaultQueryInputFormat: 'sparql', - mediatorQueryProcess, - mediatorHttpInvalidate, - mediatorQueryResultSerialize: mediatorSparqlSerialize, - mediatorQueryResultSerializeMediaTypeCombiner: mediatorSparqlSerialize, - mediatorQueryResultSerializeMediaTypeFormatCombiner: mediatorSparqlSerialize, - name: 'actor', - queryString, - context: contextString, - }); - - spyResultToString = jest.spyOn(QueryEngineBase.prototype, 'resultToString'); - spyQueryOrExplain = jest.spyOn(QueryEngineBase.prototype, 'queryOrExplain'); - }); - - describe('test', () => { - it('should be true', async() => { - await expect(actor.test( {})).resolves.toBeTruthy(); - }); - }); - - describe('run', () => { - it('emits to stderr for no argv', async() => { - const stderr = await stringifyStream( (await actor.run({ - argv: [], - env: {}, - stdin: new PassThrough(), - context, - })).stderr); - expect(stderr).toContain('evaluates SPARQL queries'); - expect(stderr).toContain('At least one source and query must be provided'); - }); - - it('handles the -v options', async() => { - const stderr = await stringifyStream( (await actor.run({ - argv: [ '-v' ], - env: {}, - stdin: new PassThrough(), - context, - })).stderr); - expect(stderr).toContain('Comunica Engine'); - expect(stderr).toContain('dev'); - }); - - it('handles the --version option', async() => { - const stderr = await stringifyStream( (await actor.run({ - argv: [ '--version' ], - env: {}, - stdin: new PassThrough(), - context, - })).stderr); - expect(stderr).toContain('Comunica Engine'); - expect(stderr).toContain('dev'); - }); - - it('handles the -v option when not in a dev environment', async() => { - jest.spyOn(CliArgsHandlerBase, 'isDevelopmentEnvironment').mockReturnValue(false); - const stderr = await stringifyStream( (await actor.run({ - argv: [ '-v' ], - env: {}, - stdin: new PassThrough(), - context, - })).stderr); - expect(stderr).toContain('Comunica Engine'); - expect(stderr).not.toContain('dev'); - }); - - it('handles the -h option', async() => { - const stderr = await stringifyStream( (await actor.run({ - argv: [ '-h' ], - env: {}, - stdin: new PassThrough(), - context, - })).stderr); - expect(stderr).toContain('evaluates SPARQL queries'); - expect(stderr).toContain('At least one source and query must be provided'); - }); - - it('handles the --help option', async() => { - const stderr = await stringifyStream( (await actor.run({ - argv: [ '--help' ], - env: {}, - stdin: new PassThrough(), - context, - })).stderr); - expect(stderr).toContain('evaluates SPARQL queries'); - expect(stderr).toContain('At least one source and query must be provided'); - }); - - it('handles the --listformats option', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ '--listformats' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain('mediaTypes'); - }); - - it('handles the media type option -t', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, queryString, '-t', 'testtype' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyResultToString) - .toHaveBeenCalledWith(expect.anything(), 'testtype', expect.anything()); - }); - - it('handles the old inline context form', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ `{ "bla": true }`, 'Q' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith('Q', { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - log: expect.any(LoggerPretty), - bla: true, - }); - }); - - it('handles a hypermedia source and query', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, queryString ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: sourceHypermedia }], - log: expect.any(LoggerPretty), - }); - }); - - it('emits to stderr for a hypermedia source without a query', async() => { - const stderr = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia ], - env: {}, - stdin: new PassThrough(), - context, - })).stderr); - expect(stderr).toContain('evaluates SPARQL queries'); - expect(stderr).toContain('At least one source and query must be provided'); - }); - - it('rejects for a hypermedia source and an invalid query', async() => { - await expect(actor.run({ - argv: [ sourceHypermedia, 'INVALID' ], - env: {}, - stdin: new PassThrough(), - context, - })).rejects.toThrow('Invalid query'); - }); - - it('handles a hypermedia source and query option', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q', queryString ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: sourceHypermedia }], - log: expect.any(LoggerPretty), - }); - }); - - it('emits to stderr with a hypermedia source and a empty query option', async() => { - const stderr = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q' ], - env: {}, - stdin: new PassThrough(), - context, - })).stderr); - expect(stderr).toContain('evaluates SPARQL queries'); - expect(stderr).toContain('At least one source and query must be provided'); - }); - - it('handles a hypermedia source and query file option', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-f', Path.join(__dirname, `assets/all-100.sparql`) ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(`SELECT * WHERE { - ?s ?p ?o -} -LIMIT 100 -`, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: sourceHypermedia }], - log: expect.any(LoggerPretty), - }); - }); - - it('emits to stderr with a hypermedia source and a empty query file option', async() => { - const stderr = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-f' ], - env: {}, - stdin: new PassThrough(), - context, - })).stderr); - expect(stderr).toContain('evaluates SPARQL queries'); - expect(stderr).toContain('At least one source and query must be provided'); - }); - - it('rejects with a hypermedia source and a query file option to an invalid path', async() => { - await expect(actor.run({ - argv: [ sourceHypermedia, '-f', `${__dirname}filedoesnotexist.sparql` ], - env: {}, - stdin: new PassThrough(), - context, - })).rejects.toThrow('no such file or directory'); - }); - - it('handles a tagged sparql source and query option', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceSparqlTagged, '-q', queryString ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ type: 'sparql', value: sourceHypermedia }], - log: expect.any(LoggerPretty), - }); - }); - - it('handles credentials in url and query option', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceAuth, '-q', queryString ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ - value: sourceHypermedia, - context: new ActionContext({ - [KeysHttp.auth.name]: 'username:passwd', - }), - }], - log: expect.any(LoggerPretty), - }); - }); - - it('handles a tagged sparql and credentials in url and query option', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceSparqlTaggedAuth, '-q', queryString ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ - type: 'sparql', - value: sourceHypermedia, - context: new ActionContext({ - [KeysHttp.auth.name]: 'username:passwd', - }), - }], - log: expect.any(LoggerPretty), - }); - }); - - it('handles an other source type and query option', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceOther, '-q', queryString ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ type: 'other', value: sourceHypermedia }], - log: expect.any(LoggerPretty), - }); - }); - - it('handles multiple hypermedia sources and a query option', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, sourceHypermedia, '-q', queryString ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: sourceHypermedia }, { value: sourceHypermedia }], - log: expect.any(LoggerPretty), - }); - }); - - it('handles multiple tagged sparql sources and a query option', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceSparqlTagged, sourceSparqlTagged, '-q', queryString ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [ - { type: 'sparql', value: sourceHypermedia }, - { type: 'sparql', value: sourceHypermedia }, - ], - log: expect.any(LoggerPretty), - }); - }); - - it('handles query and a config file option', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ queryString, '-c', Path.join(__dirname, `/assets/config.json`) ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - entrypoint: 'http://example.org/', - log: expect.any(LoggerPretty), - }); - }); - - it('handles the datetime -d option', async() => { - const dt: Date = new Date(); - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q', queryString, '-d', dt.toISOString() ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: sourceHypermedia }], - log: expect.any(LoggerPretty), - [KeysHttpMemento.datetime.name]: dt, - }); - }); - - it('handles the recoverBrokenLinks -r option', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q', queryString, '-r' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: sourceHypermedia }], - log: new LoggerPretty({ level: 'warn' }), - [KeysHttpWayback.recoverBrokenLinks.name]: true, - }); - }); - - it('handles the logger -l option', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q', queryString, '-l', 'warn' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: sourceHypermedia }], - log: new LoggerPretty({ level: 'warn' }), - }); - }); - - it('does not handle the logger -l option if the context already has a logger', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q', queryString, '-l', 'warn' ], - env: {}, - stdin: new PassThrough(), - context: new ActionContext({ - log: 'LOGGER', - }), - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: sourceHypermedia }], - log: 'LOGGER', - }); - }); - - it('handles the baseIRI -b option', async() => { - const baseIRI = 'http://example.org'; - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q', queryString, '-b', baseIRI ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: sourceHypermedia }], - log: expect.any(LoggerPretty), - [KeysInitQuery.baseIRI.name]: baseIRI, - }); - }); - - it('handles the proxy -p option', async() => { - const proxy = 'http://proxy.org/'; - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q', queryString, '-p', proxy ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: sourceHypermedia }], - log: expect.any(LoggerPretty), - [KeysHttpProxy.httpProxyHandler.name]: new ProxyHandlerStatic(proxy), - }); - }); - - it('handles the --lenient flag', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q', queryString, '--lenient' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: sourceHypermedia }], - log: expect.any(LoggerPretty), - [KeysInitQuery.lenient.name]: true, - }); - }); - - it('handles the --httpTimeout flag', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q', queryString, '--httpTimeout=60' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: sourceHypermedia }], - log: expect.any(LoggerPretty), - [KeysHttp.httpTimeout.name]: 60, - }); - }); - - it('handles the --httpBodyTimeout flag', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q', queryString, '--httpTimeout=60', '--httpBodyTimeout' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: sourceHypermedia }], - log: expect.any(LoggerPretty), - [KeysHttp.httpTimeout.name]: 60, - [KeysHttp.httpBodyTimeout.name]: true, - }); - }); - - it('--httpBodyTimeout flag requires --httpTimeout', async() => { - const stderr = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q', queryString, '--httpBodyTimeout' ], - env: {}, - stdin: new PassThrough(), - context, - })).stderr); - expect(stderr).toContain(`The --httpBodyTimeout option requires the --httpTimeout option to be set`); - }); - - it('handles the --httpRetryCount flag', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q', queryString, '--httpRetryCount=2' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: sourceHypermedia }], - log: expect.any(LoggerPretty), - [KeysHttp.httpRetryCount.name]: 2, - }); - }); - - it('handles the --httpRetryDelay flag', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q', queryString, '--httpRetryCount=2', '--httpRetryDelay=500' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: sourceHypermedia }], - log: expect.any(LoggerPretty), - [KeysHttp.httpRetryCount.name]: 2, - [KeysHttp.httpRetryDelay.name]: 500, - }); - }); - - it('handles --httpRetryDelay flag requiring --httpRetryCount', async() => { - const stderr = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q', queryString, '--httpRetryDelay=500' ], - env: {}, - stdin: new PassThrough(), - context, - })).stderr); - expect(stderr).toContain(`The --httpRetryDelay option requires the --httpRetryCount option to be set`); - }); - - it('handles the --httpRetryOnServerError flag', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q', queryString, '--httpRetryCount=2', '--httpRetryOnServerError' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: sourceHypermedia }], - log: expect.any(LoggerPretty), - [KeysHttp.httpRetryCount.name]: 2, - [KeysHttp.httpRetryOnServerError.name]: true, - }); - }); - - it('handles --httpRetryOnServerError flag requiring --httpRetryCount', async() => { - const stderr = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q', queryString, '--httpRetryOnServerError' ], - env: {}, - stdin: new PassThrough(), - context, - })).stderr); - expect(stderr).toContain(`The --httpRetryOnServerError option requires the --httpRetryCount option to be set`); - }); - - it('handles the --unionDefaultGraph flag', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q', queryString, '--unionDefaultGraph' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: sourceHypermedia }], - log: expect.any(LoggerPretty), - [KeysQueryOperation.unionDefaultGraph.name]: true, - }); - }); - - it('handles the --noCache flag', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q', queryString, '--noCache' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: sourceHypermedia }], - log: expect.any(LoggerPretty), - [KeysInitQuery.noCache.name]: true, - }); - }); - - it('handles the destination --to option', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q', queryString, '--to', 'http://target.com/' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: sourceHypermedia }], - log: expect.any(LoggerPretty), - [KeysRdfUpdateQuads.destination.name]: 'http://target.com/', - }); - }); - - it('handles a cliArgsHandler', async() => { - const cliArgsHandler: ICliArgsHandler = { - populateYargs(args) { - return args.options({ - bla: { - description: 'blabla', - }, - }); - }, - async handleArgs(args, ctx) { - ctx.bla = args.bla; - }, - }; - const stdout = await stringifyStream( (await actor.run({ - argv: [ sourceHypermedia, '-q', queryString, '--bla', 'BLA' ], - env: {}, - stdin: new PassThrough(), - context: new ActionContext({ - [KeysInitQuery.cliArgsHandlers.name]: [ cliArgsHandler ], - }), - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: sourceHypermedia }], - log: expect.any(LoggerPretty), - [KeysInitQuery.cliArgsHandlers.name]: [ cliArgsHandler ], - bla: 'BLA', - }); - }); - - describe('output format', () => { - it('defaults to application/json for bindingsStream', async() => { - const m1: any = { - mediate: (arg: any) => Promise.resolve({ type: 'bindings', bindingsStream: true, metadata: () => ({}) }), - }; - const m2: any = { - mediate: (arg: any) => Promise.resolve({ handle: { data: arg.handleMediaType }}), - }; - const actorThis = new ActorInitQuery({ - bus, - mediatorQueryProcess, - mediatorHttpInvalidate, - mediatorQueryResultSerialize: m2, - mediatorQueryResultSerializeMediaTypeCombiner: m2, - mediatorQueryResultSerializeMediaTypeFormatCombiner: m2, - name: 'actor', - queryString, - }); - expect((await actorThis.run({ - argv: [ 'S' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout) - .toBe('application/json'); - }); - - it('defaults to application/trig for quadStream', async() => { - const m1: any = { - mediate: (arg: any) => Promise.resolve({ result: { type: 'quads', quadStream: true }}), - }; - const m2: any = { - mediate: (arg: any) => Promise.resolve({ handle: { data: arg.handleMediaType }}), - }; - const actorThis = new ActorInitQuery({ - bus, - mediatorQueryProcess: m1, - mediatorHttpInvalidate, - mediatorQueryResultSerialize: m2, - mediatorQueryResultSerializeMediaTypeCombiner: m2, - mediatorQueryResultSerializeMediaTypeFormatCombiner: m2, - name: 'actor', - queryString, - }); - expect((await actorThis.run({ - argv: [ 'S' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout) - .toBe('application/trig'); - }); - - it('defaults to simple for boolean', async() => { - const m1: any = { - mediate: (arg: any) => Promise.resolve({ - result: { type: 'boolean', booleanResult: Promise.resolve(true) }, - }), - }; - const m2: any = { - mediate: (arg: any) => Promise.resolve({ handle: { data: arg.handleMediaType }}), - }; - const actorThis = new ActorInitQuery({ - bus, - mediatorQueryProcess: m1, - mediatorHttpInvalidate, - mediatorQueryResultSerialize: m2, - mediatorQueryResultSerializeMediaTypeCombiner: m2, - mediatorQueryResultSerializeMediaTypeFormatCombiner: m2, - name: 'actor', - queryString, - }); - expect((await actorThis.run({ - argv: [ 'S' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout) - .toBe('simple'); - }); - }); - - describe('for a fixed query', () => { - it('handles a single source', async() => { - const stdout = await stringifyStream( (await actorFixedQuery.run({ - argv: [ 'SOURCE' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: 'SOURCE' }], - log: expect.any(LoggerPretty), - }); - }); - - it('handles the query format option -i', async() => { - const stdout = await stringifyStream( (await actorFixedQuery.run({ - argv: [ 'SOURCE', '-i', 'graphql' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'graphql', version: '1.1' }, - sources: [{ value: 'SOURCE' }], - log: expect.any(LoggerPretty), - }); - }); - - it('emits to stderr for no args', async() => { - const stderr = await stringifyStream( (await actorFixedQuery.run({ - argv: [], - env: {}, - stdin: new PassThrough(), - context, - })).stderr); - expect(stderr).toContain('evaluates SPARQL queries'); - expect(stderr).toContain('At least one source and query must be provided'); - }); - - it('emits to stderr for no argv when the default query is falsy', async() => { - const actorThis = new ActorInitQuery({ - bus, - mediatorQueryProcess, - mediatorHttpInvalidate, - mediatorQueryResultSerialize: mediatorSparqlSerialize, - mediatorQueryResultSerializeMediaTypeCombiner: mediatorSparqlSerialize, - mediatorQueryResultSerializeMediaTypeFormatCombiner: mediatorSparqlSerialize, - name: 'actor', - queryString: null, - }); - - const stderr = await stringifyStream( (await actorThis.run({ - argv: [], - env: {}, - stdin: new PassThrough(), - context, - })).stderr); - expect(stderr).toContain('evaluates SPARQL queries'); - expect(stderr).toContain('At least one source and query must be provided'); - }); - }); - - describe('for a fixed query and context', () => { - it('handles no args', async() => { - const stdout = await stringifyStream( (await actorFixedQueryAndContext.run({ - argv: [], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{"a":"triple"}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - log: expect.any(LoggerPretty), - hypermedia: 'http://example.org/', - }); - }); - }); - - describe('for a fixed context', () => { - it('emits to stderr for no argv', async() => { - const stderr = await stringifyStream( (await actorFixedContext.run({ - argv: [], - env: {}, - stdin: new PassThrough(), - context, - })).stderr); - expect(stderr).toContain('evaluates SPARQL queries'); - expect(stderr).toContain('At least one source and query must be provided'); - }); - }); - - describe('explain', () => { - it('in parsed mode', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ 'SOURCE', '-q', queryString, '--explain', 'parsed' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`EXPLAINED`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.explain.name]: 'parsed', - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: 'SOURCE' }], - log: expect.any(LoggerPretty), - }); - }); - - it('in logical mode', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ 'SOURCE', '-q', queryString, '--explain', 'logical' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`EXPLAINED`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.explain.name]: 'logical', - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: 'SOURCE' }], - log: expect.any(LoggerPretty), - }); - }); - - it('in physical mode', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ 'SOURCE', '-q', queryString, '--explain', 'physical' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`EXPLAINED`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.explain.name]: 'physical', - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: 'SOURCE' }], - log: expect.any(LoggerPretty), - }); - }); - - it('in physical-json mode', async() => { - const stdout = await stringifyStream( (await actor.run({ - argv: [ 'SOURCE', '-q', queryString, '--explain', 'physical-json' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`EXPLAINED`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.explain.name]: 'physical-json', - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: 'SOURCE' }], - log: expect.any(LoggerPretty), - }); - }); - - it('in physical mode for scoped sources', async() => { - ( mediatorQueryProcess).mediate = () => { - return Promise.resolve({ - result: { - explain: 'true', - data: { bla: 'bla', scopedSource: { source: { toString: () => 'SRCSTR' }}}, - }, - }); - }; - - const stdout = await stringifyStream( (await actor.run({ - argv: [ 'SOURCE', '-q', queryString, '--explain', 'physical' ], - env: {}, - stdin: new PassThrough(), - context, - })).stdout); - expect(stdout).toContain(`{ - "bla": "bla", - "scopedSource": "SRCSTR" -}`); - expect(spyQueryOrExplain).toHaveBeenCalledWith(queryString, { - [KeysInitQuery.explain.name]: 'physical', - [KeysInitQuery.queryFormat.name]: { language: 'sparql', version: '1.1' }, - sources: [{ value: 'SOURCE' }], - log: expect.any(LoggerPretty), - }); - }); - }); - }); - }); -}); diff --git a/packages/actor-init-query/test/ActorInitQueryBase-test.ts b/packages/actor-init-query/test/ActorInitQueryBase-test.ts deleted file mode 100644 index 8d8603b..0000000 --- a/packages/actor-init-query/test/ActorInitQueryBase-test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Transform } from 'node:stream'; -import { ActorInit } from '@comunica/bus-init'; -import { ActionContext, Bus } from '@comunica/core'; -import type { IActionContext } from '@comunica/types'; -import type { IActorInitQueryBaseArgs } from '../lib/ActorInitQueryBase'; -import { ActorInitQueryBase } from '../lib/ActorInitQueryBase'; - -describe('ActorInitQueryBase', () => { - let bus: any; - let mediatorQueryProcess: any; - let mediatorSparqlSerialize: any; - let mediatorHttpInvalidate: any; - let context: IActionContext; - - const defaultQueryInputFormat = 'sparql'; - - beforeEach(() => { - bus = new Bus({ name: 'bus' }); - mediatorQueryProcess = { - mediate: jest.fn((action: any) => { - return Promise.reject(new Error('Invalid query')); - }), - }; - mediatorSparqlSerialize = { - mediate: (arg: any) => Promise.resolve(arg.mediaTypes ? - { mediaTypes: arg } : - { - handle: { - data: arg.handle.bindingsStream - .pipe(new Transform({ - objectMode: true, - transform: (e: any, enc: any, cb: any) => cb(null, JSON.stringify(e)), - })), - }, - }), - }; - mediatorHttpInvalidate = { - mediate: (arg: any) => Promise.resolve(true), - }; - context = new ActionContext(); - }); - - describe('The ActorInitQueryBase module', () => { - it('should be a function', () => { - expect(ActorInitQueryBase).toBeInstanceOf(Function); - }); - - it('should be a ActorInitQueryBase constructor', () => { - expect(new ( ActorInitQueryBase)( - { name: 'actor', bus, mediatorQueryProcess, mediatorSparqlSerialize }, - )) - .toBeInstanceOf(ActorInitQueryBase); - expect(new ( ActorInitQueryBase)( - { name: 'actor', bus, mediatorQueryProcess, mediatorSparqlSerialize }, - )) - .toBeInstanceOf(ActorInit); - }); - - it('should not be able to create new ActorInitQueryBase objects without \'new\'', () => { - expect(() => { - ( ActorInitQueryBase)(); - }).toThrow(`Class constructor ActorInitQueryBase cannot be invoked without 'new'`); - }); - }); - - describe('An ActorInitQueryBase instance', () => { - let actor: ActorInitQueryBase; - - beforeEach(() => { - actor = new ActorInitQueryBase( { - bus, - defaultQueryInputFormat, - mediatorHttpInvalidate, - mediatorQueryProcess, - mediatorQueryResultSerialize: mediatorSparqlSerialize, - mediatorQueryResultSerializeMediaTypeCombiner: mediatorSparqlSerialize, - mediatorQueryResultSerializeMediaTypeFormatCombiner: mediatorSparqlSerialize, - name: 'actor', - }); - }); - - describe('test', () => { - it('should be true', async() => { - await expect(actor.test( {})).resolves.toBeTruthy(); - }); - }); - - describe('run', () => { - it('should throw', async() => { - await expect(actor.run( {})).rejects - .toThrow('ActorInitSparql#run is not supported in the browser.'); - }); - }); - }); -}); \ No newline at end of file diff --git a/packages/actor-init-query/test/HttpServiceSparqlEndpoint-2-test.ts b/packages/actor-init-query/test/HttpServiceSparqlEndpoint-2-test.ts deleted file mode 100644 index d22ab9a..0000000 --- a/packages/actor-init-query/test/HttpServiceSparqlEndpoint-2-test.ts +++ /dev/null @@ -1,220 +0,0 @@ -import type { Cluster } from 'node:cluster'; -import type { IncomingMessage } from 'node:http'; -import { Writable } from 'node:stream'; -import type { QueryStringContext, QueryType } from '@comunica/types'; -import type * as RDF from '@rdfjs/types'; -import { DataFactory } from 'rdf-data-factory'; -import { HttpServiceSparqlEndpoint } from '../lib/HttpServiceSparqlEndpoint'; - -const cluster: Cluster = require('node:cluster'); -const streamToString = require('stream-to-string'); -const stringToStream = require('streamify-string'); - -const DF = new DataFactory(); - -const stdmock = new Writable(); -stdmock._write = () => {}; - -const clusterMock = { - isPrimary: true, - fork: jest.fn(), - on: jest.fn(), -}; - -const argsDefault = { - moduleRootPath: 'moduleRootPath', - defaultConfigPath: 'defaultConfigPath', -}; - -let httpServiceSparqlEndpoint: HttpServiceSparqlEndpoint; - -describe('HttpServiceSparqlEndpoint', () => { - beforeEach(() => { - jest.resetAllMocks(); - Object.assign(cluster, clusterMock); - httpServiceSparqlEndpoint = new HttpServiceSparqlEndpoint(argsDefault); - }); - - describe('constructor', () => { - it('should not error if no args are supplied', () => { - expect(() => new HttpServiceSparqlEndpoint({ ...argsDefault })).not.toThrow('TODO'); - }); - - it('should set fields with values from args if present', () => { - const args = { context: { test: 'test' }, timeout: 4_321, port: 24_321, invalidateCacheBeforeQuery: true }; - const instance = new HttpServiceSparqlEndpoint({ ...argsDefault, ...args }); - - expect((instance).context).toEqual({ test: 'test' }); - expect((instance).timeout).toBe(4_321); - expect((instance).port).toBe(24_321); - expect((instance).invalidateCacheBeforeQuery).toBeTruthy(); - }); - - it('should set default field values for fields that are not in args', () => { - const args = { ...argsDefault }; - const instance = new HttpServiceSparqlEndpoint(args); - - expect((instance).context).toEqual({}); - expect((instance).timeout).toBe(60_000); - expect((instance).port).toBe(3_000); - expect((instance).invalidateCacheBeforeQuery).toBeFalsy(); - }); - }); - - describe('run', () => { - it('should call runPrimary if primary', async() => { - jest.spyOn(httpServiceSparqlEndpoint, 'runPrimary').mockResolvedValue(); - jest.spyOn(httpServiceSparqlEndpoint, 'runWorker').mockResolvedValue(); - await httpServiceSparqlEndpoint.run(stdmock, stdmock); - expect(httpServiceSparqlEndpoint.runPrimary).toHaveBeenCalledTimes(1); - expect(httpServiceSparqlEndpoint.runWorker).not.toHaveBeenCalled(); - }); - - it('should call runWorker if worker', async() => { - (cluster).isPrimary = false; - jest.spyOn(httpServiceSparqlEndpoint, 'runPrimary').mockResolvedValue(); - jest.spyOn(httpServiceSparqlEndpoint, 'runWorker').mockResolvedValue(); - await httpServiceSparqlEndpoint.run(stdmock, stdmock); - expect(httpServiceSparqlEndpoint.runPrimary).not.toHaveBeenCalled(); - expect(httpServiceSparqlEndpoint.runWorker).toHaveBeenCalledTimes(1); - }); - }); - - describe('readRequestBody', () => { - it('should successfully read request body', async() => { - const content = 'abc'; - const contentEncoding = 'utf-8'; - const contentType = 'text/plain'; - const request: IncomingMessage = stringToStream(content); - request.headers = { 'content-type': contentType, 'content-encoding': contentEncoding }; - const requestBody = await httpServiceSparqlEndpoint.readRequestBody(request); - expect(requestBody.content).toBe(content); - expect(requestBody.contentType).toBe(contentType); - expect(requestBody.contentEncoding).toBe(contentEncoding); - }); - - it('should successfully read request body without content-encoding', async() => { - const content = 'abc'; - const contentType = 'text/plain'; - const request: IncomingMessage = stringToStream(content); - request.headers = { 'content-type': contentType }; - const requestBody = await httpServiceSparqlEndpoint.readRequestBody(request); - expect(requestBody.content).toBe(content); - expect(requestBody.contentType).toBe(contentType); - expect(requestBody.contentEncoding).toBe('utf-8'); - }); - - it('should reject request without content-type header', async() => { - const content = 'abc'; - const request: IncomingMessage = stringToStream(content); - request.headers = {}; - await expect(httpServiceSparqlEndpoint.readRequestBody(request)).rejects.toThrow('Bad Request'); - }); - - it('should reject request with failing body stream', async() => { - const content = 'abc'; - const request: IncomingMessage = stringToStream(content); - const error = new Error('Body stream failing!'); - request._read = () => { - throw error; - }; - request.headers = { 'content-type': 'text/plain' }; - await expect(httpServiceSparqlEndpoint.readRequestBody(request)).rejects.toThrow(error); - }); - }); - - describe('parseOperationParams', () => { - it('should successfully parse all parameters', () => { - const params = new URLSearchParams({ - 'default-graph-uri': 'ex:g1', - 'named-graph-uri': 'ex:g2', - 'using-graph-uri': 'ex:g3', - 'using-named-graph-uri': 'ex:g4', - }); - const parsed = httpServiceSparqlEndpoint.parseOperationParams(params); - expect(parsed).toEqual({ - defaultGraphUris: [ DF.namedNode('ex:g1') ], - namedGraphUris: [ DF.namedNode('ex:g2') ], - usingGraphUris: [ DF.namedNode('ex:g3') ], - usingNamedGraphUris: [ DF.namedNode('ex:g4') ], - }); - }); - - it('should successfully parse no parameters', () => { - const params = new URLSearchParams(); - const parsed = httpServiceSparqlEndpoint.parseOperationParams(params); - expect(parsed).toEqual({}); - }); - - it('should successfully ignore user context', () => { - const params = new URLSearchParams(); - const userContext: QueryStringContext = { sources: [{ value: 'ex:s' }], userKey: 'abc' }; - const parsed = httpServiceSparqlEndpoint.parseOperationParams(params, userContext); - expect(parsed).toEqual({}); - }); - - it('should successfully include user context', () => { - httpServiceSparqlEndpoint = new HttpServiceSparqlEndpoint({ ...argsDefault, allowContextOverride: true }); - const params = new URLSearchParams(); - const userContext: QueryStringContext = { sources: [{ value: 'ex:s' }], userKey: 'abc' }; - const parsed = httpServiceSparqlEndpoint.parseOperationParams(params, userContext); - expect(parsed).toEqual(userContext); - }); - }); - - describe('getServiceDescription', () => { - it('should successfully output service description', async() => { - const request: IncomingMessage = stringToStream('abc'); - request.headers = { host: 'http://localhost:3000' }; - const mediaTypes = [{ type: 'text/plain', quality: 1 }]; - const sd = httpServiceSparqlEndpoint.getServiceDescription(request, mediaTypes); - expect(sd.metadata).toBeUndefined(); - expect(sd.resultType).toBe('quads'); - const quadsStream = await sd.execute(); - const quads: RDF.Quad[] = []; - await new Promise((resolve, reject) => quadsStream - .on('data', quad => quads.push(quad)) - .on('error', reject) - .on('end', resolve)); - expect(quads).toHaveLength(7); - }); - }); - - describe('negotiateResultType', () => { - it('should successfully negotiate', () => { - const request: IncomingMessage = stringToStream('abc'); - request.headers = { accept: 'text/turtle' }; - const result: QueryType = { resultType: 'quads', execute: undefined, metadata: undefined }; - const mediaTypes = [{ type: 'text/turtle', quality: 1 }, { type: 'application/ld+json', quality: 1.5 }]; - const negotiatedMediaType = httpServiceSparqlEndpoint.negotiateResultType(request, result, mediaTypes); - expect(negotiatedMediaType).toBe('text/turtle'); - }); - - it('should select application/trig for quads when no accept header is provided', () => { - const request: IncomingMessage = stringToStream('abc'); - request.headers = {}; - const result: QueryType = { resultType: 'quads', execute: undefined, metadata: undefined }; - const mediaTypes = [{ type: 'text/turtle', quality: 1 }]; - const negotiatedMediaType = httpServiceSparqlEndpoint.negotiateResultType(request, result, mediaTypes); - expect(negotiatedMediaType).toBe('application/trig'); - }); - - it('should select simple for void when no accept header is provided', () => { - const request: IncomingMessage = stringToStream('abc'); - request.headers = {}; - const result: QueryType = { resultType: 'void', execute: undefined }; - const mediaTypes = [{ type: 'text/turtle', quality: 1 }]; - const negotiatedMediaType = httpServiceSparqlEndpoint.negotiateResultType(request, result, mediaTypes); - expect(negotiatedMediaType).toBe('simple'); - }); - - it('should select application/sparql-results+json for bindings when no accept header is provided', () => { - const request: IncomingMessage = stringToStream('abc'); - request.headers = {}; - const result: QueryType = { resultType: 'bindings', execute: undefined, metadata: undefined }; - const mediaTypes = [{ type: 'text/turtle', quality: 1 }]; - const negotiatedMediaType = httpServiceSparqlEndpoint.negotiateResultType(request, result, mediaTypes); - expect(negotiatedMediaType).toBe('application/sparql-results+json'); - }); - }); -}); diff --git a/packages/actor-init-query/test/HttpServiceSparqlEndpoint-test.ts b/packages/actor-init-query/test/HttpServiceSparqlEndpoint-test.ts deleted file mode 100644 index b5f6125..0000000 --- a/packages/actor-init-query/test/HttpServiceSparqlEndpoint-test.ts +++ /dev/null @@ -1,1930 +0,0 @@ -/* eslint-disable jest/prefer-spy-on,jest/no-mocks-import */ -import type { Cluster } from 'node:cluster'; -import { PassThrough } from 'node:stream'; -import { KeysQueryOperation } from '@comunica/context-entries'; -import { LoggerPretty } from '@comunica/logger-pretty'; -import { ArrayIterator } from 'asynciterator'; - -// @ts-expect-error -import { QueryEngineFactoryBase, QueryEngineBase } from '../__mocks__'; - -// @ts-expect-error -import { fs, testArgumentDict, testFileContentDict } from '../__mocks__/fs'; - -// @ts-expect-error -import { http, ServerResponseMock } from '../__mocks__/http'; - -// @ts-expect-error -import { parse } from '../__mocks__/url'; -import { CliArgsHandlerBase } from '../lib/cli/CliArgsHandlerBase'; -import type { IQueryBody } from '../lib/HttpServiceSparqlEndpoint'; -import { HttpServiceSparqlEndpoint } from '../lib/HttpServiceSparqlEndpoint'; - -// Use require instead of import for default exports, to be compatible with variants of esModuleInterop in tsconfig. -const clusterUntyped = require('node:cluster'); -const EventEmitter = require('node:events'); -const querystring = require('node:querystring'); -// Force type on Cluster, because there are issues with the Node.js typings since v18 -const cluster: Cluster = clusterUntyped; - -const quad = require('rdf-quad'); -const stringifyStream = require('stream-to-string'); -const stringToStream = require('streamify-string'); - -jest.mock('..', () => { - return { - QueryEngineBase, - QueryEngineFactoryBase, - }; -}); - -jest.mock('node:url', () => { - return { - parse, - }; -}); - -jest.mock('node:http', () => { - return http; -}); - -jest.mock('node:fs', () => { - return fs; -}); - -jest.useFakeTimers({ legacyFakeTimers: true }); - -const argsDefault = { - moduleRootPath: 'moduleRootPath', - defaultConfigPath: 'defaultConfigPath', -}; - -describe('HttpServiceSparqlEndpoint', () => { - let originalCluster: typeof cluster; - beforeAll(() => { - originalCluster = { ...cluster }; - }); - - beforeEach(() => { - process.exit = jest.fn(); - - // Assume worker thread in all tests by default - ( cluster).isMaster = false; - jest.clearAllMocks(); - - process.removeAllListeners('message'); - process.removeAllListeners('uncaughtException'); - }); - - afterAll(() => { - Object.assign(cluster, originalCluster); - }); - - describe('constructor', () => { - it('shouldn\'t error if no args are supplied', () => { - expect(() => new HttpServiceSparqlEndpoint({ ...argsDefault })).not.toThrow('TODO'); - }); - - it('should set fields with values from args if present', () => { - const args = { context: { test: 'test' }, timeout: 4_321, port: 24_321, invalidateCacheBeforeQuery: true }; - const instance = new HttpServiceSparqlEndpoint({ ...argsDefault, ...args }); - - expect(instance.context).toEqual({ test: 'test' }); - expect(instance.timeout).toBe(4_321); - expect(instance.port).toBe(24_321); - expect(instance.invalidateCacheBeforeQuery).toBeTruthy(); - }); - - it('should set default field values for fields that aren\'t in args', () => { - const args = { ...argsDefault }; - const instance = new HttpServiceSparqlEndpoint(args); - - expect(instance.context).toEqual({}); - expect(instance.timeout).toBe(60_000); - expect(instance.port).toBe(3_000); - expect(instance.invalidateCacheBeforeQuery).toBeFalsy(); - }); - }); - - describe('runArgsInProcess', () => { - const source = 'http://localhost:8080/data.jsonld'; - const context = - '{ "sources": [{ "type": "file", "value" : "http://localhost:8080/data.jsonld" }]}'; - let stdout: PassThrough; - let stderr: PassThrough; - const moduleRootPath = 'test_modulerootpath'; - const env = { COMUNICA_CONFIG: 'test_config' }; - const defaultConfigPath = 'test_defaultConfigPath'; - const exit = jest.fn(); - - beforeEach(() => { - exit.mockClear(); - stdout = new PassThrough(); - stderr = new PassThrough(); - }); - - it('exits on error', async() => { - await HttpServiceSparqlEndpoint.runArgsInProcess( - [ source ], - stdout, - stderr, - 'rejecting_engine_promise', - env, - defaultConfigPath, - exit, - ); - - expect(exit).toHaveBeenCalledWith(1); - stderr.end(); - await expect(stringifyStream(stderr)).resolves.toBe('REASON'); - }); - - it('handles valid args', async() => { - await HttpServiceSparqlEndpoint.runArgsInProcess( - [ source ], - stdout, - stderr, - moduleRootPath, - env, - defaultConfigPath, - exit, - ); - - expect(http.createServer).toHaveBeenCalledTimes(1); // Implicitly checking whether .run has been called - expect(exit).not.toHaveBeenCalled(); - }); - - it('handles the -c option', async() => { - await HttpServiceSparqlEndpoint.runArgsInProcess( - [ '-c', context ], - stdout, - stderr, - moduleRootPath, - env, - defaultConfigPath, - exit, - ); - - expect(http.createServer).toHaveBeenCalledTimes(1); // Implicitly checking whether .run has been called - }); - - it('handles the old contex passing form', async() => { - await HttpServiceSparqlEndpoint.runArgsInProcess( - [ context ], - stdout, - stderr, - moduleRootPath, - env, - defaultConfigPath, - exit, - ); - - expect(http.createServer).toHaveBeenCalledTimes(1); // Implicitly checking whether .run has been called - }); - - it('handles the --help option', async() => { - await HttpServiceSparqlEndpoint.runArgsInProcess( - [ '--help' ], - stdout, - stderr, - moduleRootPath, - env, - defaultConfigPath, - exit, - ); - - expect(exit).toHaveBeenCalledWith(1); - stderr.end(); - const err = await stringifyStream(stderr); - expect(err).toContain('exposes a SPARQL endpoint'); - expect(err).toContain('At least one source must be provided'); - }); - - it('handles the -h option', async() => { - await HttpServiceSparqlEndpoint.runArgsInProcess( - [ '-h' ], - stdout, - stderr, - moduleRootPath, - env, - defaultConfigPath, - exit, - ); - - expect(exit).toHaveBeenCalledWith(1); - stderr.end(); - const err = await stringifyStream(stderr); - expect(err).toContain('exposes a SPARQL endpoint'); - expect(err).toContain('At least one source must be provided'); - }); - - it('handles the --version option', async() => { - await HttpServiceSparqlEndpoint.runArgsInProcess( - [ source, '--version' ], - stdout, - stderr, - moduleRootPath, - env, - defaultConfigPath, - exit, - ); - - expect(exit).toHaveBeenCalledWith(1); - stderr.end(); - const err = await stringifyStream(stderr); - expect(err).toContain('Comunica Engine'); - expect(err).toContain('dev'); - }); - - it('handles the -v option', async() => { - jest.spyOn(CliArgsHandlerBase, 'isDevelopmentEnvironment').mockReturnValue(false); - await HttpServiceSparqlEndpoint.runArgsInProcess( - [ source, '-v' ], - stdout, - stderr, - moduleRootPath, - env, - defaultConfigPath, - exit, - ); - - expect(exit).toHaveBeenCalledWith(1); - stderr.end(); - const err = await stringifyStream(stderr); - expect(err).toContain('Comunica Engine'); - expect(err).not.toContain('dev'); - }); - - it('handles no args', async() => { - await HttpServiceSparqlEndpoint.runArgsInProcess( - [], - stdout, - stderr, - moduleRootPath, - env, - defaultConfigPath, - exit, - ); - - expect(exit).toHaveBeenCalledWith(1); - stderr.end(); - const err = await stringifyStream(stderr); - expect(err).toContain('exposes a SPARQL endpoint'); - expect(err).toContain('At least one source must be provided'); - }); - }); - - describe('generateConstructorArguments', () => { - let testCommandlineArguments: any; - const contextCommandlineArgument = JSON.stringify(testArgumentDict); - const moduleRootPath = 'test_modulerootpath'; - let env: any; - let stderr: any; - const exit = jest.fn(); - const defaultConfigPath = 'test_defaultConfigPath'; - beforeEach(() => { - env = { COMUNICA_CONFIG: 'test_config' }; - fs.existsSync.mockReturnValue(true); - testCommandlineArguments = [ '-c', contextCommandlineArgument ]; - stderr = new PassThrough(); - exit.mockClear(); - }); - - it('should return an object containing the correct moduleRootPath configPath', async() => { - await expect((HttpServiceSparqlEndpoint - .generateConstructorArguments( - testCommandlineArguments, - moduleRootPath, - env, - defaultConfigPath, - stderr, - exit, - [], - ))).resolves - .toMatchObject({ configPath: env.COMUNICA_CONFIG, mainModulePath: moduleRootPath }); - }); - - it('should use defaultConfigPath if env has no COMUNICA_CONFIG constant', async() => { - env = {}; - await expect((HttpServiceSparqlEndpoint - .generateConstructorArguments( - testCommandlineArguments, - moduleRootPath, - env, - defaultConfigPath, - stderr, - exit, - [], - ))).resolves - .toMatchObject({ configPath: defaultConfigPath, mainModulePath: moduleRootPath }); - }); - - it('should use logger from given context if available', async() => { - fs.existsSync.mockReturnValue(false); - const context = { ...testArgumentDict, log: new LoggerPretty({ level: 'test_loglevel' }) }; - - const log = (await HttpServiceSparqlEndpoint.generateConstructorArguments( - [ '-c', JSON.stringify(context) ], - moduleRootPath, - env, - defaultConfigPath, - stderr, - exit, - [], - )).context.log; - - expect(log).toMatchObject({ level: 'test_loglevel' }); - }); - - it('should use loglevel from commandline arguments if available', async() => { - testCommandlineArguments.push('-l', 'test_loglevel'); - const log = (await HttpServiceSparqlEndpoint - .generateConstructorArguments( - testCommandlineArguments, - moduleRootPath, - env, - defaultConfigPath, - stderr, - exit, - [], - )) - .context.log; - - expect(log).toBeInstanceOf(LoggerPretty); - expect(log.level).toBe('test_loglevel'); - }); - - it('should set a logger with loglevel "warn" if none is defined in the given context', async() => { - const log = (await HttpServiceSparqlEndpoint - .generateConstructorArguments( - testCommandlineArguments, - moduleRootPath, - env, - defaultConfigPath, - stderr, - exit, - [], - )) - .context.log; - - expect(log).toBeInstanceOf(LoggerPretty); - expect(log.level).toBe('warn'); - }); - - it('should read timeout from the commandline options or use correct default', async() => { - expect((await HttpServiceSparqlEndpoint - .generateConstructorArguments( - testCommandlineArguments, - moduleRootPath, - env, - defaultConfigPath, - stderr, - exit, - [], - )) - .timeout).toBe(60 * 1_000); - - testCommandlineArguments.push('-t', 5); - expect((await HttpServiceSparqlEndpoint - .generateConstructorArguments( - testCommandlineArguments, - moduleRootPath, - env, - defaultConfigPath, - stderr, - exit, - [], - )) - .timeout).toBe(5 * 1_000); - }); - - it('should read port from the commandline options or use correct default', async() => { - expect((await HttpServiceSparqlEndpoint - .generateConstructorArguments( - testCommandlineArguments, - moduleRootPath, - env, - defaultConfigPath, - stderr, - exit, - [], - )) - .port).toBe(3_000); - - testCommandlineArguments.push('-p', 4_321); - expect((await HttpServiceSparqlEndpoint - .generateConstructorArguments( - testCommandlineArguments, - moduleRootPath, - env, - defaultConfigPath, - stderr, - exit, - [], - )) - .port).toBe(4_321); - }); - - it('should read cache invalidation from the commandline options or use correct default', async() => { - expect((await HttpServiceSparqlEndpoint - .generateConstructorArguments( - testCommandlineArguments, - moduleRootPath, - env, - defaultConfigPath, - stderr, - exit, - [], - )) - .invalidateCacheBeforeQuery).toBeFalsy(); - - testCommandlineArguments.push('-i'); - expect((await HttpServiceSparqlEndpoint - .generateConstructorArguments( - testCommandlineArguments, - moduleRootPath, - env, - defaultConfigPath, - stderr, - exit, - [], - )) - .invalidateCacheBeforeQuery).toBe(true); - }); - - it('should try to get context by parsing the commandline argument if it\'s not an existing file', async() => { - fs.existsSync.mockReturnValue(false); - - expect((await HttpServiceSparqlEndpoint - .generateConstructorArguments( - testCommandlineArguments, - moduleRootPath, - env, - defaultConfigPath, - stderr, - exit, - [], - )) - .context).toMatchObject(testArgumentDict); - }); - - it('should read context from file if commandline argument is an existing file', async() => { - expect((await HttpServiceSparqlEndpoint - .generateConstructorArguments( - testCommandlineArguments, - moduleRootPath, - env, - defaultConfigPath, - stderr, - exit, - [], - )) - .context).toMatchObject(testFileContentDict); - }); - - it('should read update flag from the commandline options or use correct default', async() => { - expect((await HttpServiceSparqlEndpoint - .generateConstructorArguments( - testCommandlineArguments, - moduleRootPath, - env, - defaultConfigPath, - stderr, - exit, - [], - )) - .context[KeysQueryOperation.readOnly.name]).toBe(true); - - testCommandlineArguments.push('-u'); - expect((await HttpServiceSparqlEndpoint - .generateConstructorArguments( - testCommandlineArguments, - moduleRootPath, - env, - defaultConfigPath, - stderr, - exit, - [], - )) - .context[KeysQueryOperation.readOnly.name]).toBe(false); - }); - }); - - describe('An HttpServiceSparqlEndpoint instance', () => { - let instance: HttpServiceSparqlEndpoint; - beforeEach(() => { - instance = new HttpServiceSparqlEndpoint({ ...argsDefault, workers: 4 }); - }); - - describe('run', () => { - const stdout = new PassThrough(); - const stderr = new PassThrough(); - beforeEach(() => { - http.createServer.mockClear(); - ( instance.handleRequest).bind = jest.fn(() => 'handleRequest_bound'); - }); - - it('should set the server\'s port number correctly', async() => { - const port = 201_331; - const timeout = 201_331; - ( instance).port = port; - ( instance).timeout = timeout; - await instance.run(stdout, stderr); - - const server = http.createServer.mock.results[0].value; - expect(server.listen).toHaveBeenCalledWith(port); - }); - - it('should call bind handleRequest with the correct arguments', async() => { - // See mock implementation of getResultMediaTypes in ../index - const variants = [ - { type: 'mtype_1', quality: 1 }, - { type: 'mtype_2', quality: 2 }, - { type: 'mtype_3', quality: 3 }, - { type: 'mtype_4', quality: 4 }, - ]; - await instance.run(stdout, stderr); - - expect(instance.handleRequest.bind).toHaveBeenCalledTimes(1); - expect(instance.handleRequest.bind) - .toHaveBeenCalledWith(instance, await instance.engine, variants, stdout, stderr); - }); - - it('should call createServer with the correct arguments', async() => { - await instance.run(stdout, stderr); - - expect(http.createServer).toHaveBeenCalledTimes(1); - expect(http.createServer).toHaveBeenLastCalledWith(( instance).handleRequest.bind()); - }); - - it('should end an open connection on shutdown messages', async() => { - let shutdownListener: any; - ( process).on = (event: any, listener: any): void => { - if (event === 'message') { - shutdownListener = listener; - } - }; - - // Start server - await instance.run(stdout, stderr); - const server = http.createServer.mock.results[0].value; - - // Open new connection - const response1 = new EventEmitter(); - (response1).end = jest.fn((message, resolve) => resolve()); - server.emit('request', undefined, response1); - - // Send shutdown message - shutdownListener('shutdown'); - - expect((response1).end).toHaveBeenCalledWith('!TIMEDOUT!', expect.anything()); - await new Promise(setImmediate); - expect(process.exit).toHaveBeenCalledTimes(1); - }); - - it('should end multiple open connections on shutdown messages', async() => { - let shutdownListener: any; - ( process).on = (event: any, listener: any): void => { - if (event === 'message') { - shutdownListener = listener; - } - }; - - // Start server - await instance.run(stdout, stderr); - const server = http.createServer.mock.results[0].value; - - // Open new connections - const response1 = new EventEmitter(); - (response1).end = jest.fn((message, resolve) => resolve()); - server.emit('request', undefined, response1); - const response2 = new EventEmitter(); - (response2).end = jest.fn((message, resolve) => resolve()); - server.emit('request', undefined, response2); - - // Send shutdown message - shutdownListener('shutdown'); - - await new Promise(setImmediate); - expect((response1).end).toHaveBeenCalledTimes(1); - expect((response1).end).toHaveBeenCalledWith('!TIMEDOUT!', expect.anything()); - expect((response2).end).toHaveBeenCalledTimes(1); - expect((response2).end).toHaveBeenCalledWith('!TIMEDOUT!', expect.anything()); - expect(process.exit).toHaveBeenCalledTimes(1); - }); - - it('should end only actively open connections on shutdown messages', async() => { - let shutdownListener: any; - ( process).on = (event: any, listener: any): void => { - if (event === 'message') { - shutdownListener = listener; - } - }; - - // Start server - await instance.run(stdout, stderr); - const server = http.createServer.mock.results[0].value; - - // Open new connections - const response1 = new EventEmitter(); - (response1).end = jest.fn((message, resolve) => resolve()); - server.emit('request', undefined, response1); - const response2 = new EventEmitter(); - (response2).end = jest.fn((message, resolve) => resolve()); - server.emit('request', undefined, response2); - response2.emit('close'); - - // Send shutdown message - shutdownListener('shutdown'); - - expect((response1).end).toHaveBeenCalledWith('!TIMEDOUT!', expect.anything()); - expect((response2).end).not.toHaveBeenCalled(); - await new Promise(setImmediate); - expect(process.exit).toHaveBeenCalledTimes(1); - }); - - it('should not end open connections on non-shutdown messages', async() => { - let shutdownListener: any; - ( process).on = (event: any, listener: any): void => { - if (event === 'message') { - shutdownListener = listener; - } - }; - - // Start server - await instance.run(stdout, stderr); - const server = http.createServer.mock.results[0].value; - - // Open new connection - const response1 = new EventEmitter(); - (response1).end = jest.fn((message, resolve) => resolve()); - server.emit('request', undefined, response1); - - // Send shutdown message - shutdownListener('non-shutdown'); - - expect((response1).end).not.toHaveBeenCalled(); - await new Promise(setImmediate); - expect(process.exit).not.toHaveBeenCalled(); - }); - - it('should end multiple open connections on uncaught exceptions', async() => { - let uncaughtExceptionListener: any; - ( process).on = (event: any, listener: any): void => { - if (event === 'uncaughtException') { - uncaughtExceptionListener = listener; - } - }; - - // Start server - await instance.run(stdout, stderr); - const server = http.createServer.mock.results[0].value; - - // Open new connections - const response1 = new EventEmitter(); - (response1).end = jest.fn((message, resolve) => resolve()); - server.emit('request', undefined, response1); - const response2 = new EventEmitter(); - (response2).end = jest.fn((message, resolve) => resolve()); - server.emit('request', undefined, response2); - - // Send shutdown message - uncaughtExceptionListener(new Error('sparql endpoint uncaught exception')); - - await new Promise(setImmediate); - expect((response1).end).toHaveBeenCalledTimes(1); - expect((response1).end).toHaveBeenCalledWith('!ERROR!', expect.anything()); - expect((response2).end).toHaveBeenCalledTimes(1); - expect((response2).end).toHaveBeenCalledWith('!ERROR!', expect.anything()); - expect(process.exit).toHaveBeenCalledTimes(1); - }); - }); - - describe('run as a master', () => { - const stdout = new PassThrough(); - const stderr = new PassThrough(); - beforeEach(() => { - // Assume worker thread in all tests by default - ( cluster).isMaster = true; - ( cluster).fork = jest.fn(); - ( cluster).disconnect = jest.fn(); - ( cluster).on = jest.fn(); - ( process).once = jest.fn(); - }); - - it('should invoke fork for each worker', async() => { - await instance.run(stdout, stderr); - - expect(cluster.fork).toHaveBeenCalledTimes(4); - }); - - it('should handle worker exits', async() => { - await instance.run(stdout, stderr); - - // Simulate listening event - const dummyWorker = new EventEmitter(); - (dummyWorker).process = {}; - ( jest.mocked(cluster.on).mock.calls[0][1])(dummyWorker); - - // Simulate exit event - dummyWorker.emit('exit'); - - expect(cluster.fork).toHaveBeenCalledTimes(5); - expect(cluster.disconnect).not.toHaveBeenCalled(); - }); - - it('should handle worker exits when exitedAfterDisconnect is true', async() => { - await instance.run(stdout, stderr); - - // Simulate listening event - const dummyWorker = new EventEmitter(); - (dummyWorker).exitedAfterDisconnect = true; - (dummyWorker).process = {}; - ( jest.mocked(cluster.on).mock.calls[0][1])(dummyWorker); - - // Simulate exit event - dummyWorker.emit('exit'); - - expect(cluster.fork).toHaveBeenCalledTimes(4); - }); - - it('should handle worker exits with code 9', async() => { - await instance.run(stdout, stderr); - - // Simulate listening event - const dummyWorker = new EventEmitter(); - (dummyWorker).process = {}; - ( jest.mocked(cluster.on).mock.calls[0][1])(dummyWorker); - - // Simulate exit event - dummyWorker.emit('exit', 9); - - expect(cluster.fork).toHaveBeenCalledTimes(4); - expect(cluster.disconnect).toHaveBeenCalledTimes(1); - }); - - it('should handle worker exits with signal SIGKILL', async() => { - await instance.run(stdout, stderr); - - // Simulate listening event - const dummyWorker = new EventEmitter(); - (dummyWorker).process = {}; - ( jest.mocked(cluster.on).mock.calls[0][1])(dummyWorker); - - // Simulate exit event - dummyWorker.emit('exit', undefined, 'SIGKILL'); - - expect(cluster.fork).toHaveBeenCalledTimes(4); - expect(cluster.disconnect).toHaveBeenCalledTimes(1); - }); - - it('should handle worker start messages', async() => { - await instance.run(stdout, stderr); - - // Simulate listening event - const dummyWorker: any = new EventEmitter(); - dummyWorker.send = jest.fn(); - dummyWorker.isConnected = jest.fn(() => true); - dummyWorker.process = { - pid: 123, - }; - ( jest.mocked(cluster.on).mock.calls[0][1])(dummyWorker); - - // Simulate start event - dummyWorker.emit('message', { type: 'start', queryId: 0 }); - - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 60_000); - expect(dummyWorker.send).not.toHaveBeenCalled(); - - // Simulate timeout is passed - jest.runAllTimers(); - - expect(dummyWorker.send).toHaveBeenCalledWith('shutdown'); - }); - - it('should handle worker end messages before timeout is reached', async() => { - await instance.run(stdout, stderr); - - // Simulate listening event - const dummyWorker: any = new EventEmitter(); - dummyWorker.send = jest.fn(); - dummyWorker.isConnected = jest.fn(() => true); - dummyWorker.process = { - pid: 123, - }; - ( jest.mocked(cluster.on).mock.calls[0][1])(dummyWorker); - - // Simulate start event - dummyWorker.emit('message', { type: 'start', queryId: 0 }); - - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 60_000); - expect(dummyWorker.send).not.toHaveBeenCalled(); - - // Simulate end event - dummyWorker.emit('message', { type: 'end', queryId: 0 }); - - expect(clearTimeout).toHaveBeenCalledTimes(1); - - expect(dummyWorker.send).not.toHaveBeenCalled(); - - // Simulate timeout is passed - jest.runAllTimers(); - - expect(dummyWorker.send).not.toHaveBeenCalled(); - }); - - it('should handle worker end messages after timeout is reached', async() => { - await instance.run(stdout, stderr); - - // Simulate listening event - const dummyWorker: any = new EventEmitter(); - dummyWorker.send = jest.fn(); - dummyWorker.isConnected = jest.fn(() => true); - dummyWorker.process = { - pid: 123, - }; - ( jest.mocked(cluster.on).mock.calls[0][1])(dummyWorker); - - // Simulate start event - dummyWorker.emit('message', { type: 'start', queryId: 0 }); - - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 60_000); - expect(dummyWorker.send).not.toHaveBeenCalled(); - - // Simulate timeout is passed - jest.runAllTimers(); - - expect(dummyWorker.send).toHaveBeenCalledWith('shutdown'); - - // Simulate end event - dummyWorker.emit('message', 'end'); - - expect(clearTimeout).not.toHaveBeenCalled(); - }); - - it('should handle SIGINTs', async() => { - await instance.run(stdout, stderr); - - // Simulate SIGINT event - ( jest.mocked(process.once).mock.calls[0][1])(); - - expect(cluster.disconnect).toHaveBeenCalledTimes(1); - }); - - it('should catch worker shutdown errors', async() => { - await instance.run(stdout, stderr); - - // Simulate listening event - const dummyWorker: any = new EventEmitter(); - dummyWorker.send = jest.fn(() => { - throw new Error('shutdown exception'); - }); - dummyWorker.isConnected = jest.fn(() => true); - dummyWorker.process = { - pid: 123, - }; - ( jest.mocked(cluster.on).mock.calls[0][1])(dummyWorker); - - // Simulate start event - dummyWorker.emit('message', { type: 'start', queryId: 0 }); - - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 60_000); - expect(dummyWorker.send).not.toHaveBeenCalled(); - - // Simulate timeout is passed - jest.runAllTimers(); - - expect(dummyWorker.send).toHaveBeenCalledWith('shutdown'); - }); - - it('should not send message to disconnected worker', async() => { - await instance.run(stdout, stderr); - - // Simulate listening event - const dummyWorker: any = new EventEmitter(); - dummyWorker.send = jest.fn(); - dummyWorker.isConnected = jest.fn(() => false); - dummyWorker.process = { - pid: 123, - }; - ( jest.mocked(cluster.on).mock.calls[0][1])(dummyWorker); - - // Simulate start event - dummyWorker.emit('message', { type: 'start', queryId: 0 }); - - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 60_000); - expect(dummyWorker.send).not.toHaveBeenCalled(); - - // Simulate timeout is passed - jest.runAllTimers(); - - expect(dummyWorker.send).not.toHaveBeenCalled(); - }); - }); - - describe('handleRequest', () => { - let engine: any; - let variants: any; - const stdout = new PassThrough(); - const stderr = new PassThrough(); - let request: any; - let response: any; - beforeEach(async() => { - jest.spyOn(instance, 'writeQueryResult').mockImplementation(); - engine = await new QueryEngineFactoryBase().create(); - variants = [{ type: 'test_type', quality: 1 }]; - request = makeRequest(); - response = new ServerResponseMock(); - }); - - function makeRequest() { - request = stringToStream('default_test_request_content'); - request.url = 'url_sparql'; - request.headers = { 'content-type': 'contenttypewhichdefinitelydoesnotexist', accept: '*/*' }; - return request; - } - - it('should use the empty query string when the request method equals GET and url parsing fails', async() => { - request.method = 'GET'; - request.url = 'url_undefined_query'; - await instance.handleRequest(engine, variants, stdout, stderr, request, response); - - expect(instance.writeQueryResult) - .toHaveBeenCalledWith( - engine, - stdout, - stderr, - request, - response, - undefined, - null, - false, - true, - 0, - ); - }); - - it('should use the parsed query string when the request method equals GET', async() => { - request.method = 'GET'; - await instance.handleRequest(engine, variants, stdout, stderr, request, response); - - expect(instance.writeQueryResult).toHaveBeenCalledWith( - engine, - stdout, - stderr, - request, - response, - { type: 'query', value: 'test_query' }, - null, - false, - true, - 0, - ); - }); - - it(`should set headonly and use the empty query string when the request method is HEAD and url parsing fails`, async() => { - request.method = 'HEAD'; - request.url = 'url_undefined_query'; - await instance.handleRequest(engine, variants, stdout, stderr, request, response); - - expect(instance.writeQueryResult).toHaveBeenCalledWith( - engine, - stdout, - stderr, - request, - response, - undefined, - null, - true, - true, - 0, - ); - }); - - it('should set headonly and use the parsed query string when the request method is HEAD', async() => { - request.method = 'HEAD'; - await instance.handleRequest(engine, variants, stdout, stderr, request, response); - - expect(instance.writeQueryResult).toHaveBeenCalledWith( - engine, - stdout, - stderr, - request, - response, - { type: 'query', value: 'test_query' }, - null, - true, - true, - 0, - ); - }); - - it('should call writeQueryResult with correct arguments if request method equals POST', async() => { - ( instance).parseBody = jest.fn(() => Promise.resolve({ type: 'query', value: 'test_parseBody_result' })); - request.method = 'POST'; - await instance.handleRequest(engine, variants, stdout, stderr, request, response); - - expect(instance.writeQueryResult).toHaveBeenCalledWith( - engine, - stdout, - stderr, - request, - response, - { type: 'query', value: 'test_parseBody_result' }, - null, - false, - false, - 0, - ); - }); - - it('should choose a mediaType if accept header is set', async() => { - const chosen = 'test_chosen_mediatype'; - variants = [{ type: chosen, quality: 1 }]; - request.headers = { accept: chosen }; - - ( instance).parseBody = jest.fn(() => Promise.resolve({ type: 'query', value: 'test_parseBody_result' })); - request.method = 'POST'; - await instance.handleRequest(engine, variants, stdout, stderr, request, response); - - expect(instance.writeQueryResult).toHaveBeenCalledWith( - engine, - stdout, - stderr, - request, - response, - { type: 'query', value: 'test_parseBody_result' }, - chosen, - false, - false, - 0, - ); - }); - - it('should choose the best matching mediaType when we can exactly match', async() => { - variants = [{ type: 'a/a', quality: 1 }, { type: 'b/b', quality: 0.9 }]; - request.headers = { accept: 'a/a,b/b' }; - - ( instance).parseBody = jest.fn(() => Promise.resolve({ type: 'query', value: 'test_parseBody_result' })); - request.method = 'POST'; - await instance.handleRequest(engine, variants, stdout, stderr, request, response); - - expect(instance.writeQueryResult).toHaveBeenCalledWith( - engine, - stdout, - stderr, - request, - response, - { type: 'query', value: 'test_parseBody_result' }, - 'a/a', - false, - false, - 0, - ); - }); - - it('should choose the best matching mediaType when we can exactly match with out-of-order q', async() => { - variants = [{ type: 'b/b', quality: 0.9 }, { type: 'a/a', quality: 1 }]; - request.headers = { accept: 'a/a,b/b' }; - - ( instance).parseBody = jest.fn(() => Promise.resolve({ type: 'query', value: 'test_parseBody_result' })); - request.method = 'POST'; - await instance.handleRequest(engine, variants, stdout, stderr, request, response); - - expect(instance.writeQueryResult).toHaveBeenCalledWith( - engine, - stdout, - stderr, - request, - response, - { type: 'query', value: 'test_parseBody_result' }, - 'a/a', - false, - false, - 0, - ); - }); - - it('should choose the second best matching mediaType when we can exactly match', async() => { - variants = [{ type: 'a/a', quality: 1 }, { type: 'b/b', quality: 0.9 }]; - request.headers = { accept: 'b/b' }; - - ( instance).parseBody = jest.fn(() => Promise.resolve({ type: 'query', value: 'test_parseBody_result' })); - request.method = 'POST'; - await instance.handleRequest(engine, variants, stdout, stderr, request, response); - - expect(instance.writeQueryResult).toHaveBeenCalledWith( - engine, - stdout, - stderr, - request, - response, - { type: 'query', value: 'test_parseBody_result' }, - 'b/b', - false, - false, - 0, - ); - }); - - it('should choose the mediaType when the first is unknown', async() => { - variants = [{ type: 'a/a', quality: 1 }, { type: 'b/b', quality: 0.9 }]; - request.headers = { accept: 'x/x,a/a;q=0.8,b/b;q=0.9' }; - - ( instance).parseBody = jest.fn(() => Promise.resolve({ type: 'query', value: 'test_parseBody_result' })); - request.method = 'POST'; - await instance.handleRequest(engine, variants, stdout, stderr, request, response); - - expect(instance.writeQueryResult).toHaveBeenCalledWith( - engine, - stdout, - stderr, - request, - response, - { type: 'query', value: 'test_parseBody_result' }, - 'b/b', - false, - false, - 0, - ); - }); - - it('should choose a null media type if accept header is *', async() => { - request.headers = { accept: '*' }; - - ( instance).parseBody = jest.fn(() => Promise.resolve({ type: 'query', value: 'test_parseBody_result' })); - request.method = 'POST'; - await instance.handleRequest(engine, variants, stdout, stderr, request, response); - - expect(instance.writeQueryResult).toHaveBeenCalledWith( - engine, - stdout, - stderr, - request, - response, - { type: 'query', value: 'test_parseBody_result' }, - null, - false, - false, - 0, - ); - }); - - it('should choose a null media type if accept header is */*', async() => { - request.headers = { accept: '*/*' }; - - ( instance).parseBody = jest.fn(() => Promise.resolve({ type: 'query', value: 'test_parseBody_result' })); - request.method = 'POST'; - await instance.handleRequest(engine, variants, stdout, stderr, request, response); - - expect(instance.writeQueryResult).toHaveBeenCalledWith( - engine, - stdout, - stderr, - request, - response, - { type: 'query', value: 'test_parseBody_result' }, - null, - false, - false, - 0, - ); - }); - - it('should choose a null mediaType if accept header is not set', async() => { - request.headers = {}; - - ( instance).parseBody = jest.fn(() => Promise.resolve({ type: 'query', value: 'test_parseBody_result' })); - request.method = 'POST'; - await instance.handleRequest(engine, variants, stdout, stderr, request, response); - - expect(instance.writeQueryResult).toHaveBeenCalledWith( - engine, - stdout, - stderr, - request, - response, - { type: 'query', value: 'test_parseBody_result' }, - null, - false, - false, - 0, - ); - }); - - it('should only invalidate cache if invalidateCacheBeforeQuery is set to true', async() => { - ( instance).invalidateCacheBeforeQuery = false; - await instance.handleRequest(engine, variants, stdout, stderr, request, response); - - expect(engine.invalidateHttpCache).not.toHaveBeenCalled(); - }); - - it('should invalidate cache if invalidateCacheBeforeQuery is set to true', async() => { - ( instance).invalidateCacheBeforeQuery = true; - await instance.handleRequest(engine, variants, stdout, stderr, request, response); - - expect(engine.invalidateHttpCache).toHaveBeenCalledTimes(1); - }); - - it('should respond with 404 when not sparql url or root url', async() => { - request.url = 'not_urlsparql'; - await instance.handleRequest(engine, variants, stdout, stderr, request, response); - - expect(response.writeHead).toHaveBeenCalledWith( - 404, - { 'content-type': HttpServiceSparqlEndpoint.MIME_JSON, 'Access-Control-Allow-Origin': '*' }, - ); - expect(response.end) - .toHaveBeenCalledWith(JSON.stringify({ message: 'Resource not found. Queries are accepted on /sparql.' })); - }); - - it('should respond with 404 when url undefined', async() => { - request.url = undefined; - await instance.handleRequest(engine, variants, stdout, stderr, request, response); - - expect(response.writeHead).toHaveBeenCalledWith( - 404, - { 'content-type': HttpServiceSparqlEndpoint.MIME_JSON, 'Access-Control-Allow-Origin': '*' }, - ); - expect(response.end) - .toHaveBeenCalledWith(JSON.stringify({ message: 'Resource not found. Queries are accepted on /sparql.' })); - }); - - it('should respond with 301 when GET method called on root url', async() => { - request.method = 'GET'; - request.url = '/'; - await instance.handleRequest(engine, variants, stdout, stderr, request, response); - expect(response.writeHead).toHaveBeenCalledWith(301, { 'content-type': HttpServiceSparqlEndpoint.MIME_JSON, 'Access-Control-Allow-Origin': '*', Location: 'http://localhost:3000/sparql' }); - expect(response.end) - .toHaveBeenCalledWith(JSON.stringify({ message: 'Queries are accepted on /sparql. Redirected.' })); - }); - }); - - describe('writeQueryResult', () => { - let response: any; - let request: any; - let query: IQueryBody; - let mediaType: any; - let endCalledPromise: any; - beforeEach(() => { - response = new ServerResponseMock(); - request = stringToStream('default_request_content'); - request.url = 'http://example.org/sparql'; - query = { - type: 'query', - value: 'default_test_query', - context: undefined, - }; - mediaType = 'default_test_mediatype'; - endCalledPromise = new Promise(resolve => response.onEnd = resolve); - }); - - it('should end the response with error message content when the query rejects', async() => { - query = { type: 'query', value: 'query_reject', context: undefined }; - await instance.writeQueryResult( - await new QueryEngineFactoryBase().create(), - new PassThrough(), - new PassThrough(), - request, - response, - query, - mediaType, - false, - true, - 0, - ); - - await expect(endCalledPromise).resolves.toBe('Rejected query'); - expect(response.writeHead).toHaveBeenLastCalledWith( - 400, - { 'content-type': HttpServiceSparqlEndpoint.MIME_PLAIN, 'Access-Control-Allow-Origin': '*' }, - ); - }); - - it(`should end the response with correct error message when the query cannot be serialized for given mediatype`, async() => { - mediaType = 'mediatype_throwerror'; - await instance.writeQueryResult( - await new QueryEngineFactoryBase().create(), - new PassThrough(), - new PassThrough(), - request, - response, - query, - mediaType, - false, - true, - 0, - ); - - await expect(endCalledPromise).resolves.toBe( - 'The response for the given query could not be serialized for the requested media type\n', - ); - expect(response.writeHead).toHaveBeenLastCalledWith( - 400, - { 'content-type': HttpServiceSparqlEndpoint.MIME_PLAIN, 'Access-Control-Allow-Origin': '*' }, - ); - }); - - it('should put the query result in the response if the query was successful', async() => { - await instance.writeQueryResult( - await new QueryEngineFactoryBase().create(), - new PassThrough(), - new PassThrough(), - request, - response, - query, - mediaType, - false, - true, - 0, - ); - - await expect(endCalledPromise).resolves.toBeFalsy(); - expect(response.writeHead).toHaveBeenCalledTimes(1); - expect(response.writeHead).toHaveBeenLastCalledWith( - 200, - { 'content-type': mediaType, 'Access-Control-Allow-Origin': '*' }, - ); - response.push(null); - const responseString = await stringifyStream(response); - expect(responseString).toBe('test_query_result'); - }); - - it(`should end the response with an internal server error message when the queryresult stream emits an error`, async() => { - mediaType = 'mediatype_queryresultstreamerror'; - await instance.writeQueryResult( - await new QueryEngineFactoryBase().create(), - new PassThrough(), - new PassThrough(), - request, - response, - query, - mediaType, - false, - true, - 0, - ); - - await expect(endCalledPromise).resolves.toBe('An internal server error occurred.\n'); - expect(response.writeHead).toHaveBeenCalledTimes(1); - expect(response.writeHead).toHaveBeenLastCalledWith( - 200, - { 'content-type': mediaType, 'Access-Control-Allow-Origin': '*' }, - ); - }); - - it('should only write the head when headOnly is true', async() => { - await instance.writeQueryResult( - await new QueryEngineFactoryBase().create(), - new PassThrough(), - new PassThrough(), - request, - response, - query, - mediaType, - true, - true, - 0, - ); - - expect(response.writeHead).toHaveBeenCalledTimes(1); - expect(response.writeHead).toHaveBeenLastCalledWith( - 200, - { 'content-type': mediaType, 'Access-Control-Allow-Origin': '*' }, - ); - expect(response.end).toHaveBeenCalledTimes(1); - response.push(null); - const responseString = await stringifyStream(response); - expect(responseString).toBe(''); - }); - - it('should write the service description when no query was defined', async() => { - // Create spies - const engine = await new QueryEngineFactoryBase().create(); - const spyWriteServiceDescription = jest.spyOn(instance, 'writeServiceDescription'); - const spyGetResultMediaTypeFormats = jest.spyOn(engine, 'getResultMediaTypeFormats'); - const spyResultToString = jest.spyOn(engine, 'resultToString'); - - // Invoke writeQueryResult - await instance.writeQueryResult( - engine, - new PassThrough(), - new PassThrough(), - request, - response, - { type: 'query', value: '', context: undefined }, - mediaType, - false, - true, - 0, - ); - - // Check output - await expect(endCalledPromise).resolves.toBeFalsy(); - expect(response.writeHead).toHaveBeenCalledTimes(1); - expect(response.writeHead).toHaveBeenLastCalledWith( - 200, - { 'content-type': mediaType, 'Access-Control-Allow-Origin': '*' }, - ); - response.push(null); - const responseString = await stringifyStream(response); - expect(responseString).toBe('test_query_result'); - - // Check if the SD logic has been called - expect(spyWriteServiceDescription).toHaveBeenCalledTimes(1); - - // Check if result media type formats have been retrieved - expect(spyGetResultMediaTypeFormats).toHaveBeenCalledTimes(1); - - // Check if result to string has been called with the correct arguments - expect(spyResultToString).toHaveBeenCalledTimes(1); - expect(spyResultToString.mock.calls[0][1]).toEqual(mediaType); - const s = 'http://example.org/sparql'; - const sd = 'http://www.w3.org/ns/sparql-service-description#'; - await expect((spyResultToString.mock.calls[0][0]).execute()).resolves.toEqual(new ArrayIterator([ - quad(s, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', `${sd}Service`), - quad(s, `${sd}endpoint`, '/sparql'), - quad(s, `${sd}url`, '/sparql'), - quad(s, `${sd}feature`, `${sd}BasicFederatedQuery`), - quad(s, `${sd}supportedLanguage`, `${sd}SPARQL10Query`), - quad(s, `${sd}supportedLanguage`, `${sd}SPARQL11Query`), - quad(s, `${sd}resultFormat`, 'ONE'), - quad(s, `${sd}resultFormat`, 'TWO'), - quad(s, `${sd}resultFormat`, 'THREE'), - quad(s, `${sd}resultFormat`, 'FOUR'), - ])); - }); - - it('should write the service description when no query was defined for HEAD', async() => { - // Create spies - const engine = await new QueryEngineFactoryBase().create(); - const spyWriteServiceDescription = jest.spyOn(instance, 'writeServiceDescription'); - const spyGetResultMediaTypeFormats = jest.spyOn(engine, 'getResultMediaTypeFormats'); - const spyResultToString = jest.spyOn(engine, 'resultToString'); - - // Invoke writeQueryResult - await instance.writeQueryResult( - engine, - new PassThrough(), - new PassThrough(), - request, - response, - { type: 'query', value: '', context: undefined }, - mediaType, - true, - true, - 0, - ); - - // Check output - await expect(endCalledPromise).resolves.toBeFalsy(); - expect(response.writeHead).toHaveBeenCalledTimes(1); - expect(response.writeHead).toHaveBeenLastCalledWith( - 200, - { 'content-type': mediaType, 'Access-Control-Allow-Origin': '*' }, - ); - response.push(null); - const responseString = await stringifyStream(response); - expect(responseString).toBe(''); - - // Check if the SD logic has been called - expect(spyWriteServiceDescription).toHaveBeenCalledTimes(1); - - // Check if further processing is not done - expect(spyGetResultMediaTypeFormats).toHaveBeenCalledTimes(0); - expect(spyResultToString).toHaveBeenCalledTimes(0); - }); - - it('should handle errors in service description stringification', async() => { - mediaType = 'mediatype_queryresultstreamerror'; - await instance.writeQueryResult( - await new QueryEngineFactoryBase().create(), - new PassThrough(), - new PassThrough(), - request, - response, - { type: 'query', value: '', context: undefined }, - mediaType, - false, - true, - 0, - ); - - await expect(endCalledPromise).resolves.toBe('An internal server error occurred.\n'); - expect(response.writeHead).toHaveBeenCalledTimes(1); - expect(response.writeHead).toHaveBeenLastCalledWith( - 200, - { 'content-type': mediaType, 'Access-Control-Allow-Origin': '*' }, - ); - }); - - it('should handle an invalid media type in service description', async() => { - mediaType = 'mediatype_queryresultstreamerror'; - await instance.writeQueryResult( - await new QueryEngineFactoryBase().create(), - new PassThrough(), - new PassThrough(), - request, - response, - { type: 'query', value: '', context: undefined }, - 'mediatype_throwerror', - false, - true, - 0, - ); - - await expect(endCalledPromise).resolves.toBe( - 'The response for the given query could not be serialized for the requested media type\n', - ); - expect(response.writeHead) - .toHaveBeenLastCalledWith(400, { 'content-type': 'text/plain', 'Access-Control-Allow-Origin': '*' }); - }); - - it('should fallback to SPARQL JSON for bindings if media type is falsy', async() => { - const engine = await new QueryEngineFactoryBase().create(); - engine.query = () => ({ resultType: 'bindings' }); - - await instance.writeQueryResult( - engine, - new PassThrough(), - new PassThrough(), - request, - response, - query, - '', - false, - true, - 0, - ); - - await expect(endCalledPromise).resolves.toBeFalsy(); - expect(response.writeHead).toHaveBeenCalledTimes(1); - expect(response.writeHead).toHaveBeenLastCalledWith( - 200, - { 'content-type': 'application/sparql-results+json', 'Access-Control-Allow-Origin': '*' }, - ); - response.push(null); - const responseString = await stringifyStream(response); - expect(responseString).toBe('test_query_result'); - }); - - it('should fallback to SPARQL JSON for booleans if media type is falsy', async() => { - const engine = await new QueryEngineFactoryBase().create(); - engine.query = () => ({ type: 'boolean' }); - - await instance.writeQueryResult( - engine, - new PassThrough(), - new PassThrough(), - request, - response, - query, - '', - false, - true, - 0, - ); - - await expect(endCalledPromise).resolves.toBeFalsy(); - expect(response.writeHead).toHaveBeenCalledTimes(1); - expect(response.writeHead).toHaveBeenLastCalledWith( - 200, - { 'content-type': 'application/sparql-results+json', 'Access-Control-Allow-Origin': '*' }, - ); - response.push(null); - const responseString = await stringifyStream(response); - expect(responseString).toBe('test_query_result'); - }); - - it('should fallback to TriG for quads if media type is falsy', async() => { - const engine = await new QueryEngineFactoryBase().create(); - engine.query = () => ({ resultType: 'quads' }); - - await instance.writeQueryResult( - engine, - new PassThrough(), - new PassThrough(), - request, - response, - query, - '', - false, - true, - 0, - ); - - await expect(endCalledPromise).resolves.toBeFalsy(); - expect(response.writeHead).toHaveBeenCalledTimes(1); - expect(response.writeHead).toHaveBeenLastCalledWith( - 200, - { 'content-type': 'application/trig', 'Access-Control-Allow-Origin': '*' }, - ); - response.push(null); - const responseString = await stringifyStream(response); - expect(responseString).toBe('test_query_result'); - }); - - it('should emit process start and end events', async() => { - jest.spyOn(process, 'send').mockImplementation(); - const engine = await new QueryEngineFactoryBase().create(); - engine.query = () => ({ resultType: 'bindings' }); - - await instance.writeQueryResult( - engine, - new PassThrough(), - new PassThrough(), - request, - response, - query, - '', - false, - true, - 0, - ); - - expect(process.send).toHaveBeenCalledTimes(1); - expect(process.send).toHaveBeenCalledWith({ type: 'start', queryId: 0 }); - - response.emit('close'); - expect(process.send).toHaveBeenCalledTimes(2); - expect(process.send).toHaveBeenCalledWith({ type: 'end', queryId: 0 }); - }); - - it('should fallback to simple for updates if media type is falsy', async() => { - const engine = await new QueryEngineFactoryBase().create(); - engine.query = () => ({ resultType: 'void', execute: () => Promise.resolve() }); - - await instance.writeQueryResult( - engine, - new PassThrough(), - new PassThrough(), - request, - response, - query, - '', - false, - true, - 0, - ); - - await expect(endCalledPromise).resolves.toBeFalsy(); - expect(response.writeHead).toHaveBeenCalledTimes(1); - expect(response.writeHead).toHaveBeenLastCalledWith( - 200, - { 'content-type': 'simple', 'Access-Control-Allow-Origin': '*' }, - ); - response.push(null); - const responseString = await stringifyStream(response); - expect(responseString).toBe('test_query_result'); - }); - - it('should set readOnly in the context if called with readOnly true', async() => { - const engine = await new QueryEngineFactoryBase().create(); - jest.spyOn(engine, 'query').mockImplementation(() => ({ resultType: 'bindings' })); - - await instance.writeQueryResult( - engine, - new PassThrough(), - new PassThrough(), - request, - response, - query, - '', - false, - true, - 0, - ); - - await expect(endCalledPromise).resolves.toBeFalsy(); - expect(engine.query).toHaveBeenCalledWith('default_test_query', { [KeysQueryOperation.readOnly.name]: true }); - }); - - it('should set not readOnly in the context if called with readOnly false', async() => { - const engine = await new QueryEngineFactoryBase().create(); - jest.spyOn(engine, 'query').mockImplementation(() => ({ resultType: 'bindings' })); - - await instance.writeQueryResult( - engine, - new PassThrough(), - new PassThrough(), - request, - response, - query, - '', - false, - false, - 0, - ); - - await expect(endCalledPromise).resolves.toBeFalsy(); - expect(engine.query).toHaveBeenCalledWith('default_test_query', {}); - }); - - it('should not override context entries by default', async() => { - const engine = await new QueryEngineFactoryBase().create(); - jest.spyOn(engine, 'query').mockImplementation(() => ({ resultType: 'bindings' })); - - query.context = { overrideKey: 'overrideValue' }; - - await instance.writeQueryResult( - engine, - new PassThrough(), - new PassThrough(), - request, - response, - query, - '', - false, - false, - 0, - ); - - await expect(endCalledPromise).resolves.toBeFalsy(); - expect(engine.query).toHaveBeenCalledWith('default_test_query', {}); - }); - - it('should override context entries if contextOverride is enabled', async() => { - const engine = await new QueryEngineFactoryBase().create(); - jest.spyOn(engine, 'query').mockImplementation(() => ({ resultType: 'bindings' })); - - query.context = { overrideKey: 'overrideValue' }; - - instance = new HttpServiceSparqlEndpoint({ ...argsDefault, workers: 4, contextOverride: true }); - await instance.writeQueryResult( - engine, - new PassThrough(), - new PassThrough(), - request, - response, - query, - '', - false, - false, - 0, - ); - - await expect(endCalledPromise).resolves.toBeFalsy(); - expect(engine.query).toHaveBeenCalledWith('default_test_query', { overrideKey: 'overrideValue' }); - }); - }); - - describe('stopResponse', () => { - let response: any; - let eventEmitter: any; - const endListener = jest.fn(); - let stderr: PassThrough; - beforeEach(() => { - endListener.mockClear(); - ( instance).timeout = 1_500; - response = new ServerResponseMock(); - eventEmitter = stringToStream('queryresult'); - eventEmitter.addListener('test', endListener); - stderr = new PassThrough(); - }); - - it('should not error when eventEmitter is undefined', async() => { - instance.stopResponse(response, 0, stderr); - response.emit('close'); - }); - - it('should do nothing when no timeout or close event occurs', async() => { - instance.stopResponse(response, 0, stderr, eventEmitter); - // Not waiting for timeout to occur - - expect(eventEmitter.listeners('test')).toHaveLength(1); - expect(response.end).not.toHaveBeenCalled(); - expect(endListener).not.toHaveBeenCalled(); - expect(process.exit).not.toHaveBeenCalled(); - }); - - it('should remove event eventlisteners from eventEmitter when response is closed', async() => { - instance.stopResponse(response, 0, stderr, eventEmitter); - response.emit('close'); - - expect(eventEmitter.listeners('test')).toHaveLength(0); - expect(response.end).toHaveBeenCalledTimes(1); - expect(process.exit).not.toHaveBeenCalled(); - }); - - it('should void error events', async() => { - instance.stopResponse(response, 0, stderr, eventEmitter); - response.emit('close'); - - eventEmitter.emit('error', new Error('Ignored stopResponse error')); - }); - - it('should exit when freshWorkerPerQuery is enabled', async() => { - instance = new HttpServiceSparqlEndpoint({ ...argsDefault, workers: 4, freshWorkerPerQuery: true }); - - instance.stopResponse(response, 0, stderr, eventEmitter); - response.emit('close'); - - expect(process.exit).toHaveBeenCalledWith(15); - }); - }); - - describe('parseBody', () => { - let httpRequestMock: any; - const testRequestBody = 'teststring'; - beforeEach(() => { - httpRequestMock = stringToStream(testRequestBody); - httpRequestMock.headers = { 'content-type': 'contenttypewhichdefinitelydoesnotexist' }; - }); - - it('should reject if the stream emits an error', async() => { - httpRequestMock._read = () => httpRequestMock.emit('error', new Error('error')); - await expect(instance.parseBody(httpRequestMock)).rejects.toBeTruthy(); - }); - - it('should set encoding of the request to utf8', async() => { - httpRequestMock.setEncoding(null); - ( instance).parseBody(httpRequestMock) - .catch(() => { - // Ignore error - }); - expect(httpRequestMock._readableState.encoding).toBe('utf8'); - }); - - it('should reject without content-type', async() => { - httpRequestMock.headers = {}; - await expect(instance.parseBody(httpRequestMock)).rejects - .toThrow(`Invalid POST body received, query type could not be determined`); - }); - - it('should reject if the query is invalid and the content-type is application/x-www-form-urlencoded', async() => { - httpRequestMock.headers = { 'content-type': 'application/x-www-form-urlencoded' }; - await expect(instance.parseBody(httpRequestMock)).rejects - .toThrow(`Invalid POST body received, query type could not be determined`); - }); - - it('should parse query from url if the content-type is application/x-www-form-urlencoded', async() => { - const exampleQueryString = 'query=SELECT%20*%20WHERE%20%7B%3Fs%20%3Fp%20%3Fo%7D'; - httpRequestMock = stringToStream(exampleQueryString); - httpRequestMock.headers = { 'content-type': 'application/x-www-form-urlencoded' }; - - await expect(instance.parseBody(httpRequestMock)).resolves.toEqual({ - type: 'query', - value: querystring.parse(exampleQueryString).query, - }); - }); - - it('should parse update from url if the content-type is application/x-www-form-urlencoded', async() => { - const exampleQueryString = 'update=INSERT%20*%20WHERE%20%7B%3Fs%20%3Fp%20%3Fo%7D'; - httpRequestMock = stringToStream(exampleQueryString); - httpRequestMock.headers = { 'content-type': 'application/x-www-form-urlencoded' }; - - await expect(instance.parseBody(httpRequestMock)).resolves.toEqual({ - type: 'void', - value: querystring.parse(exampleQueryString).update, - }); - }); - - it('should parse context from url if the content-type is application/x-www-form-urlencoded', async() => { - const exampleQueryString = 'query=SELECT%20*%20WHERE%20%7B%3Fs%20%3Fp%20%3Fo%7D&context={"a":"b"}'; - httpRequestMock = stringToStream(exampleQueryString); - httpRequestMock.headers = { 'content-type': 'application/x-www-form-urlencoded' }; - - await expect(instance.parseBody(httpRequestMock)).resolves.toEqual({ - type: 'query', - value: querystring.parse(exampleQueryString).query, - context: { a: 'b' }, - }); - }); - - it(`should reject if the context is invalid and the content-type is application/x-www-form-urlencoded`, async() => { - const exampleQueryString = 'query=SELECT%20*%20WHERE%20%7B%3Fs%20%3Fp%20%3Fo%7D&context={"a:"b"}'; - httpRequestMock = stringToStream(exampleQueryString); - httpRequestMock.headers = { 'content-type': 'application/x-www-form-urlencoded' }; - - await expect(instance.parseBody(httpRequestMock)).rejects - .toThrow('Invalid POST body with context received'); - }); - - it('should reject if content-type is not application/[sparql-query|x-www-form-urlencoded]', async() => { - await expect(instance.parseBody(httpRequestMock)).rejects - .toThrow(`Invalid POST body received, query type could not be determined`); - }); - - it('should return input body if content-type is application/sparql-query', async() => { - httpRequestMock.headers = { 'content-type': 'application/sparql-query' }; - await expect(instance.parseBody(httpRequestMock)).resolves.toEqual({ - type: 'query', - value: testRequestBody, - }); - }); - - it('should return input body if content-type is application/sparql-update', async() => { - httpRequestMock.headers = { 'content-type': 'application/sparql-update' }; - await expect(instance.parseBody(httpRequestMock)).resolves.toEqual({ - type: 'void', - value: testRequestBody, - }); - }); - }); - }); -}); diff --git a/packages/actor-init-query/test/QueryEngineBase-test.ts b/packages/actor-init-query/test/QueryEngineBase-test.ts deleted file mode 100644 index 356d8d0..0000000 --- a/packages/actor-init-query/test/QueryEngineBase-test.ts +++ /dev/null @@ -1,502 +0,0 @@ -import { Readable, Transform } from 'node:stream'; -import { BindingsFactory } from '@comunica/bindings-factory'; -import { KeysInitQuery } from '@comunica/context-entries'; -import { Bus, ActionContext } from '@comunica/core'; -import { MetadataValidationState } from '@comunica/metadata'; -import type { - IActionContext, - QueryStringContext, - IQueryBindingsEnhanced, - IQueryQuadsEnhanced, - QueryType, - IQueryOperationResultQuads, - IQueryOperationResultBindings, - IQueryOperationResultBoolean, - IQueryOperationResultVoid, - IQueryEngine, -} from '@comunica/types'; -import type * as RDF from '@rdfjs/types'; -import arrayifyStream from 'arrayify-stream'; -import { ArrayIterator } from 'asynciterator'; -import { DataFactory } from 'rdf-data-factory'; -import { translate } from 'sparqlalgebrajs'; -import { QueryEngineBase } from '../lib'; -import { ActorInitQuery } from '../lib/ActorInitQuery'; -import { ActorInitQueryBase } from '../lib/ActorInitQueryBase'; -import '@comunica/jest'; -import 'jest-rdf'; - -const DF = new DataFactory(); -const BF = new BindingsFactory(DF, {}); - -describe('ActorInitQueryBase', () => { - it('should not allow invoking its run method', async() => { - await expect(new ( ActorInitQueryBase)({ bus: new Bus({ name: 'bus' }) }).run()).rejects.toBeTruthy(); - }); -}); - -describe('QueryEngineBase', () => { - let bus: any; - let mediatorQueryProcess: any; - let mediatorSparqlSerialize: any; - let mediatorHttpInvalidate: any; - let actorInitQuery: ActorInitQuery; - let context: IActionContext; - let input: any; - - beforeEach(() => { - bus = new Bus({ name: 'bus' }); - input = new Readable({ objectMode: true }); - input._read = () => { - const triple = { a: 'triple' }; - input.push(triple); - input.push(null); - }; - mediatorQueryProcess = { - mediate: jest.fn((action: any) => { - if (action.context.has(KeysInitQuery.explain)) { - return Promise.resolve({ - result: { - explain: 'true', - data: 'EXPLAINED', - }, - }); - } - return action.query === 'INVALID' ? - Promise.reject(new Error('Invalid query')) : - Promise.resolve({ - result: { type: 'bindings', bindingsStream: input, metadata: () => ({}), context: action.context }, - }); - }), - }; - mediatorSparqlSerialize = { - mediate: (arg: any) => Promise.resolve(arg.mediaTypes ? - { mediaTypes: arg } : - { - handle: { - data: arg.handle.bindingsStream - .pipe(new Transform({ - objectMode: true, - transform: (e: any, enc: any, cb: any) => cb(null, JSON.stringify(e)), - })), - }, - }), - }; - mediatorHttpInvalidate = { - mediate: (arg: any) => Promise.resolve(true), - }; - context = new ActionContext(); - }); - - describe('The QueryEngineBase module', () => { - it('should be a function', () => { - expect(QueryEngineBase).toBeInstanceOf(Function); - }); - - it('should be a QueryEngineBase constructor', () => { - expect(new QueryEngineBase(actorInitQuery)).toBeInstanceOf(QueryEngineBase); - expect(new QueryEngineBase(actorInitQuery)).toBeInstanceOf(QueryEngineBase); - }); - - it('should not be able to create new QueryEngineBase objects without \'new\'', () => { - expect(() => { - ( QueryEngineBase)(); - }).toThrow(`Class constructor QueryEngineBase cannot be invoked without 'new'`); - }); - }); - - describe('An QueryEngineBase instance', () => { - const queryString = 'SELECT * WHERE { ?s ?p ?o } LIMIT 100'; - let actor: ActorInitQuery; - let queryEngine: IQueryEngine; - - beforeEach(() => { - const defaultQueryInputFormat = 'sparql'; - - actor = new ActorInitQuery({ - bus, - defaultQueryInputFormat, - mediatorHttpInvalidate, - mediatorQueryProcess, - mediatorQueryResultSerialize: mediatorSparqlSerialize, - mediatorQueryResultSerializeMediaTypeCombiner: mediatorSparqlSerialize, - mediatorQueryResultSerializeMediaTypeFormatCombiner: mediatorSparqlSerialize, - name: 'actor', - }); - queryEngine = new QueryEngineBase(actor); - }); - - describe('invalidateHttpCache', () => { - it('should call the HTTP invalidate mediator', async() => { - jest.spyOn(mediatorHttpInvalidate, 'mediate'); - await queryEngine.invalidateHttpCache('a'); - expect(mediatorHttpInvalidate.mediate).toHaveBeenCalledWith({ context, url: 'a' }); - }); - }); - - describe('query', () => { - it('should apply bindings when initialBindings are passed via the context', async() => { - const ctx: QueryStringContext = { - sources: [ 'dummy' ], - '@comunica/actor-init-query:initialBindings': BF.bindings([ - [ DF.variable('s'), DF.literal('sl') ], - ]), - }; - await expect(queryEngine.query('SELECT * WHERE { ?s ?p ?o }', ctx)) - .resolves.toBeTruthy(); - }); - - it('should apply bindings when initialBindings in the old format are passed via the context', async() => { - const ctx: QueryStringContext = { - sources: [ 'dummy' ], - initialBindings: BF.bindings([ - [ DF.variable('s'), DF.literal('sl') ], - ]), - }; - await expect(queryEngine.query('SELECT * WHERE { ?s ?p ?o }', ctx)) - .resolves.toBeTruthy(); - }); - - it('should apply bindings when sources in the old format are passed via the context', async() => { - await expect(queryEngine.query('SELECT * WHERE { ?s ?p ?o }', { sources: [ 'abc' ]})) - .resolves.toBeTruthy(); - }); - - it('should allow query to be called without context', async() => { - await expect(queryEngine.query('SELECT * WHERE { ?s ?p ?o }')) - .resolves.toBeTruthy(); - }); - - it('should allow KeysInitSparql.queryTimestamp to be set', async() => { - const ctx: QueryStringContext = { sources: [ 'dummy' ], [KeysInitQuery.queryTimestamp.name]: new Date() }; - await expect(queryEngine.query('SELECT * WHERE { ?s ?p ?o }', ctx)) - .resolves.toBeTruthy(); - }); - - it('should allow a parsed query to be passed', async() => { - await expect(queryEngine.query(translate('SELECT * WHERE { ?s ?p ?o }'))) - .resolves.toBeTruthy(); - }); - - it('should not modify the baseIRI without BASE in query', async() => { - expect(( (await queryEngine.query('SELECT * WHERE { ?s ?p ?o }')).context) - .toJS()['@comunica/actor-init-query:baseIRI']).toBeFalsy(); - }); - - it('should allow process actors to modify the context', async() => { - mediatorQueryProcess.mediate = (action: any) => { - return Promise.resolve({ - result: { - type: 'bindings', - bindingsStream: input, - metadata: () => ({}), - context: action.context.setRaw('the-answer', 42), - }, - }); - }; - const result = await queryEngine.query('SELECT * WHERE { ?s ?p ?o }'); - expect(result).toHaveProperty('context'); - expect(( result.context).getRaw('the-answer')).toBe(42); - }); - - it('should return a rejected promise on an invalid request', async() => { - const ctx: QueryStringContext = { sources: [ 'abc' ]}; - // Make it reject instead of reading input - mediatorQueryProcess.mediate = (action: any) => Promise.reject(new Error('a')); - await expect(queryEngine.query('INVALID QUERY', ctx)).rejects.toBeTruthy(); - }); - - it('should return a rejected promise on an explain', async() => { - const ctx: QueryStringContext = { sources: [ 'abc' ], [KeysInitQuery.explain.name]: 'parsed' }; - await expect(queryEngine.query('BLA', ctx)).rejects - .toThrow('Tried to explain a query when in query-only mode'); - }); - }); - - describe('SparqlQueryable methods', () => { - describe('queryBindings', () => { - it('handles a valid bindings query', async() => { - input = new ArrayIterator([ - BF.bindings([ - [ DF.variable('a'), DF.namedNode('ex:a') ], - ]), - ]); - await expect(queryEngine.queryBindings('SELECT ...')).resolves.toEqualBindingsStream([ - BF.bindings([ - [ DF.variable('a'), DF.namedNode('ex:a') ], - ]), - ]); - }); - - it('rejects for an invalid bindings query', async() => { - jest.spyOn(mediatorQueryProcess, 'mediate').mockResolvedValue({ result: { type: 'void' }}); - await expect(queryEngine.queryBindings('INSERT ...')).rejects - .toThrow(`Query result type 'bindings' was expected, while 'void' was found.`); - }); - }); - - describe('queryQuads', () => { - it('handles a valid bindings query', async() => { - input = new ArrayIterator([ - DF.quad(DF.namedNode('ex:a'), DF.namedNode('ex:a'), DF.namedNode('ex:a')), - ]); - jest.spyOn(mediatorQueryProcess, 'mediate') - .mockResolvedValue({ result: { type: 'quads', quadStream: input }}); - await expect(arrayifyStream(await queryEngine.queryQuads('CONSTRUCT ...'))).resolves.toEqualRdfQuadArray([ - DF.quad(DF.namedNode('ex:a'), DF.namedNode('ex:a'), DF.namedNode('ex:a')), - ]); - }); - - it('rejects for an invalid bindings query', async() => { - jest.spyOn(mediatorQueryProcess, 'mediate').mockResolvedValue({ result: { type: 'void' }}); - await expect(queryEngine.queryQuads('INSERT ...')).rejects - .toThrow(`Query result type 'quads' was expected, while 'void' was found.`); - }); - }); - - describe('queryBoolean', () => { - it('handles a valid boolean query', async() => { - jest.spyOn(mediatorQueryProcess, 'mediate').mockResolvedValue({ - result: { - type: 'boolean', - execute: () => Promise.resolve(true), - }, - }); - await expect(queryEngine.queryBoolean('ASK ...')).resolves.toBe(true); - }); - - it('rejects for an invalid boolean query', async() => { - jest.spyOn(mediatorQueryProcess, 'mediate').mockResolvedValue({ result: { type: 'void' }}); - await expect(queryEngine.queryBoolean('INSERT ...')).rejects - .toThrow(`Query result type 'boolean' was expected, while 'void' was found.`); - }); - }); - - describe('queryVoid', () => { - it('handles a valid void query', async() => { - jest.spyOn(mediatorQueryProcess, 'mediate').mockResolvedValue({ - result: { - type: 'void', - execute: () => Promise.resolve(true), - }, - }); - await expect(queryEngine.queryVoid('INSERT ...')).resolves.toBe(true); - }); - - it('rejects for an invalid void query', async() => { - jest.spyOn(mediatorQueryProcess, 'mediate').mockResolvedValue({ - result: { type: 'boolean' }, - }); - await expect(queryEngine.queryVoid('ASK ...')).rejects - .toThrow(`Query result type 'void' was expected, while 'boolean' was found.`); - }); - }); - }); - - describe('getResultMediaTypeFormats', () => { - it('should return the media type formats', async() => { - const med: any = { - mediate: (arg: any) => Promise.resolve({ mediaTypeFormats: { data: 'DATA' }}), - }; - actor = new ActorInitQuery({ - bus, - mediatorHttpInvalidate, - mediatorQueryProcess, - mediatorQueryResultSerialize: med, - mediatorQueryResultSerializeMediaTypeCombiner: med, - mediatorQueryResultSerializeMediaTypeFormatCombiner: med, - name: 'actor', - queryString, - }); - queryEngine = new QueryEngineBase(actor); - await expect(queryEngine.getResultMediaTypeFormats()) - .resolves.toEqual({ data: 'DATA' }); - }); - }); - }); - - describe('An QueryEngineBase instance for quads', () => { - let actor: ActorInitQuery; - let queryEngine: QueryEngineBase; - - beforeEach(() => { - mediatorQueryProcess.mediate = (action: any) => action.operation.query === 'INVALID' ? - Promise.reject(new Error('a')) : - Promise.resolve({ quadStream: input, type: 'quads' }); - const defaultQueryInputFormat = 'sparql'; - actor = new ActorInitQuery({ - bus, - defaultQueryInputFormat, - mediatorHttpInvalidate, - mediatorQueryProcess, - mediatorQueryResultSerialize: mediatorSparqlSerialize, - mediatorQueryResultSerializeMediaTypeCombiner: mediatorSparqlSerialize, - mediatorQueryResultSerializeMediaTypeFormatCombiner: mediatorSparqlSerialize, - name: 'actor', - }); - queryEngine = new QueryEngineBase(actor); - }); - - it('should return a rejected promise on an invalid request', async() => { - // Make it reject instead of reading input - mediatorQueryProcess.mediate = (action: any) => Promise.reject(new Error('a')); - await expect(queryEngine.query('INVALID QUERY', { sources: [ 'abc' ]})).rejects.toBeTruthy(); - }); - }); - - describe('internalToFinalResult', () => { - it('converts bindings', async() => { - const final = QueryEngineBase.internalToFinalResult({ - type: 'bindings', - bindingsStream: new ArrayIterator([ - BF.bindings([ - [ DF.variable('a'), DF.namedNode('ex:a') ], - ]), - ]), - metadata: async() => ({ - state: new MetadataValidationState(), - cardinality: { type: 'estimate', value: 1 }, - canContainUndefs: false, - variables: [ DF.variable('a') ], - }), - context: new ActionContext({ c: 'd' }), - }); - - expect(final.resultType).toBe('bindings'); - await expect(final.execute()).resolves.toEqualBindingsStream([ - BF.bindings([ - [ DF.variable('a'), DF.namedNode('ex:a') ], - ]), - ]); - await expect(final.metadata()).resolves.toEqual({ - state: expect.any(MetadataValidationState), - cardinality: { type: 'estimate', value: 1 }, - canContainUndefs: false, - variables: [ DF.variable('a') ], - }); - expect(final.context).toEqual(new ActionContext({ c: 'd' })); - }); - - it('converts quads', async() => { - const final = QueryEngineBase.internalToFinalResult({ - type: 'quads', - quadStream: new ArrayIterator([ - DF.quad(DF.namedNode('ex:a'), DF.namedNode('ex:a'), DF.namedNode('ex:a')), - ]), - metadata: async() => ({ - state: new MetadataValidationState(), - cardinality: { type: 'estimate', value: 1 }, - canContainUndefs: false, - variables: [ DF.variable('a') ], - }), - context: new ActionContext({ c: 'd' }), - }); - - expect(final.resultType).toBe('quads'); - await expect(arrayifyStream(await final.execute())).resolves.toEqualRdfQuadArray([ - DF.quad(DF.namedNode('ex:a'), DF.namedNode('ex:a'), DF.namedNode('ex:a')), - ]); - await expect(final.metadata()).resolves.toEqual({ - state: expect.any(MetadataValidationState), - cardinality: { type: 'estimate', value: 1 }, - canContainUndefs: false, - variables: [ DF.variable('a') ], - }); - expect(final.context).toEqual(new ActionContext({ c: 'd' })); - }); - - it('converts booleans', async() => { - const final = QueryEngineBase.internalToFinalResult({ - type: 'boolean', - execute: () => Promise.resolve(true), - context: new ActionContext({ c: 'd' }), - }); - - expect(final.resultType).toBe('boolean'); - await expect(final.execute()).resolves.toBe(true); - expect(final.context).toEqual(new ActionContext({ c: 'd' })); - }); - - it('converts voids', async() => { - const final = QueryEngineBase.internalToFinalResult({ - type: 'void', - execute: () => Promise.resolve(), - context: new ActionContext({ c: 'd' }), - }); - - expect(final.resultType).toBe('void'); - await expect(final.execute()).resolves.toBeUndefined(); - expect(final.context).toEqual(new ActionContext({ c: 'd' })); - }); - }); - - describe('finalToInternalResult', () => { - it('converts bindings', async() => { - const internal = await QueryEngineBase.finalToInternalResult({ - resultType: 'bindings', - execute: async() => new ArrayIterator([ - BF.bindings([ - [ DF.variable('a'), DF.namedNode('ex:a') ], - ]), - ]), - metadata: async() => ({ - cardinality: { type: 'estimate', value: 1 }, - canContainUndefs: false, - variables: [ DF.variable('a') ], - }), - }); - - expect(internal.type).toBe('bindings'); - await expect(internal.bindingsStream).toEqualBindingsStream([ - BF.bindings([ - [ DF.variable('a'), DF.namedNode('ex:a') ], - ]), - ]); - await expect(internal.metadata()).resolves.toEqual({ - cardinality: { type: 'estimate', value: 1 }, - canContainUndefs: false, - variables: [ DF.variable('a') ], - }); - }); - - it('converts quads', async() => { - const internal = await QueryEngineBase.finalToInternalResult({ - resultType: 'quads', - execute: async() => new ArrayIterator([ - DF.quad(DF.namedNode('ex:a'), DF.namedNode('ex:a'), DF.namedNode('ex:a')), - ]), - metadata: async() => ({ cardinality: 1, canContainUndefs: false }), - }); - - expect(internal.type).toBe('quads'); - await expect(arrayifyStream(internal.quadStream)).resolves.toEqualRdfQuadArray([ - DF.quad(DF.namedNode('ex:a'), DF.namedNode('ex:a'), DF.namedNode('ex:a')), - ]); - await expect(internal.metadata()).resolves.toEqual({ - cardinality: 1, - canContainUndefs: false, - }); - }); - - it('converts booleans', async() => { - const final = await QueryEngineBase.finalToInternalResult({ - resultType: 'boolean', - execute: async() => true, - }); - - expect(final.type).toBe('boolean'); - await expect(final.execute()).resolves.toBe(true); - }); - - it('converts voids', async() => { - const final = await QueryEngineBase.finalToInternalResult({ - resultType: 'void', - - execute: async() => undefined, - }); - - expect(final.type).toBe('void'); - await expect(final.execute()).resolves.toBeUndefined(); - }); - }); -}); \ No newline at end of file diff --git a/packages/actor-init-query/test/QueryEngineFactoryBase-test.ts b/packages/actor-init-query/test/QueryEngineFactoryBase-test.ts deleted file mode 100644 index 3efd82a..0000000 --- a/packages/actor-init-query/test/QueryEngineFactoryBase-test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import * as Path from 'node:path'; -import { QueryEngineBase } from '../lib/QueryEngineBase'; -import { QueryEngineFactoryBase } from '../lib/QueryEngineFactoryBase'; - -jest.setTimeout(30_000); - -describe('QueryEngineFactoryBase', () => { - let factory: QueryEngineFactoryBase; - - beforeEach(() => { - factory = new QueryEngineFactoryBase( - Path.join(__dirname, '../../../engines/query-sparql/'), - Path.join(__dirname, '../../../engines/query-sparql/config/config-default.json'), - actor => new QueryEngineBase(actor), - ); - }); - - describe('create', () => { - it('should return a query engine', async() => { - await expect(factory.create({})).resolves - .toBeInstanceOf(QueryEngineBase); - }); - - it('should return a query engine without options', async() => { - await expect(factory.create()).resolves - .toBeInstanceOf(QueryEngineBase); - }); - - it('with mainModulePath option', async() => { - const opts = { - mainModulePath: Path.join(__dirname, '../'), - }; - await expect(factory.create(opts)).resolves - .toBeInstanceOf(QueryEngineBase); - }); - - it('with configPath option', async() => { - const opts = { - configPath: Path.join(__dirname, '../../../engines/query-sparql/config/config-default.json'), - }; - await expect(factory.create(opts)).resolves - .toBeInstanceOf(QueryEngineBase); - }); - - it('with instanceUri option', async() => { - const opts = { - instanceUri: 'urn:comunica:default:init/actors#query', - }; - await expect(factory.create(opts)).resolves - .toBeInstanceOf(QueryEngineBase); - }); - - it('with runnerInstanceUri option', async() => { - const opts = { - runnerInstanceUri: 'urn:comunica:default:Runner', - }; - await expect(factory.create(opts)).resolves - .toBeInstanceOf(QueryEngineBase); - }); - - it('with invalid instanceUri option', async() => { - const opts = { - instanceUri: 'urn:comunica:myUNKNOWN', - }; - await expect(factory.create(opts)) - .rejects.toThrow(new Error('No actor for key engine was found for IRI urn:comunica:myUNKNOWN.')); - }); - }); -}); diff --git a/packages/actor-init-query/test/assets/all-100.sparql b/packages/actor-init-query/test/assets/all-100.sparql deleted file mode 100644 index ec029aa..0000000 --- a/packages/actor-init-query/test/assets/all-100.sparql +++ /dev/null @@ -1,4 +0,0 @@ -SELECT * WHERE { - ?s ?p ?o -} -LIMIT 100 \ No newline at end of file diff --git a/packages/actor-init-query/test/assets/config.json b/packages/actor-init-query/test/assets/config.json deleted file mode 100644 index 35e6757..0000000 --- a/packages/actor-init-query/test/assets/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "entrypoint": "http://example.org/" -} diff --git a/packages/actor-init-query/test/cli/CliArgsHandlerBase-test.ts b/packages/actor-init-query/test/cli/CliArgsHandlerBase-test.ts deleted file mode 100644 index 0c4d339..0000000 --- a/packages/actor-init-query/test/cli/CliArgsHandlerBase-test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { KeysHttp } from '@comunica/context-entries'; -import { ActionContext } from '@comunica/core'; -import { CliArgsHandlerBase } from '../../lib/cli/CliArgsHandlerBase'; - -describe('CliArgsHandlerBase', () => { - describe('getScriptOutput', () => { - it('should return the fallback for a failing command', async() => { - await expect(CliArgsHandlerBase.getScriptOutput('acommandthatdefinitelydoesnotexist', 'fallback')) - .resolves.toBe('fallback'); - }); - }); - - describe('getSourceObjectFromString', () => { - it('should correctly parse normal URL', () => { - const hypermedia = 'http://example.org/'; - expect(CliArgsHandlerBase.getSourceObjectFromString(hypermedia)) - .toEqual({ value: 'http://example.org/' }); - }); - - it('should work with type annotation', () => { - const hypermedia = 'hypermedia@http://example.org/'; - expect(CliArgsHandlerBase.getSourceObjectFromString(hypermedia)) - .toEqual({ value: 'http://example.org/', type: 'hypermedia' }); - }); - - it('should work with authorization in url', () => { - const hypermedia = 'http://username:passwd@example.org/'; - expect(CliArgsHandlerBase.getSourceObjectFromString(hypermedia)) - .toEqual({ - value: 'http://example.org/', - context: new ActionContext({ [KeysHttp.auth.name]: 'username:passwd' }), - }); - }); - - it('should work with type annotation and authorization in url', () => { - const hypermedia = 'hypermedia@http://username:passwd@example.org/'; - expect(CliArgsHandlerBase.getSourceObjectFromString(hypermedia)) - .toEqual({ value: 'http://example.org/', type: 'hypermedia', context: new ActionContext({ [KeysHttp.auth.name]: 'username:passwd' }) }); - }); - - it('should work with empty username in authorization in url', () => { - const hypermedia = 'http://:passwd@example.org/'; - expect(CliArgsHandlerBase.getSourceObjectFromString(hypermedia)) - .toEqual({ value: 'http://example.org/', context: new ActionContext({ [KeysHttp.auth.name]: ':passwd' }) }); - }); - - it('should work with empty password in authorization in url', () => { - const hypermedia = 'http://username:@example.org/'; - expect(CliArgsHandlerBase.getSourceObjectFromString(hypermedia)) - .toEqual({ value: 'http://example.org/', context: new ActionContext({ [KeysHttp.auth.name]: 'username:' }) }); - }); - }); -}); diff --git a/yarn.lock b/yarn.lock index 4b0a0c8..88eff68 100644 --- a/yarn.lock +++ b/yarn.lock @@ -907,7 +907,7 @@ __metadata: languageName: node linkType: hard -"@comunica/actor-http-proxy@npm:^3.0.0, @comunica/actor-http-proxy@npm:^3.2.1": +"@comunica/actor-http-proxy@npm:^3.2.1": version: 3.2.1 resolution: "@comunica/actor-http-proxy@npm:3.2.1" dependencies: @@ -943,32 +943,7 @@ __metadata: languageName: node linkType: hard -"@comunica/actor-init-query@npm:^3.0.0, @comunica/actor-init-query@workspace:packages/actor-init-query": - version: 0.0.0-use.local - resolution: "@comunica/actor-init-query@workspace:packages/actor-init-query" - dependencies: - "@comunica/actor-http-proxy": "npm:^3.0.0" - "@comunica/bus-http-invalidate": "npm:^3.0.0" - "@comunica/bus-init": "npm:^3.0.0" - "@comunica/bus-query-process": "npm:^3.0.0" - "@comunica/bus-query-result-serialize": "npm:^3.0.0" - "@comunica/context-entries": "npm:^3.0.0" - "@comunica/core": "npm:^3.0.0" - "@comunica/logger-pretty": "npm:^3.0.0" - "@comunica/runner": "npm:^3.0.0" - "@comunica/types": "npm:^3.0.0" - "@rdfjs/types": "npm:*" - "@types/yargs": "npm:^17.0.0" - asynciterator: "npm:^3.0.0" - negotiate: "npm:^1.0.0" - process: "npm:^0.11.0" - rdf-data-factory: "npm:^1.0.0" - streamify-string: "npm:^1.0.0" - yargs: "npm:^17.0.0" - languageName: unknown - linkType: soft - -"@comunica/actor-init-query@npm:^3.2.1": +"@comunica/actor-init-query@npm:^3.0.0, @comunica/actor-init-query@npm:^3.2.1": version: 3.2.1 resolution: "@comunica/actor-init-query@npm:3.2.1" dependencies: @@ -2463,7 +2438,7 @@ __metadata: languageName: node linkType: hard -"@comunica/actor-rdf-metadata-extract-void@workspace:packages/actor-rdf-metadata-extract-void": +"@comunica/actor-rdf-metadata-extract-void@npm:^3.0.0, @comunica/actor-rdf-metadata-extract-void@workspace:packages/actor-rdf-metadata-extract-void": version: 0.0.0-use.local resolution: "@comunica/actor-rdf-metadata-extract-void@workspace:packages/actor-rdf-metadata-extract-void" dependencies: @@ -3032,7 +3007,7 @@ __metadata: languageName: node linkType: hard -"@comunica/bus-http-invalidate@npm:^3.0.0, @comunica/bus-http-invalidate@npm:^3.2.1": +"@comunica/bus-http-invalidate@npm:^3.2.1": version: 3.2.1 resolution: "@comunica/bus-http-invalidate@npm:3.2.1" dependencies: @@ -3078,7 +3053,7 @@ __metadata: languageName: node linkType: hard -"@comunica/bus-init@npm:^3.0.0, @comunica/bus-init@npm:^3.2.1": +"@comunica/bus-init@npm:^3.2.1": version: 3.2.1 resolution: "@comunica/bus-init@npm:3.2.1" dependencies: @@ -3140,7 +3115,7 @@ __metadata: languageName: node linkType: hard -"@comunica/bus-query-process@npm:^3.0.0, @comunica/bus-query-process@npm:^3.2.1": +"@comunica/bus-query-process@npm:^3.2.1": version: 3.2.1 resolution: "@comunica/bus-query-process@npm:3.2.1" dependencies: @@ -3151,7 +3126,7 @@ __metadata: languageName: node linkType: hard -"@comunica/bus-query-result-serialize@npm:^3.0.0, @comunica/bus-query-result-serialize@npm:^3.2.1": +"@comunica/bus-query-result-serialize@npm:^3.2.1": version: 3.2.1 resolution: "@comunica/bus-query-result-serialize@npm:3.2.1" dependencies: @@ -3497,7 +3472,7 @@ __metadata: languageName: node linkType: hard -"@comunica/logger-pretty@npm:^3.0.0, @comunica/logger-pretty@npm:^3.2.1": +"@comunica/logger-pretty@npm:^3.2.1": version: 3.2.1 resolution: "@comunica/logger-pretty@npm:3.2.1" dependencies: @@ -3678,11 +3653,13 @@ __metadata: "@comunica/actor-init-query": "npm:^3.0.0" "@comunica/actor-rdf-join-inner-multi-adaptive-heuristics": "npm:^3.0.0" "@comunica/actor-rdf-metadata-extract-link-filter": "npm:^3.0.0" + "@comunica/actor-rdf-metadata-extract-void": "npm:^3.0.0" "@comunica/actor-rdf-parse-link-filter-bloom": "npm:^3.0.0" "@comunica/actor-rdf-resolve-hypermedia-links-queue-wrapper-link-filter": "npm:^3.0.0" "@comunica/bus-rdf-parse-link-filter": "npm:^3.0.0" "@comunica/config-query-sparql-components": "npm:^3.0.0" "@comunica/query-sparql-link-traversal-solid": "npm:^0.5.0" + "@comunica/runner": "npm:^3.0.0" "@comunica/runner-cli": "npm:^3.0.0" bin: comunica-dynamic-sparql-components: bin/query-dynamic.js @@ -4131,7 +4108,7 @@ __metadata: languageName: node linkType: hard -"@comunica/types@npm:^3.0.0, @comunica/types@npm:^3.2.1": +"@comunica/types@npm:^3.2.1": version: 3.2.1 resolution: "@comunica/types@npm:3.2.1" dependencies: @@ -5326,7 +5303,7 @@ __metadata: languageName: node linkType: hard -"@types/yargs@npm:^17.0.0, @types/yargs@npm:^17.0.24, @types/yargs@npm:^17.0.8": +"@types/yargs@npm:^17.0.24, @types/yargs@npm:^17.0.8": version: 17.0.33 resolution: "@types/yargs@npm:17.0.33" dependencies: @@ -10041,7 +10018,7 @@ __metadata: languageName: node linkType: hard -"negotiate@npm:^1.0.0, negotiate@npm:^1.0.1": +"negotiate@npm:^1.0.1": version: 1.0.1 resolution: "negotiate@npm:1.0.1" checksum: 10/088f71bd733ee5ecbc0bd16519134eaebd7d22fe496fca18ab61deb3ad470b103755e63ed27b315759fbd64974c68897a41c678159d8e0bf39e8547609db9851 @@ -10601,7 +10578,7 @@ __metadata: languageName: node linkType: hard -"process@npm:^0.11.0, process@npm:^0.11.10": +"process@npm:^0.11.10": version: 0.11.10 resolution: "process@npm:0.11.10" checksum: 10/dbaa7e8d1d5cf375c36963ff43116772a989ef2bb47c9bdee20f38fd8fc061119cf38140631cf90c781aca4d3f0f0d2c834711952b728953f04fd7d238f59f5b @@ -10701,7 +10678,7 @@ __metadata: languageName: node linkType: hard -"rdf-data-factory@npm:^1.0.0, rdf-data-factory@npm:^1.0.1, rdf-data-factory@npm:^1.1.0, rdf-data-factory@npm:^1.1.1, rdf-data-factory@npm:^1.1.2": +"rdf-data-factory@npm:^1.0.1, rdf-data-factory@npm:^1.1.0, rdf-data-factory@npm:^1.1.1, rdf-data-factory@npm:^1.1.2": version: 1.1.2 resolution: "rdf-data-factory@npm:1.1.2" dependencies: @@ -11628,13 +11605,6 @@ __metadata: languageName: node linkType: hard -"streamify-string@npm:^1.0.0": - version: 1.0.1 - resolution: "streamify-string@npm:1.0.1" - checksum: 10/5637d10d30907875ce194e468f11a5252bd76f8c991f2bb46cddfe794cdeff82d6faad4afa5325b397ad78ca6222053a0235d7769b48a45e03de3c637bc5fd8f - languageName: node - linkType: hard - "string-argv@npm:^0.3.1": version: 0.3.2 resolution: "string-argv@npm:0.3.2" @@ -12593,7 +12563,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.0.0, yargs@npm:^17.3.1, yargs@npm:^17.7.2": +"yargs@npm:^17.3.1, yargs@npm:^17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" dependencies: