rdf-test-suite
Version:
Executes the RDF and SPARQL test suites.
302 lines (297 loc) • 14.6 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TestCaseQueryEvaluation = exports.TestCaseQueryEvaluationHandler = void 0;
const rdf_data_factory_1 = require("rdf-data-factory");
const rdf_object_1 = require("rdf-object");
const rdf_string_1 = require("rdf-string");
const rdf_terms_1 = require("rdf-terms");
const sparqljson_parse_1 = require("sparqljson-parse");
const sparqlxml_parse_1 = require("sparqlxml-parse");
const ErrorTest_1 = require("../../ErrorTest");
const Util_1 = require("../../Util");
const QueryResultBindings_1 = require("./QueryResultBindings");
const QueryResultBoolean_1 = require("./QueryResultBoolean");
const QueryResultQuads_1 = require("./QueryResultQuads");
const arrayify_stream_1 = require("arrayify-stream");
// tslint:disable:no-var-requires
const stringifyStream = require('stream-to-string');
const DF = new rdf_data_factory_1.DataFactory();
/**
* Test case handler for http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#QueryEvaluationTest.
*/
class TestCaseQueryEvaluationHandler {
/**
* Parse SPARQL query results in any of the following content types:
* * application/sparql-results+xml (bindings/ask)
* * application/sparql-results+json (bindings/ask)
* * Any RDF serialization (quads)
*
* If the results has as first triple '[] a <http://www.w3.org/2001/sw/DataAccess/tests/result-set#ResultSet>',
* the it will be considered as a DAWG result set, and parsed as such. (bindings)
*
* @param {string} contentType The content type.
* @param {string} url The base IRI.
* @param {NodeJS.ReadableStream} data The data stream to parse.
* @return {Promise<IQueryResult>} A promise resolving to a SPARQL query result.
*/
static parseQueryResult(contentType, url, data) {
return __awaiter(this, void 0, void 0, function* () {
let queryResult;
try {
const rdfStream = Util_1.Util.parseRdfRaw(contentType, url, data);
queryResult = new QueryResultQuads_1.QueryResultQuads(yield (0, arrayify_stream_1.default)(rdfStream));
}
catch (e) {
// Fallthrough to the next cases
}
if (contentType.indexOf('application/sparql-results+xml') >= 0 || url.endsWith('.srx')) {
contentType = 'application/sparql-results+xml';
queryResult = yield TestCaseQueryEvaluationHandler.parseSparqlResults('xml', data);
}
if (contentType.indexOf('application/sparql-results+json') >= 0) {
queryResult = yield TestCaseQueryEvaluationHandler.parseSparqlResults('json', data);
}
if (!queryResult) {
throw new Error(`Could not parse the query result with content type ${contentType} at ${url}`);
}
// Discover DAWG result sets
if (queryResult.type === 'quads' && queryResult.value.length > 0
&& queryResult.value[0].predicate.value === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'
&& queryResult.value[0].object.value === 'http://www.w3.org/2001/sw/DataAccess/tests/result-set#ResultSet') {
return yield TestCaseQueryEvaluationHandler.parseDawgResultSet(queryResult.value);
}
return queryResult;
});
}
/**
* Parses query results in either SPARQL/JSON or SPARQL/XML query results syntax.
* @param {string} type json or xml
* @param {NodeJS.ReadableStream} data The data stream to parse.
* @return {Promise<IQueryResult>} A promise resolving to a SPARQL query result.
*/
static parseSparqlResults(type, data) {
return __awaiter(this, void 0, void 0, function* () {
let booleanPromise;
let bindingsStream;
if (type === 'json') {
const parser = new sparqljson_parse_1.SparqlJsonParser({ prefixVariableQuestionMark: true });
booleanPromise = parser.parseJsonBooleanStream(data);
bindingsStream = parser.parseJsonResultsStream(data);
}
else {
const parser = new sparqlxml_parse_1.SparqlXmlParser({ prefixVariableQuestionMark: true });
booleanPromise = parser.parseXmlBooleanStream(data);
bindingsStream = parser.parseXmlResultsStream(data);
}
const bindingsPromise = Promise.all([
new Promise((resolve) => {
bindingsStream
.on('variables', resolve)
.on('end', _ => resolve([]));
}),
(0, arrayify_stream_1.default)(bindingsStream),
]);
// Parse both in parallel, and silence the errors
let bindingsError = null;
let booleanError = null;
const parsingResults = yield Promise.all([
booleanPromise.catch((e) => {
booleanError = e;
return null;
}),
bindingsPromise.catch((e) => {
bindingsError = e;
return null;
}),
]);
if (!booleanError) {
return new QueryResultBoolean_1.QueryResultBoolean(parsingResults[0]);
}
else if (!bindingsError) {
return new QueryResultBindings_1.QueryResultBindings(parsingResults[1][0].map((variable) => '?' + variable.value), parsingResults[1][1], false);
}
else {
throw new Error('Found no valid ASK or SELECT query.\n'
+ bindingsError.message + '\n' + booleanError.message);
}
});
}
/**
* Parses query results in the DAWG vocabulary.
* https://www.w3.org/2001/sw/DataAccess/tests/test-dawg.n3
* @param {Quad[]} quads An array of quads.
* @return {Promise<IQueryResultBindings>} A promise resolving to a bindings results object.
*/
static parseDawgResultSet(quads) {
return __awaiter(this, void 0, void 0, function* () {
// Construct resources for easier interpretation of the bindings
// tslint:disable:object-literal-sort-keys
const objectLoader = new rdf_object_1.RdfObjectLoader({
context: {
result: 'http://www.w3.org/2001/sw/DataAccess/tests/result-set#',
bindings: 'result:binding',
index: 'result:index',
resultVariables: 'result:resultVariable',
solutions: 'result:solution',
value: 'result:value',
variable: 'result:variable',
},
});
// tslint:enable:object-literal-sort-keys
yield objectLoader.importArray(quads);
let resultSet = null;
for (const resourceName in objectLoader.resources) {
if (objectLoader.resources[resourceName]
.isA((0, rdf_string_1.stringToTerm)('http://www.w3.org/2001/sw/DataAccess/tests/result-set#ResultSet'))) {
resultSet = objectLoader.resources[resourceName];
break;
}
}
if (!resultSet) {
throw new Error('No valid DAWG result set was found in ' + quads);
}
// Get the variable names
const variables = resultSet.properties.resultVariables
.map((resource) => '?' + resource.value);
// check if any binding has .index
let checkOrder = false;
const s = resultSet.properties.solutions;
for (const solution of s) {
if (solution.property.index) {
checkOrder = true;
break;
}
}
// Ensure that the solutions are sorted by index
const solutions = resultSet.properties.solutions;
if (checkOrder) {
solutions.sort((solution1, solution2) => {
return parseInt(solution1.property.index.value, 10) - parseInt(solution2.property.index.value, 10);
});
}
// Collect the bindings as object
const value = solutions.map((solution) => {
return solution.properties.bindings.reduce((bindings, binding) => {
bindings['?' + binding.property.variable.value] = binding.property.value.term;
return bindings;
}, {});
});
return new QueryResultBindings_1.QueryResultBindings(variables, value, checkOrder);
});
}
/**
* Obtain all data links for the given query test action.
* @param action A query test action.
*/
static getQueryDataLinks(action) {
const queryDataLinks = [];
if (action.property.data) {
queryDataLinks.push({
dataUri: action.property.data.value,
});
}
for (const graphData of action.properties.graphData) {
if (graphData.property.graph) {
queryDataLinks.push({
dataUri: graphData.property.graph.value,
dataGraph: DF.namedNode(Util_1.Util.normalizeBaseUrl(graphData.property.label.value)),
});
}
else {
queryDataLinks.push({
dataUri: graphData.value,
dataGraph: DF.namedNode(Util_1.Util.normalizeBaseUrl(graphData.value)),
});
}
}
return queryDataLinks;
}
/**
* Determine the quads for the given query data links.
* @param queryDataLinks Links to query data.
* @param options Fetch options.
*/
static resolveQueryDataLinks(queryDataLinks, options) {
return __awaiter(this, void 0, void 0, function* () {
let queryData = [];
for (const queryDataLink of queryDataLinks) {
let queryDataThis = yield (0, arrayify_stream_1.default)((yield Util_1.Util.fetchRdf(queryDataLink.dataUri, Object.assign(Object.assign({}, options), { normalizeUrl: true })))[1]);
if (queryDataLink.dataGraph) {
queryDataThis = queryDataThis.map((quad) => (0, rdf_terms_1.mapTerms)(quad, (value, key) => key === 'graph' ? queryDataLink.dataGraph : value));
}
queryData = [...queryData, ...queryDataThis];
}
return queryData;
});
}
resourceToTestCase(resource, testCaseData, options) {
return __awaiter(this, void 0, void 0, function* () {
if (!resource.property.action) {
throw new Error(`Missing mf:action in ${resource}`);
}
if (!resource.property.result) {
throw new Error(`Missing mf:result in ${resource}`);
}
const action = resource.property.action;
if (!action.property.query) {
throw new Error(`Missing qt:query in mf:action of ${resource}`);
}
// Determine links to data
const queryDataLinks = TestCaseQueryEvaluationHandler.getQueryDataLinks(action);
// Check for lax cardinality property
let laxCardinality = false;
if (resource.property.resultCardinality && resource.property.resultCardinality.value
=== 'http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#LaxCardinality') {
laxCardinality = true;
}
// Collect all query data
const queryData = yield TestCaseQueryEvaluationHandler.resolveQueryDataLinks(queryDataLinks, options);
const queryResponse = yield Util_1.Util.fetchCached(resource.property.result.value, options);
return new TestCaseQueryEvaluation(testCaseData, {
baseIRI: Util_1.Util.normalizeBaseUrl(action.property.query.value),
queryDataLinks,
laxCardinality,
queryData,
queryResult: yield TestCaseQueryEvaluationHandler.parseQueryResult(Util_1.Util.identifyContentType(queryResponse.url, queryResponse.headers), queryResponse.url, queryResponse.body),
queryString: yield stringifyStream((yield Util_1.Util.fetchCached(action.property.query.value, options)).body),
resultSource: queryResponse,
});
});
}
}
exports.TestCaseQueryEvaluationHandler = TestCaseQueryEvaluationHandler;
class TestCaseQueryEvaluation {
constructor(testCaseData, props) {
this.type = "sparql";
Object.assign(this, testCaseData);
Object.assign(this, props);
}
static queryDataLinksToString(queryDataLinks) {
return queryDataLinks.map((queryDataLink) => queryDataLink.dataUri + (queryDataLink.dataGraph ? ` (named graph: ${queryDataLink.dataGraph.value})` : '')).join(',\n ');
}
test(engine, injectArguments) {
return __awaiter(this, void 0, void 0, function* () {
const result = yield engine.query(this.queryData, this.queryString, Object.assign({ baseIRI: this.baseIRI }, injectArguments));
if (!(yield this.queryResult.equals(result, this.laxCardinality))) {
throw new ErrorTest_1.ErrorTest(`Invalid query evaluation
Query:\n\n${this.queryString}
Data links: ${TestCaseQueryEvaluation.queryDataLinksToString(this.queryDataLinks)}
Result Source: ${this.resultSource.url}
Expected: ${this.queryResult.toString()}
Got: \n ${result.toString()}
`);
}
});
}
}
exports.TestCaseQueryEvaluation = TestCaseQueryEvaluation;
//# sourceMappingURL=TestCaseQueryEvaluation.js.map