From 72f3505a25546987bcbaf1e90cedb1361c37e09f Mon Sep 17 00:00:00 2001 From: Ashley Caselli Date: Mon, 31 Aug 2026 10:16:32 +0200 Subject: [PATCH 1/2] test: read the test nanopublications from the nanopub test suite Nanopublications used as fixtures belong in the nanopub test suite, where every implementation reads them from, rather than being copied into each consumer. #619 did that for the grlc query template it needed; this is the rest of them (#620), and they were not all the same kind of thing: np-grlc-query.trig Pure duplication: byte-identical to valid/signed/RA6T-....trig in the suite already. Deleted, and GrlcQueryTest and NanopubElementTest read it from there. np-statusline-example.trig A published, signed, trusty retraction, so it belongs in the suite's valid/signed alongside the others: Nanopublication/nanopub-testsuite#6, since merged. Deleted here, and StatusLineTest reads it from the suite. np-governed-definition.trig, np-nongoverned-definition.trig Kept here. They are hand-built, unsigned and carry placeholder artifact codes, because they exist to exercise Nanodash's own governance logic rather than to test whether an implementation reads nanopublications correctly; a suite for validating implementations is not their home, and an unsigned nanopub with a made-up code has no business in its valid/ folder. Said so where they are loaded, so the question isn't reopened. templates/new-style-assertion-template.trig Deleted: no test referenced it. If it was meant for a test that never landed, it should come back with that test, from the suite. Each test resolves its fixture through the connector by artifact code, the way SparqlPlaceholderValidationTest already did, rather than through a wrapper of our own. A fixture that isn't there fails rather than skips: it would mean the entry has been renamed or removed, and the test is no longer testing what it says it is. The connector downloads the suite once per JVM, so several fixtures cost one download. Full test run: 1232 tests, 0 failures, no new skips. Closes #620 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014dByNErNfuj4au4r5vMsbf --- .../nanodash/GovernedVersionsTest.java | 8 ++ .../nanodash/GrlcQueryTest.java | 21 +++++- .../nanodash/NanopubElementTest.java | 14 +++- .../nanodash/component/StatusLineTest.java | 17 ++++- src/test/resources/np-grlc-query.trig | 73 ------------------- src/test/resources/np-statusline-example.trig | 39 ---------- .../new-style-assertion-template.trig | 47 ------------ 7 files changed, 55 insertions(+), 164 deletions(-) delete mode 100644 src/test/resources/np-grlc-query.trig delete mode 100644 src/test/resources/np-statusline-example.trig delete mode 100644 src/test/resources/templates/new-style-assertion-template.trig diff --git a/src/test/java/com/knowledgepixels/nanodash/GovernedVersionsTest.java b/src/test/java/com/knowledgepixels/nanodash/GovernedVersionsTest.java index bfc8c9a89..44cc636d8 100644 --- a/src/test/java/com/knowledgepixels/nanodash/GovernedVersionsTest.java +++ b/src/test/java/com/knowledgepixels/nanodash/GovernedVersionsTest.java @@ -15,6 +15,14 @@ class GovernedVersionsTest { + /** + * Fixtures normally belong in the nanopub test suite rather than in this repository (#620), + * but these two stay here: they are hand-built, unsigned, and carry placeholder artifact + * codes, because they exist to exercise Nanodash's own governance logic rather than to test + * whether an implementation reads nanopublications correctly. A suite for validating + * nanopublication implementations is not their home, and an unsigned nanopub with a made-up + * code has no business in its {@code valid/} folder. + */ private static Nanopub load(String fileName) throws MalformedNanopubException, IOException { return new NanopubImpl(new File("src/test/resources/" + fileName), RDFFormat.TRIG); } diff --git a/src/test/java/com/knowledgepixels/nanodash/GrlcQueryTest.java b/src/test/java/com/knowledgepixels/nanodash/GrlcQueryTest.java index 10fc9d763..e79985a25 100644 --- a/src/test/java/com/knowledgepixels/nanodash/GrlcQueryTest.java +++ b/src/test/java/com/knowledgepixels/nanodash/GrlcQueryTest.java @@ -11,9 +11,11 @@ import org.mockito.MockedStatic; import org.nanopub.*; import org.nanopub.extra.services.QueryRef; +import org.nanopub.testsuite.NanopubTestSuite; +import org.nanopub.testsuite.TestSuiteCategory; +import org.nanopub.testsuite.TestSuiteEntry; import org.nanopub.vocabulary.KPXL_GRLC; -import java.io.File; import java.io.IOException; import java.util.List; import java.util.concurrent.atomic.AtomicReference; @@ -42,6 +44,19 @@ class GrlcQueryTest { */ private static final String SPARQL_WITH_NON_BREAKING_SPACE = "select ?thing where { ?thing ?p\u00A0?o }"; + /** + * The published "Get participation links" query. It lives in the nanopub test suite rather + * than in this repository, so that the fixture is shared with the other implementations + * instead of being copied into each of them (#620). Its absence is a failure rather than a + * reason to skip: it would mean the entry has been renamed or removed. + */ + private static Nanopub queryNanopub() throws MalformedNanopubException, IOException { + TestSuiteEntry entry = NanopubTestSuite.getLatest() + .getByArtifactCode(NANOPUB_ID, TestSuiteCategory.VALID) + .orElseThrow(() -> new IllegalStateException("Not in the nanopub test suite: " + NANOPUB_ID)); + return new NanopubImpl(entry.toFile(), RDFFormat.TRIG); + } + @AfterEach void tearDown() throws NoSuchFieldException, IllegalAccessException { // Using reflection to clear the instance map to ensure a fresh start for each test @@ -198,7 +213,7 @@ void getQuerySuffix() { @Test void getNanopub() throws MalformedNanopubException, IOException { GrlcQuery query = GrlcQuery.get(NANOPUB_URI); - Nanopub nanopub = new NanopubImpl(new File("src/test/resources/np-grlc-query.trig"), RDFFormat.TRIG); + Nanopub nanopub = queryNanopub(); assertEquals(query.getNanopub(), nanopub); } @@ -247,7 +262,7 @@ void createParamFields() { void getSparql() throws MalformedNanopubException, IOException { GrlcQuery query = GrlcQuery.get(NANOPUB_URI); String sparql = query.getSparql(); - Nanopub nanopub = new NanopubImpl(new File("src/test/resources/np-grlc-query.trig"), RDFFormat.TRIG); + Nanopub nanopub = queryNanopub(); AtomicReference sparqlFromNanopub = new AtomicReference<>(); nanopub.getAssertion().forEach(st -> { if (st.getPredicate().equals(KPXL_GRLC.SPARQL)) { diff --git a/src/test/java/com/knowledgepixels/nanodash/NanopubElementTest.java b/src/test/java/com/knowledgepixels/nanodash/NanopubElementTest.java index 56898788c..29810fd2d 100644 --- a/src/test/java/com/knowledgepixels/nanodash/NanopubElementTest.java +++ b/src/test/java/com/knowledgepixels/nanodash/NanopubElementTest.java @@ -10,6 +10,8 @@ import org.nanopub.NanopubImpl; import org.nanopub.extra.security.MalformedCryptoElementException; import org.nanopub.extra.security.SignatureUtils; +import org.nanopub.testsuite.NanopubTestSuite; +import org.nanopub.testsuite.TestSuiteCategory; import org.nanopub.vocabulary.NPX; import java.io.File; @@ -23,7 +25,17 @@ class NanopubElementTest { - private final File np = new File("src/test/resources/np-grlc-query.trig"); + /** + * The published "Get participation links" query. It lives in the nanopub test suite rather + * than in this repository, so that the fixture is shared with the other implementations + * instead of being copied into each of them (#620). + */ + private static final String QUERY_CODE = "RA6T-YLqLnYd5XfnqR9PaGUjCzudvHdYjcG4GvOc7fdpA"; + + private final File np = NanopubTestSuite.getLatest() + .getByArtifactCode(QUERY_CODE, TestSuiteCategory.VALID) + .orElseThrow(() -> new IllegalStateException("Not in the nanopub test suite: " + QUERY_CODE)) + .toFile(); @Test void getWithURI() throws MalformedNanopubException, IOException { diff --git a/src/test/java/com/knowledgepixels/nanodash/component/StatusLineTest.java b/src/test/java/com/knowledgepixels/nanodash/component/StatusLineTest.java index a4e752332..37d135832 100644 --- a/src/test/java/com/knowledgepixels/nanodash/component/StatusLineTest.java +++ b/src/test/java/com/knowledgepixels/nanodash/component/StatusLineTest.java @@ -10,6 +10,9 @@ import org.nanopub.NanopubImpl; import org.nanopub.extra.services.ApiResponse; import org.nanopub.extra.services.ApiResponseEntry; +import org.nanopub.testsuite.NanopubTestSuite; +import org.nanopub.testsuite.TestSuiteCategory; +import org.nanopub.testsuite.TestSuiteEntry; import java.io.File; @@ -20,6 +23,15 @@ class StatusLineTest { + /** + * A published retraction, which is one of the shapes a status line is drawn from. It lives + * in the nanopub test suite rather than in this repository, so that the fixture is shared + * with the other implementations instead of being copied into each of them (#620). Its + * absence is a failure rather than a reason to skip: it would mean the entry has been + * renamed or removed. + */ + private static final String RETRACTION_CODE = "RA58YcJyv1h-UmS8jI6UfFP6_LTAh59GTgpU_4lvBv7a4"; + private WicketTester wicketTester; @BeforeEach @@ -37,7 +49,10 @@ private static ApiResponseEntry entry(String newerVersion, String retractedBy, S @Test void createComponentReturnsNonNullComponent() throws Exception { - Nanopub np = new NanopubImpl(new File("src/test/resources/np-statusline-example.trig"), RDFFormat.TRIG); + TestSuiteEntry entry = NanopubTestSuite.getLatest() + .getByArtifactCode(RETRACTION_CODE, TestSuiteCategory.VALID) + .orElseThrow(() -> new IllegalStateException("Not in the nanopub test suite: " + RETRACTION_CODE)); + Nanopub np = new NanopubImpl(entry.toFile(), RDFFormat.TRIG); Component component = StatusLine.createComponent("statusLine", np); assertNotNull(component); diff --git a/src/test/resources/np-grlc-query.trig b/src/test/resources/np-grlc-query.trig deleted file mode 100644 index 7c97dfdc5..000000000 --- a/src/test/resources/np-grlc-query.trig +++ /dev/null @@ -1,73 +0,0 @@ -@prefix this: . -@prefix sub: . -@prefix np: . -@prefix dct: . -@prefix nt: . -@prefix npx: . -@prefix xsd: . -@prefix rdfs: . -@prefix orcid: . -@prefix ns1: . -@prefix prov: . -@prefix foaf: . - -sub:Head { - this: np:hasAssertion sub:assertion; - np:hasProvenance sub:provenance; - np:hasPublicationInfo sub:pubinfo; - a np:Nanopublication . -} - -sub:assertion { - sub:get-participation dct:description "This query returns all participation links."; - dct:license ; - a ; - rdfs:label "Get participation links"; - ; - """prefix rdfs: -prefix dct: -prefix np: -prefix npa: -prefix npx: -prefix wd: - -select ?person ?event ?np ?date where { - graph npa:graph { - ?np npa:hasValidSignatureForPublicKey ?pubkey . - filter not exists { ?npx npx:invalidates ?np ; npa:hasValidSignatureForPublicKey ?pubkey . } - ?np dct:created ?date . - ?np np:hasAssertion ?a . - optional { ?np rdfs:label ?label } - } - graph ?a { - ?person wd:P1344 ?event . - } -} order by desc(?date)""" . -} - -sub:provenance { - sub:assertion prov:wasAttributedTo orcid:0000-0002-1267-0234 . -} - -sub:pubinfo { - orcid:0000-0002-1267-0234 foaf:name "Tobias Kuhn" . - - this: dct:created "2025-06-03T10:39:46.322Z"^^xsd:dateTime; - dct:creator orcid:0000-0002-1267-0234; - dct:license ; - npx:embeds sub:get-participation; - npx:supersedes ; - npx:wasCreatedAt ; - a npx:ExampleNanopub; - nt:wasCreatedFromProvenanceTemplate ; - nt:wasCreatedFromPubinfoTemplate ns1:RAXflINqt3smqxV5Aq7E9lzje4uLdkKIOefa6Bp8oJ8CY, - , , - ; - nt:wasCreatedFromTemplate . - - sub:sig npx:hasAlgorithm "RSA"; - npx:hasPublicKey "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD4Wj537OijfOWVtsHMznuXKISqBhtGDQZfdO6pbb4hg9EHMcUFGTLbWaPrP783PHv8HMAAPjvEkHLaOHMIknqhaIa5236lfBO3r+ljVdYBElBcLvROmwG+ZGtmPNZf7lMhI15xf5TfoaSa84AFRd5J2EXekK6PhaFQhRm1IpSYtwIDAQAB"; - npx:hasSignature "bmf4gPxGC32ednx00sJ13pB+6dNtwfPR+ev7RzVRtSqr5IHR8I4URq44kS4xTLKY7ZeHsfLLoFavN9PqJIhsMRjeXRoxUVSYLBUPTPY5s1nGKKvgp9ktx1kTU8RJuiBJ5Un0VA612dmNxbrU/G6Ul9gsE0bRjiN6WF7RTfY3izA="; - npx:hasSignatureTarget this:; - npx:signedBy orcid:0000-0002-1267-0234 . -} diff --git a/src/test/resources/np-statusline-example.trig b/src/test/resources/np-statusline-example.trig deleted file mode 100644 index ffc416b78..000000000 --- a/src/test/resources/np-statusline-example.trig +++ /dev/null @@ -1,39 +0,0 @@ -@prefix this: . -@prefix sub: . -@prefix np: . -@prefix dct: . -@prefix pav: . -@prefix rdf: . -@prefix owl: . -@prefix rdfg: . -@prefix dce: . -@prefix xsd: . -@prefix rdfs: . -@prefix prov: . -@prefix npx: . - -sub:Head { - this: a np:Nanopublication; - np:hasAssertion sub:assertion; - np:hasProvenance sub:provenance; - np:hasPublicationInfo sub:pubinfo . -} - -sub:assertion { - npx:retracts . -} - -sub:provenance { - sub:assertion prov:wasAttributedTo . -} - -sub:pubinfo { - this: dct:creator . - - sub:sig npx:hasAlgorithm "RSA"; - npx:hasPublicKey "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApbztY8l4lWqVF8L/djJ1knoc7Nm5kVHT9NqSe0fXO9Hel3DRO2IyxJYVEvThhllBuHNtZK32ww23AlglArokhxPCSBPKvVgQS6r46khF2D85tnd5htaBq+bfjMqL+LDlQh3LdBpAqrLmsmfsPkU65CCxSGufBs2v39p41z5FkRXE1JKJ/UZe+1OUq+CibjOfo1g1Nz6HO0fZML7GnQBj5X9lvU0llmDk/sqdNMxAJDSQh2/Zh0kz7+Dm7vJOx3mNEXU4FuzVzKBYwTotvEcJod7Vot1fPOJXPGoNDVNKMQffCala9o4pqT739LS7R7ZFVMjzPkLUxYCjnEgOZI5t6wIDAQAB"; - npx:hasSignature "b02egVbAt/XzvcLq12zscVfRJYYeC/d7Vc8bV+EEoYFT9Mk2zT2eE/wugXCTsB1DalLrtjyHd1r9xiBKyZuj161O8b5KKlYUYvEh4wpnd6xdJevGH9Ad9LK+VI3a5dNXVxJAKlYX87MvuHPohLeu0EHT1IochUDyAVWtU7roHaxJHZ6SB4FnJv6XrJ2detnkx2LeT6nNLUvPxgE62qjdML2hgEhZNDVTCma7/ub6gk0n5MIgtthX8IpafvJH9VvePtZg+L2pp3cGW2MQPFkG7fnnYlli6OX37yu1Q4YAS0JLubqPaUGL4rI82zkIT6CpLhJfU13TruqzzWZE30e1jQ=="; - npx:hasSignatureTarget this:; - npx:signedBy . -} - diff --git a/src/test/resources/templates/new-style-assertion-template.trig b/src/test/resources/templates/new-style-assertion-template.trig deleted file mode 100644 index 20c9be60d..000000000 --- a/src/test/resources/templates/new-style-assertion-template.trig +++ /dev/null @@ -1,47 +0,0 @@ -@prefix this: . -@prefix sub: . -@prefix np: . -@prefix dct: . -@prefix npx: . -@prefix nt: . -@prefix xsd: . -@prefix rdf: . -@prefix rdfs: . -@prefix prov: . - -sub:Head { - this: a np:Nanopublication ; - np:hasAssertion sub:assertion ; - np:hasProvenance sub:provenance ; - np:hasPublicationInfo sub:pubinfo . -} - -sub:assertion { - sub:template a nt:AssertionTemplate ; - dct:isVersionOf sub:test-template-kind ; - rdfs:label "Test template with embedded identity" ; - dct:description "A test template whose template node is an embedded IRI (not the assertion graph URI), per docs/template-identity-and-governance.md." ; - nt:hasStatement sub:st01 . - - sub:st01 rdf:subject sub:thing ; - rdf:predicate rdfs:label ; - rdf:object sub:name . - - sub:thing a nt:UriPlaceholder ; - rdfs:label "URI of the thing to label" . - - sub:name a nt:LiteralPlaceholder ; - rdfs:label "label text" . -} - -sub:provenance { - sub:assertion prov:wasAttributedTo . -} - -sub:pubinfo { - this: dct:created "2026-07-08T00:00:00Z"^^xsd:dateTime ; - dct:creator ; - dct:license ; - npx:embeds sub:template ; - npx:introduces sub:test-template-kind . -} From 80f7ce4ef3cdae997ab9d464c95c2506c2110cf6 Mon Sep 17 00:00:00 2001 From: Ashley Caselli Date: Mon, 31 Aug 2026 11:22:33 +0200 Subject: [PATCH 2/2] test: require the grlc query template rather than skipping without it The check against the real "Defining a grlc query" template was written while its test-suite entry was still a pending pull request, so it skipped when the entry was missing (Nanopublication/nanopub-testsuite#5). That merged on 2026-08-20, and the guard has been dead weight since: its own comment says the test "fails rather than quietly skipping" if a later version of the template moves the SPARQL field, which was not true while an absent entry took the test out of the run instead. Now a missing entry throws, like the other suite fixtures read in this tree (#620): it would mean the entry has been renamed or removed, and the test is no longer testing what it says it is. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014dByNErNfuj4au4r5vMsbf --- .../SparqlPlaceholderValidationTest.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/test/java/com/knowledgepixels/nanodash/component/SparqlPlaceholderValidationTest.java b/src/test/java/com/knowledgepixels/nanodash/component/SparqlPlaceholderValidationTest.java index aebc4f31a..842cf6ec6 100644 --- a/src/test/java/com/knowledgepixels/nanodash/component/SparqlPlaceholderValidationTest.java +++ b/src/test/java/com/knowledgepixels/nanodash/component/SparqlPlaceholderValidationTest.java @@ -29,11 +29,8 @@ import org.nanopub.vocabulary.KPXL_GRLC; import org.nanopub.vocabulary.NTEMPLATE; -import java.util.Optional; - import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; @@ -124,7 +121,8 @@ void onlyTheSparqlHoldingPlaceholderIsRecognised() throws Exception { * The artifact code of the published "Defining a grlc query" template, which is what query * nanopublications are created from. It lives in the nanopub test suite rather than in this * repository, so that the fixture is shared with the other implementations instead of being - * copied into each of them. + * copied into each of them. Its absence is a failure rather than a reason to skip: it would + * mean the entry has been renamed or removed. */ private static final String QUERY_TEMPLATE_CODE = "RAEFAt-QcFK0ZhqfvlsmS10BnzGJA0xwOICZXkO-ai87k"; @@ -133,11 +131,11 @@ void onlyTheSparqlHoldingPlaceholderIsRecognised() throws Exception { // version of that template moves the SPARQL field, this fails rather than quietly skipping. @Test void theRealQueryTemplateHasItsSparqlFieldRecognised() throws Exception { - Optional entry = NanopubTestSuite.getLatest() - .getByArtifactCode(QUERY_TEMPLATE_CODE, TestSuiteCategory.VALID); - assumeTrue(entry.isPresent(), - "The grlc query template is not in the test suite yet: Nanopublication/nanopub-testsuite#5"); - Nanopub np = new NanopubImpl(entry.get().toFile(), RDFFormat.TRIG); + TestSuiteEntry entry = NanopubTestSuite.getLatest() + .getByArtifactCode(QUERY_TEMPLATE_CODE, TestSuiteCategory.VALID) + .orElseThrow(() -> new IllegalStateException( + "Not in the nanopub test suite: " + QUERY_TEMPLATE_CODE)); + Nanopub np = new NanopubImpl(entry.toFile(), RDFFormat.TRIG); Template template = TemplateTestUtil.parseTemplate(np); String base = np.getUri().stringValue() + "/"; assertTrue(template.isSparqlPlaceholder(vf.createIRI(base + "sparql")));