ValidationReportTest.java

/*******************************************************************************
 * Copyright (c) 2019 Eclipse RDF4J contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *******************************************************************************/
package org.eclipse.rdf4j.sail.shacl;

import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;

import org.apache.commons.io.IOUtils;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.model.util.Models;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.model.vocabulary.RDFS;
import org.eclipse.rdf4j.repository.RepositoryException;
import org.eclipse.rdf4j.repository.sail.SailRepository;
import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.Rio;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class ValidationReportTest {

	ValueFactory vf = SimpleValueFactory.getInstance();

	@Test
	public void simpleFirstTest() throws IOException {
		SailRepository shaclSail = Utils.getInitializedShaclRepository("shacl.trig");

		try (SailRepositoryConnection connection = shaclSail.getConnection()) {

			connection.begin();
			connection.add(RDFS.RESOURCE, RDF.TYPE, RDFS.RESOURCE);
			connection.add(RDFS.RESOURCE, RDFS.LABEL, connection.getValueFactory().createLiteral("a"));
			connection.add(RDFS.CLASS, RDF.TYPE, RDFS.RESOURCE);
			connection.add(RDF.SUBJECT, RDF.TYPE, RDFS.RESOURCE);

			connection.commit();
			Assertions.fail();

		} catch (RepositoryException e) {
			ShaclSailValidationException cause = (ShaclSailValidationException) e.getCause();
			Model actual = cause.validationReportAsModel();

			actual.setNamespace(RDF.PREFIX, RDF.NAMESPACE);
			actual.setNamespace(RDFS.PREFIX, RDFS.NAMESPACE);
			actual.setNamespace("ex", "http://example.com/ns#");

			ShaclSailValidationReportHelper.printValidationReport(e, System.out);

			Model expected = Rio.parse(new StringReader("" +
					"@prefix rsx: <http://rdf4j.org/shacl-extensions#> .\n" +
					"@prefix rdf4j: <http://rdf4j.org/schema/rdf4j#> .\n" +
					"@prefix sh: <http://www.w3.org/ns/shacl#> .\n" +
					"\n" +
					"[] a sh:ValidationReport;\n" +
					"  sh:conforms false;\n" +
					"  rdf4j:truncated false;\n" +
					"  sh:result [ a sh:ValidationResult;\n" +
					"      sh:focusNode <http://www.w3.org/2000/01/rdf-schema#Class>;\n" +
					"      rsx:shapesGraph rdf4j:SHACLShapeGraph;\n" +
					"      sh:resultPath <http://www.w3.org/2000/01/rdf-schema#label>;\n" +
					"      sh:sourceConstraintComponent sh:MinCountConstraintComponent;\n" +
					"      sh:resultSeverity sh:Violation;\n" +
					"      sh:sourceShape <http://example.com/ns#PersonShapeProperty>\n" +
					"    ], [ a sh:ValidationResult;\n" +
					"      sh:focusNode <http://www.w3.org/1999/02/22-rdf-syntax-ns#subject>;\n" +
					"      rsx:shapesGraph rdf4j:SHACLShapeGraph;\n" +
					"      sh:resultPath <http://www.w3.org/2000/01/rdf-schema#label>;\n" +
					"      sh:sourceConstraintComponent sh:MinCountConstraintComponent;\n" +
					"      sh:resultSeverity sh:Violation;\n" +
					"      sh:sourceShape <http://example.com/ns#PersonShapeProperty>\n" +
					"    ] .\n" +
					"\n" +
					"<http://example.com/ns#PersonShapeProperty> a sh:PropertyShape;\n" +
					"  sh:path <http://www.w3.org/2000/01/rdf-schema#label>;\n" +
					"  sh:minCount 1 ." +
					""), "", RDFFormat.TURTLE);

			Assertions.assertTrue(Models.isomorphic(expected, actual));

		} finally {
			shaclSail.shutDown();
		}
	}

	@Test
	public void withoutPathTest() throws IOException {
		SailRepository shaclSail = Utils.getInitializedShaclRepository("shaclValidateTarget.trig");

		try (SailRepositoryConnection connection = shaclSail.getConnection()) {

			connection.begin();
			connection.add(vf.createIRI("http://example.com/ns#", "node1"), RDF.TYPE,
					vf.createIRI("http://example.com/ns#", "SecondTarget"));
			connection.commit();

			Assertions.fail();

		} catch (RepositoryException e) {
			ShaclSailValidationException cause = (ShaclSailValidationException) e.getCause();
			Model actual = cause.validationReportAsModel();

			actual.setNamespace(RDF.PREFIX, RDF.NAMESPACE);
			actual.setNamespace(RDFS.PREFIX, RDFS.NAMESPACE);
			actual.setNamespace("ex", "http://example.com/ns#");

			ShaclSailValidationReportHelper.printValidationReport(e, System.out);

			Model expected = Rio.parse(new StringReader("" +
					"@prefix rsx: <http://rdf4j.org/shacl-extensions#> .\n" +
					"@prefix rdf4j: <http://rdf4j.org/schema/rdf4j#> .\n" +
					"@prefix sh: <http://www.w3.org/ns/shacl#> .\n" +
					"\n" +
					"[] a sh:ValidationReport;\n" +
					"  sh:conforms false;\n" +
					"  rdf4j:truncated false;\n" +
					"  sh:result [ a sh:ValidationResult;\n" +
					"      sh:focusNode <http://example.com/ns#node1>;\n" +
					"      rsx:shapesGraph rdf4j:SHACLShapeGraph;\n" +
					"      sh:value <http://example.com/ns#node1>;\n" +
					"      sh:sourceConstraintComponent sh:ClassConstraintComponent;\n" +
					"      sh:resultSeverity sh:Violation;\n" +
					"      sh:sourceShape <http://example.com/ns#PersonShape>\n" +
					"    ] .\n" +
					"\n" +
					"<http://example.com/ns#PersonShape> a sh:NodeShape;\n" +
					"  sh:targetClass <http://example.com/ns#Person>, <http://example.com/ns#SecondTarget>;\n" +
					"  sh:class <http://example.com/ns#Person> .\n" +
					""), "", RDFFormat.TRIG);

			Assertions.assertTrue(Models.isomorphic(expected, actual));

		} finally {
			shaclSail.shutDown();
		}
	}

	@Test
	public void nestedLogicalOrSupport() throws IOException {

		SailRepository shaclSail = Utils.getInitializedShaclRepository("test-cases/or/datatype/shacl.trig");

		try (SailRepositoryConnection connection = shaclSail.getConnection()) {

			connection.begin();
			connection.prepareUpdate(IOUtils.toString(ValidationReportTest.class.getClassLoader()
					.getResourceAsStream("test-cases/or/datatype/invalid/case1/query1.rq"), StandardCharsets.UTF_8))
					.execute();
			connection.commit();
			Assertions.fail();

		} catch (RepositoryException e) {
			ShaclSailValidationException cause = (ShaclSailValidationException) e.getCause();
			Model actual = cause.validationReportAsModel();

			actual.setNamespace(RDF.PREFIX, RDF.NAMESPACE);
			actual.setNamespace(RDFS.PREFIX, RDFS.NAMESPACE);
			actual.setNamespace("ex", "http://example.com/ns#");

			ShaclSailValidationReportHelper.printValidationReport(e, System.out);

			Model expected = Rio.parse(new StringReader("" +
					"@prefix rsx: <http://rdf4j.org/shacl-extensions#> .\n" +
					"@prefix rdf4j: <http://rdf4j.org/schema/rdf4j#> .\n" +
					"@prefix sh: <http://www.w3.org/ns/shacl#> .\n" +
					"\n" +
					"[] a sh:ValidationReport;\n" +
					"  sh:conforms false;\n" +
					"  rdf4j:truncated false;\n" +
					"  sh:result [ a sh:ValidationResult;\n" +
					"      sh:focusNode <http://example.com/ns#validPerson1>;\n" +
					"      rsx:shapesGraph rdf4j:SHACLShapeGraph;\n" +
					"      sh:value \"abc\";\n" +
					"      sh:resultPath <http://example.com/ns#age>;\n" +
					"      sh:sourceConstraintComponent sh:OrConstraintComponent;\n" +
					"      sh:resultSeverity sh:Violation;\n" +
					"      sh:sourceShape <http://example.com/ns#personShapeOr>\n" +
					"    ] .\n" +
					"\n" +
					"<http://example.com/ns#personShapeOr> a sh:PropertyShape;\n" +
					"  sh:path <http://example.com/ns#age>;\n" +
					"  sh:or (<http://example.com/ns#personShapeAgeInteger> <http://example.com/ns#personShapeAgeLong>) .\n"
					+
					"\n" +
					"<http://example.com/ns#personShapeAgeInteger> a sh:NodeShape;\n" +
					"  sh:datatype <http://www.w3.org/2001/XMLSchema#integer> .\n" +
					"\n" +
					"<http://example.com/ns#personShapeAgeLong> a sh:NodeShape;\n" +
					"  sh:datatype <http://www.w3.org/2001/XMLSchema#long> .\n" +
					""), "", RDFFormat.TURTLE);

			Assertions.assertTrue(Models.isomorphic(expected, actual));

		} finally {
			shaclSail.shutDown();
		}
	}

	@Test
	public void testHasValueIn() throws IOException {

		SailRepository shaclSail = Utils.getInitializedShaclRepository("test-cases/hasValueIn/simple/shacl.trig");

		ShaclSail sail = (ShaclSail) shaclSail.getSail();
		sail.setDashDataShapes(true);

		try (SailRepositoryConnection connection = shaclSail.getConnection()) {

			connection.begin();
			connection.prepareUpdate(IOUtils.toString(ValidationReportTest.class.getClassLoader()
					.getResourceAsStream("test-cases/hasValueIn/simple/invalid/case1/query1.rq"),
					StandardCharsets.UTF_8))
					.execute();
			connection.commit();
			Assertions.fail();

		} catch (RepositoryException e) {
			ShaclSailValidationException cause = (ShaclSailValidationException) e.getCause();
			Model actual = cause.validationReportAsModel();

			actual.setNamespace(RDF.PREFIX, RDF.NAMESPACE);
			actual.setNamespace(RDFS.PREFIX, RDFS.NAMESPACE);
			actual.setNamespace("ex", "http://example.com/ns#");

			ShaclSailValidationReportHelper.printValidationReport(e, System.out);

			Model expected = Rio.parse(new StringReader("" +
					"@prefix rsx: <http://rdf4j.org/shacl-extensions#> .\n" +
					"@prefix rdf4j: <http://rdf4j.org/schema/rdf4j#> .\n" +
					"@prefix sh: <http://www.w3.org/ns/shacl#> .\n" +
					"\n" +
					"[] a sh:ValidationReport;\n" +
					"  sh:conforms false;\n" +
					"  rdf4j:truncated false;\n" +
					"  sh:result [ a sh:ValidationResult;\n" +
					"      sh:focusNode <http://example.com/ns#validPerson1>;\n" +
					"      rsx:shapesGraph rdf4j:SHACLShapeGraph;\n" +
					"      sh:resultPath <http://example.com/ns#knows>;\n" +
					"      sh:sourceConstraintComponent <http://datashapes.org/dash#HasValueInConstraintComponent>;\n" +
					"      sh:resultSeverity sh:Violation;\n" +
					"      sh:sourceShape [ a sh:PropertyShape;\n" +
					"          sh:path <http://example.com/ns#knows>;\n" +
					"          <http://datashapes.org/dash#hasValueIn> (<http://example.com/ns#peter> <http://example.com/ns#mary>\n"
					+
					"              <http://example.com/ns#kate>)\n" +
					"        ]\n" +
					"    ] ." +
					""), "", RDFFormat.TRIG);

			Assertions.assertTrue(Models.isomorphic(expected, actual));

		} finally {
			shaclSail.shutDown();
		}
	}

	@Test
	public void testHasValue() throws IOException {

		SailRepository shaclSail = Utils.getInitializedShaclRepository("test-cases/hasValue/simple/shacl.trig");

		ShaclSail sail = (ShaclSail) shaclSail.getSail();
		sail.setDashDataShapes(true);

		try (SailRepositoryConnection connection = shaclSail.getConnection()) {

			connection.begin();
			connection.prepareUpdate(IOUtils.toString(ValidationReportTest.class.getClassLoader()
					.getResourceAsStream("test-cases/hasValue/simple/invalid/case1/query1.rq"),
					StandardCharsets.UTF_8))
					.execute();
			connection.commit();
			Assertions.fail();

		} catch (RepositoryException e) {
			ShaclSailValidationException cause = (ShaclSailValidationException) e.getCause();
			Model actual = cause.validationReportAsModel();

			actual.setNamespace(RDF.PREFIX, RDF.NAMESPACE);
			actual.setNamespace(RDFS.PREFIX, RDFS.NAMESPACE);
			actual.setNamespace("ex", "http://example.com/ns#");

			ShaclSailValidationReportHelper.printValidationReport(e, System.out);

			Model expected = Rio.parse(new StringReader(""
					+ "@prefix rsx: <http://rdf4j.org/shacl-extensions#> .\n" +
					"@prefix rdf4j: <http://rdf4j.org/schema/rdf4j#> .\n" +
					"@prefix sh: <http://www.w3.org/ns/shacl#> .\n" +
					"\n" +
					"[] a sh:ValidationReport;\n" +
					"  sh:conforms false;\n" +
					"  rdf4j:truncated false;\n" +
					"  sh:result [ a sh:ValidationResult;\n" +
					"      sh:focusNode <http://example.com/ns#validPerson1>;\n" +
					"      rsx:shapesGraph rdf4j:SHACLShapeGraph;\n" +
					"      sh:resultPath <http://example.com/ns#knows>;\n" +
					"      sh:sourceConstraintComponent sh:HasValueConstraintComponent;\n" +
					"      sh:resultSeverity sh:Violation;\n" +
					"      sh:sourceShape [ a sh:PropertyShape;\n" +
					"          sh:path <http://example.com/ns#knows>;\n" +
					"          sh:hasValue <http://example.com/ns#peter>\n" +
					"        ]\n" +
					"    ] ."), "", RDFFormat.TRIG);

			Assertions.assertTrue(Models.isomorphic(expected, actual));

		} finally {
			shaclSail.shutDown();
		}
	}

}