diff --git a/CHANGELOG.md b/CHANGELOG.md index caf0b78..d7be698 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Ensure container created correctly on slash semantic tests. * Update default test subjects for latest releases. * For tests posting to a non-existent target, 405 is a valid response if target is not a container. +* Add example tests for the Solid Notifications and WebSocketSubscription2021 specifications (disabled by default). ## Release 0.0.11 * Moved repository to `solid-contrib` organization. diff --git a/application.yaml b/application.yaml index b099f82..7ee043b 100644 --- a/application.yaml +++ b/application.yaml @@ -12,6 +12,13 @@ sources: - https://github.com/solid-contrib/specification-tests/blob/main/web-access-control/web-access-control-test-manifest.ttl - https://github.com/solid-contrib/specification-tests/blob/main/web-access-control/requirement-comments.ttl + # Notifications spec & manifest + # Editor's draft (fully annotated) + - https://solid.github.io/notifications/protocol + - https://github.com/solid-contrib/specification-tests/blob/main/notifications/notifications-test-manifest.ttl + - https://solidproject.org/TR/websocket-subscription-2021 + - https://github.com/solid-contrib/specification-tests/blob/main/notifications/websocket-test-manifest.ttl + # Published draft (not annotated) # - https://solidproject.org/TR/2021/wac-20210711 # - https://github.com/solid-contrib/specification-tests/blob/main/web-access-control/web-access-control-test-manifest-20210711.ttl diff --git a/notifications/access/subscribe.feature b/notifications/access/subscribe.feature new file mode 100644 index 0000000..1c79006 --- /dev/null +++ b/notifications/access/subscribe.feature @@ -0,0 +1,13 @@ +@ignore +Feature: Subscribe to a resource + + # params are subscriptionEndpoint, subscriptionType, url + Scenario: + Given url subscriptionEndpoint + And headers clients.alice.getAuthHeaders('POST', subscriptionEndpoint) + And header Content-Type = 'application/ld+json' + And header Accept = 'application/ld+json' + And request {@context: ['https://www.w3.org/ns/solid/notification/v1'], type: '#(subscriptionType)', topic: '#(url)'} + When method POST + Then status 200 + * def endpoint = response.endpoint diff --git a/notifications/access/subscription-access-controls.feature b/notifications/access/subscription-access-controls.feature new file mode 100644 index 0000000..bfc988f --- /dev/null +++ b/notifications/access/subscription-access-controls.feature @@ -0,0 +1,24 @@ +@notifications +Feature: Notification subscription access controls + + Background: + * def testContainer = rootTestContainer.createContainer() + * def setup = callonce read('../subscription-endpoint.feature') + * def subscription = call read('subscribe.feature') { subscriptionEndpoint: '#(setup.subscriptionEndpoint)', subscriptionType: '#(setup.subscriptionType)', url: '#(testContainer.url)' } + + Scenario: Notifications are sent + * def containerSocket = karate.webSocket(subscription.endpoint, null, {subProtocol: 'solid-0.2'}) + * assert containerSocket != null + * def resource = testContainer.createResource('.txt', 'Hello World!', 'text/plain'); + * listen 5000 + * def model = parse(listenResult, 'application/ld+json', testContainer.url) + * assert model.contains(null, iri(RDF, 'type'), iri(PROV, 'Activity')) + * assert model.contains(null, iri(RDF, 'type'), iri(AS, 'Update')) + * assert model.contains(null, iri(AS, 'object'), iri(testContainer.url)) + * assert model.contains(null, iri(AS, 'published'), null) + # actor - currently returns container not webid +# * assert model.contains(null, iri('https://www.w3.org/ns/activitystreams#actor'), iri(webIds.alice)) + * resource.delete() + * listen 5000 + * def resourceModel = parse(listenResult, 'application/ld+json', testContainer.url) + * print resourceModel.asTriples() diff --git a/notifications/access/subscription-read-required.feature b/notifications/access/subscription-read-required.feature new file mode 100644 index 0000000..f2f4aad --- /dev/null +++ b/notifications/access/subscription-read-required.feature @@ -0,0 +1,27 @@ +@notifications +Feature: Notification subscription requires read access + + Background: + * def testContainer = rootTestContainer.createContainer() + * def setup = callonce read('../subscription-endpoint.feature') + * def subscriptionEndpoint = setup.subscriptionEndpoint + + Scenario: Bob can subscribe with read access to resource + * testContainer.accessDataset = testContainer.accessDatasetBuilder.setAgentAccess(testContainer.url, webIds.bob, ['read']).build() + Given url subscriptionEndpoint + And headers clients.bob.getAuthHeaders('POST', subscriptionEndpoint) + And header Content-Type = 'application/ld+json' + And header Accept = 'application/ld+json' + And request {@context: ['https://www.w3.org/ns/solid/notification/v1'], type: '#(setup.subscriptionType)', topic: '#(testContainer.url)'} + When method POST + Then status 200 + + Scenario: Bob cannot subscribe without read access to resource + * testContainer.accessDataset = testContainer.accessDatasetBuilder.setAgentAccess(testContainer.url, webIds.bob, ['write', 'append', 'control']).build() + Given url subscriptionEndpoint + And headers clients.bob.getAuthHeaders('POST', subscriptionEndpoint) + And header Content-Type = 'application/ld+json' + And header Accept = 'application/ld+json' + And request {@context: ['https://www.w3.org/ns/solid/notification/v1'], type: '#(setup.subscriptionType)', topic: '#(testContainer.url)'} + When method POST + Then status 403 \ No newline at end of file diff --git a/notifications/notifications-test-manifest.ttl b/notifications/notifications-test-manifest.ttl new file mode 100644 index 0000000..0ed3418 --- /dev/null +++ b/notifications/notifications-test-manifest.ttl @@ -0,0 +1,67 @@ +prefix rdf: +prefix rdfs: +prefix xsd: +prefix dcterms: +prefix td: +prefix spec: + +# Notifications Proposal +prefix sono: + +prefix manifest: <#> + +manifest:notifications-discovery-serialization + a td:TestCase ; + spec:requirementReference sono:discovery-serialization ; + td:reviewStatus td:unreviewed ; + spec:testScript + . + +manifest:notifications-subscription-resource-safe-methods + a td:TestCase ; + spec:requirementReference sono:server-subscription-resource-safe-methods ; + td:reviewStatus td:unreviewed ; + spec:testScript + . + +manifest:notifications-subscription-creation + a td:TestCase ; + spec:requirementReference sono:server-subscription-creation ; + td:reviewStatus td:unreviewed ; + spec:testScript + . + +manifest:notifications-subscription-context + a td:TestCase ; + spec:requirementReference sono:server-subscription-context-value ; + td:reviewStatus td:unreviewed ; + spec:testScript + . + +manifest:notifications-subscription-type + a td:TestCase ; + spec:requirementReference sono:server-subscription-type-value ; + td:reviewStatus td:unreviewed ; + spec:testScript + . + +manifest:notifications-subscription-topic + a td:TestCase ; + spec:requirementReference sono:server-subscription-topic-value ; + td:reviewStatus td:unreviewed ; + spec:testScript + . + +manifest:notifications-subscription-access-controls + a td:TestCase ; + spec:requirementReference sono:server-subscription-access-controls ; + td:reviewStatus td:unreviewed ; + spec:testScript + . + +manifest:notifications-subscription-access-read + a td:TestCase ; + spec:requirementReference sono:server-subscription-access-read ; + td:reviewStatus td:unreviewed ; + spec:testScript + . diff --git a/notifications/protocol/serialization.feature b/notifications/protocol/serialization.feature new file mode 100644 index 0000000..e2ccf48 --- /dev/null +++ b/notifications/protocol/serialization.feature @@ -0,0 +1,42 @@ +@notifications +Feature: Notification subscription metadata resource serialization + + Background: Discover the notification channels + # TODO: The spec does not yet define how Notification Subscription Metadata should be discovered - this is an example approach + Given url resolveUri(rootTestContainer.url, '/.well-known/solid') + And header Accept = 'text/turtle' + When method GET + Then status 200 + + * def model = parse(response, 'text/turtle', rootTestContainer.url) + * def notificationGatewayPredicate = iri(SOLID, 'notificationGateway') + * assert model.contains(null, notificationGatewayPredicate, null) + * def notificationSubscriptionMetadata = model.objects(null, notificationGatewayPredicate)[0] + + * def channelsHaveTypes = + """ + function(model) { + // get all channels and filter out those with an RDF type - the result should be empty + return model.objects(null, iri(NOTIFY, 'notificationChannel')).filter(nc => { + !model.contains(iri(nc), iri(RDF, 'type'), null) + }).length === 0 + } + """ + + Scenario: Serialized as Turtle + Given url notificationSubscriptionMetadata + And header Accept = 'text/turtle' + When method GET + Then status 200 + And match header Content-Type contains 'text/turtle' + * def model = parse(response, 'text/turtle', notificationGateway) + And assert channelsHaveTypes(model) + + Scenario: Serialized as JSON-LD + Given url notificationSubscriptionMetadata + And header Accept = 'application/ld+json' + When method GET + Then status 200 + And match header Content-Type contains 'application/ld+json' + * def model = parse(response, 'application/ld+json', notificationGateway) + And assert channelsHaveTypes(model) diff --git a/notifications/protocol/subscription-context.feature b/notifications/protocol/subscription-context.feature new file mode 100644 index 0000000..ebbe167 --- /dev/null +++ b/notifications/protocol/subscription-context.feature @@ -0,0 +1,16 @@ +@notifications +Feature: Notification subscription context field + + Background: + * def testContainer = rootTestContainer.createContainer() + * def setup = callonce read('../subscription-endpoint.feature') + * def subscriptionEndpoint = setup.subscriptionEndpoint + + Scenario: Subscription request must contain the correct context + Given url subscriptionEndpoint + And headers clients.alice.getAuthHeaders('POST', subscriptionEndpoint) + And header Content-Type = 'application/ld+json' + And header Accept = 'application/ld+json' + And request {@context: ['https://www.w3.org/ns/solid/notification/v1'], type: '#(setup.subscriptionType)', topic: '#(testContainer.url)'} + When method POST + Then match response['@context'] contains 'https://www.w3.org/ns/solid/notification/v1' diff --git a/notifications/protocol/subscription-creation.feature b/notifications/protocol/subscription-creation.feature new file mode 100644 index 0000000..de86b38 --- /dev/null +++ b/notifications/protocol/subscription-creation.feature @@ -0,0 +1,16 @@ +@notifications +Feature: Notification subscription creation + + Background: + * def testContainer = rootTestContainer.createContainer() + * def setup = callonce read('../subscription-endpoint.feature') + * def subscriptionEndpoint = setup.subscriptionEndpoint + + Scenario: Subscription endpoint accepts POST + Given url subscriptionEndpoint + And headers clients.alice.getAuthHeaders('POST', subscriptionEndpoint) + And header Content-Type = 'application/ld+json' + And header Accept = 'application/ld+json' + And request {@context: ['https://www.w3.org/ns/solid/notification/v1'], type: '#(setup.subscriptionType)', topic: '#(testContainer.url)'} + When method POST + Then match responseStatus != 405 diff --git a/notifications/protocol/subscription-safe-methods.feature b/notifications/protocol/subscription-safe-methods.feature new file mode 100644 index 0000000..32f83d0 --- /dev/null +++ b/notifications/protocol/subscription-safe-methods.feature @@ -0,0 +1,24 @@ +@notifications +Feature: Notification subscription safe methods + + Background: + * def setup = callonce read('../subscription-endpoint.feature') + * def subscriptionEndpoint = setup.subscriptionEndpoint + + Scenario: Subscription endpoint accepts GET + Given url subscriptionEndpoint + And headers clients.alice.getAuthHeaders('GET', subscriptionEndpoint) + When method GET + Then status 200 + + Scenario: Subscription endpoint accepts HEAD + Given url subscriptionEndpoint + And headers clients.alice.getAuthHeaders('HEAD', subscriptionEndpoint) + When method HEAD + Then status 200 + + Scenario: Subscription endpoint accepts OPTIONS + Given url subscriptionEndpoint + And headers clients.alice.getAuthHeaders('OPTIONS', subscriptionEndpoint) + When method OPTIONS + Then match [200, 204] contains responseStatus diff --git a/notifications/protocol/subscription-topic.feature b/notifications/protocol/subscription-topic.feature new file mode 100644 index 0000000..96d9771 --- /dev/null +++ b/notifications/protocol/subscription-topic.feature @@ -0,0 +1,34 @@ +@notifications +Feature: Notification subscription topic field + + Background: + * def testContainer = rootTestContainer.createContainer() + * def setup = callonce read('../subscription-endpoint.feature') + * def subscriptionEndpoint = setup.subscriptionEndpoint + + Scenario: Subscription request must contain topic + Given url subscriptionEndpoint + And headers clients.alice.getAuthHeaders('POST', subscriptionEndpoint) + And header Content-Type = 'application/ld+json' + And header Accept = 'application/ld+json' + And request {@context: ['https://www.w3.org/ns/solid/notification/v1'], type: '#(setup.subscriptionType)', topic: '#(testContainer.url)'} + When method POST + Then status 200 + + # server should respond with an error if the topic is missing + Given url subscriptionEndpoint + And headers clients.alice.getAuthHeaders('POST', subscriptionEndpoint) + And header Content-Type = 'application/ld+json' + And header Accept = 'application/ld+json' + And request {@context: ['https://www.w3.org/ns/solid/notification/v1'], type: '#(setup.subscriptionType)'} + When method POST + Then assert responseStatus >= 400 && responseStatus < 500 + + # server should respond with an error if the topic is invalid + Given url subscriptionEndpoint + And headers clients.alice.getAuthHeaders('POST', subscriptionEndpoint) + And header Content-Type = 'application/ld+json' + And header Accept = 'application/ld+json' + And request {@context: ['https://www.w3.org/ns/solid/notification/v1'], type: '#(setup.subscriptionType)', topic: 'BAD'} + When method POST + Then assert responseStatus >= 400 && responseStatus < 500 \ No newline at end of file diff --git a/notifications/protocol/subscription-type.feature b/notifications/protocol/subscription-type.feature new file mode 100644 index 0000000..59ad358 --- /dev/null +++ b/notifications/protocol/subscription-type.feature @@ -0,0 +1,35 @@ +@notifications +Feature: Notification subscription type field + + Background: + * def testContainer = rootTestContainer.createContainer() + * def setup = callonce read('../subscription-endpoint.feature') + * def subscriptionEndpoint = setup.subscriptionEndpoint + + Scenario: Subscription request must contain subscription type + Given url subscriptionEndpoint + And headers clients.alice.getAuthHeaders('POST', subscriptionEndpoint) + And header Content-Type = 'application/ld+json' + And header Accept = 'application/ld+json' + And request {@context: ['https://www.w3.org/ns/solid/notification/v1'], type: '#(setup.subscriptionType)', topic: '#(testContainer.url)'} + When method POST + Then status 200 + And match response.type == setup.subscriptionType + + # server should respond with an error if the type is missing + Given url subscriptionEndpoint + And headers clients.alice.getAuthHeaders('POST', subscriptionEndpoint) + And header Content-Type = 'application/ld+json' + And header Accept = 'application/ld+json' + And request {@context: ['https://www.w3.org/ns/solid/notification/v1'], topic: '#(testContainer.url)'} + When method POST + Then assert responseStatus >= 400 && responseStatus < 500 + + # server should respond with an error if the type is unknown + Given url subscriptionEndpoint + And headers clients.alice.getAuthHeaders('POST', subscriptionEndpoint) + And header Content-Type = 'application/ld+json' + And header Accept = 'application/ld+json' + And request {@context: ['https://www.w3.org/ns/solid/notification/v1'], type: 'UNKNOWN', topic: '#(testContainer.url)'} + When method POST + Then assert responseStatus >= 400 && responseStatus < 500 diff --git a/notifications/subscription-endpoint.feature b/notifications/subscription-endpoint.feature new file mode 100644 index 0000000..b7ed0cc --- /dev/null +++ b/notifications/subscription-endpoint.feature @@ -0,0 +1,63 @@ +@ignore +Feature: Routine to get a websocket endpoint from a notification gateway + + # param: subscriptionType + Scenario: + # TODO: The spec does not yet define how Notification Subscription Metadata should be discovered - this is an example approach + Given url resolveUri(rootTestContainer.url, '/.well-known/solid') + And header Accept = 'text/turtle' + When method GET + Then status 200 + + * def model = parse(response, 'text/turtle', rootTestContainer.url) + * def notificationGatewayPredicate = iri('http://www.w3.org/ns/solid/terms#notificationGateway') + * assert model.contains(null, notificationGatewayPredicate, null) + * def notificationSubscriptionMetadata = model.objects(null, notificationGatewayPredicate)[0] + + # NOTIFICATION GATEWAY IMPLEMENTATION + * def selectedType = karate.get('subscriptionType', 'WebSocketSubscription2021') + Given url notificationSubscriptionMetadata + And header Accept = 'application/ld+json' + And header Content-Type = 'application/ld+json' + And request {"@context": ["https://www.w3.org/ns/solid/notification/v1"], "type": ["#(selectedType)"], "protocols": ["ws"]} + When method POST + Then status 200 + And match response.endpoint == '#notnull' + * def subscriptionEndpoint = response.endpoint + * def subscriptionType = selectedType + + # NOTIFICATION CHANNEL DISCOVERY +# Given url notificationSubscriptionMetadata +# And header Accept = 'text/turtle' +# When method GET +# Then status 200 +# +# # find the subscription endpoint for the given channel, or default to the first available +# * def findEndpoint = +# """ +# function(model) { +# let channels; +# const selectedType = karate.get('subscriptionType') +# if (selectedType) { +# channels = model.subjects(iri(RDF, 'type'), iri(NOTIFY, selectedType)); +# } else { +# channels = model.objects(null, iri(NOTIFY, 'notificationChannel')); +# } +# if (channels.length > 0) { +# const subscriptions = model.objects(channels[0], iri(NOTIFY, 'subscription')); +# if (subscriptions.length > 0) { +# if (!selectedType) { +# const types = model.objects(channels[0], iri(RDF, 'type')); +# if (types.length > 0) { +# karate.set('subscriptionType', types[0]) +# } +# } +# return subscriptions[0] +# } +# } +# return null; +# } +# """ +# +# * def model = parse(response, 'text/turtle', notificationSubscriptionMetadata) +# * def subscriptionEndpoint = findEndpoint(model) \ No newline at end of file diff --git a/notifications/websocket-test-manifest.ttl b/notifications/websocket-test-manifest.ttl new file mode 100644 index 0000000..e59e8f1 --- /dev/null +++ b/notifications/websocket-test-manifest.ttl @@ -0,0 +1,33 @@ +prefix rdf: +prefix rdfs: +prefix xsd: +prefix dcterms: +prefix td: +prefix spec: + +# Notifications Proposal +prefix sows: + +prefix manifest: <#> + +manifest:websocket-source + a td:TestCase ; + spec:requirementReference sows:websocketsubscription2021-source-wss ; + td:reviewStatus td:unreviewed ; + spec:testScript + . + +manifest:websocket-type + a td:TestCase ; + spec:requirementReference sows:client-subscription-type ; + td:reviewStatus td:unreviewed ; + spec:testScript + . + +manifest:websocket-topic + a td:TestCase ; + spec:requirementReference sows:client-subscription-feature ; + td:reviewStatus td:unreviewed ; + spec:testScript + . + diff --git a/notifications/websocket/websocket-source.feature b/notifications/websocket/websocket-source.feature new file mode 100644 index 0000000..b432722 --- /dev/null +++ b/notifications/websocket/websocket-source.feature @@ -0,0 +1,17 @@ +@notifications +Feature: WebSocketSubscription2021 source field + + Background: + * def testContainer = rootTestContainer.createContainer() + * def setup = callonce read('../subscription-endpoint.feature') {subscriptionType: 'WebSocketSubscription2021'} + * def wsEndpoint = setup.subscriptionEndpoint + + Scenario: Subscription response source must use wss schema + Given url wsEndpoint + And headers clients.alice.getAuthHeaders('POST', wsEndpoint) + And header Content-Type = 'application/ld+json' + And header Accept = 'application/ld+json' + And request {@context: ['https://www.w3.org/ns/solid/notification/v1'], type: 'WebSocketSubscription2021', topic: '#(testContainer.url)'} + When method POST + Then match responseStatus != 405 + And assert response.endpoint.startsWith('wss://') diff --git a/notifications/websocket/websocket-topic.feature b/notifications/websocket/websocket-topic.feature new file mode 100644 index 0000000..236cfe8 --- /dev/null +++ b/notifications/websocket/websocket-topic.feature @@ -0,0 +1,34 @@ +@notifications +Feature: WebSocketSubscription2021 topic field + + Background: + * def testContainer = rootTestContainer.createContainer() + * def setup = callonce read('../subscription-endpoint.feature') {subscriptionType: 'WebSocketSubscription2021'} + * def wsEndpoint = setup.subscriptionEndpoint + + Scenario: Subscription request must contain topic + Given url wsEndpoint + And headers clients.alice.getAuthHeaders('POST', wsEndpoint) + And header Content-Type = 'application/ld+json' + And header Accept = 'application/ld+json' + And request {@context: ['https://www.w3.org/ns/solid/notification/v1'], type: 'WebSocketSubscription2021', topic: '#(testContainer.url)'} + When method POST + Then status 200 + + # server should respond with an error if the topic is missing + Given url wsEndpoint + And headers clients.alice.getAuthHeaders('POST', wsEndpoint) + And header Content-Type = 'application/ld+json' + And header Accept = 'application/ld+json' + And request {@context: ['https://www.w3.org/ns/solid/notification/v1'], type: 'WebSocketSubscription2021'} + When method POST + Then assert responseStatus >= 400 && responseStatus < 500 + + # server should respond with an error if the topic is invalid + Given url wsEndpoint + And headers clients.alice.getAuthHeaders('POST', wsEndpoint) + And header Content-Type = 'application/ld+json' + And header Accept = 'application/ld+json' + And request {@context: ['https://www.w3.org/ns/solid/notification/v1'], type: 'WebSocketSubscription2021', topic: 'BAD'} + When method POST + Then assert responseStatus >= 400 && responseStatus < 500 \ No newline at end of file diff --git a/notifications/websocket/websocket-type.feature b/notifications/websocket/websocket-type.feature new file mode 100644 index 0000000..bddc3b7 --- /dev/null +++ b/notifications/websocket/websocket-type.feature @@ -0,0 +1,36 @@ +@notifications +Feature: WebSocketSubscription2021 type field + + Background: + * def testContainer = rootTestContainer.createContainer() + * def setup = callonce read('../subscription-endpoint.feature') {subscriptionType: 'WebSocketSubscription2021'} + * def wsEndpoint = setup.subscriptionEndpoint + + Scenario: Subscription request must contain subscription type + Given url wsEndpoint + And headers clients.alice.getAuthHeaders('POST', wsEndpoint) + And header Content-Type = 'application/ld+json' + And header Accept = 'application/ld+json' + And request {@context: ['https://www.w3.org/ns/solid/notification/v1'], type: 'WebSocketSubscription2021', topic: '#(testContainer.url)'} + When method POST + Then status 200 +# "protocol":"ws","subprotocol":"solid-0.2" for ESS + And match response.type == 'WebSocketSubscription2021' + + # server should respond with an error if the type is missing + Given url wsEndpoint + And headers clients.alice.getAuthHeaders('POST', wsEndpoint) + And header Content-Type = 'application/ld+json' + And header Accept = 'application/ld+json' + And request {@context: ['https://www.w3.org/ns/solid/notification/v1'], topic: '#(testContainer.url)'} + When method POST + Then assert responseStatus >= 400 && responseStatus < 500 + + # server should respond with an error if the type is unknown + Given url wsEndpoint + And headers clients.alice.getAuthHeaders('POST', wsEndpoint) + And header Content-Type = 'application/ld+json' + And header Accept = 'application/ld+json' + And request {@context: ['https://www.w3.org/ns/solid/notification/v1'], type: 'UNKNOWN', topic: '#(testContainer.url)'} + When method POST + Then assert responseStatus >= 400 && responseStatus < 500 diff --git a/test-subjects.ttl b/test-subjects.ttl index 3fa3406..4f3df5a 100644 --- a/test-subjects.ttl +++ b/test-subjects.ttl @@ -18,7 +18,7 @@ doap:homepage ; doap:description "A production-grade Solid server produced and supported by Inrupt."@en ; doap:programming-language "Java"@en ; - solid-test:skip "wac", "wac-allow-public", "publicagent" ; + solid-test:skip "wac", "wac-allow-public", "publicagent", "notifications" ; rdfs:comment "This version of ESS implements ACP for access control instead of WAC. Whilst it does not therefore conform to that aspect of the specification the access control tests are still run in order to assess the equivalent capabilities in this implementation."@en ; rdfs:comment "Access for anonymous users is not supported nor required by Solid specifications so tests that depend on this are not run."@en. @@ -34,7 +34,7 @@ doap:homepage ; doap:description "An open and modular implementation of the Solid specifications."@en ; doap:programming-language "TypeScript"@en ; - solid-test:skip "acp", "http-redirect" . + solid-test:skip "acp", "http-redirect", "notifications" . doap:revision "5.0.0" ; @@ -48,7 +48,7 @@ doap:homepage ; doap:description "Solid server on top of the file-system in NodeJS."@en ; doap:programming-language "JavaScript"@en ; - solid-test:skip "acp" . + solid-test:skip "acp", "notifications" . doap:revision "5.7.1"@en ; @@ -63,7 +63,7 @@ doap:description "TrinPodâ„¢ is an Industrial strength Solid Pod with conceptual computing through Trinity AI Capable of handling a massive amount of data."@en ; doap:programming-language "Common Lisp"@en ; - solid-test:skip "acp" . + solid-test:skip "acp", "notifications" . doap:revision "2.3.197"@en ;