This rule checks that URLs of ActivityPub objects can be resolved to a representation with well-known media type for further processing.
Use these identifiers to refer to this Test.
urn:uuid:e7ee491d-88d7-4e67-80c8-f74781bb247c
This slug is memorable, but it is not guaranteed to be globally unique like a URI.
actor-must-serve-as2-object-to-get
This describes the input that each test run will use to select test targets.
identifier of an ActivityPub Object hosted at an ActivityPub Server
{
"help": "identifier of an ActivityPub Object hosted at an ActivityPub Server",
"type": "xsd:anyUri",
"rangeIncludes": [
"https://www.w3.org/ns/activitystreams#Actor"
],
"required": true
}
amount of time allowed to run test. This is meant to configure the limit for how long this test will wait for network requests. MUST be an [RFC3339 `dur-time`](https://datatracker.ietf.org/doc/html/rfc3339#appendix-A)
{
"help": "amount of time allowed to run test. This is meant to configure the limit for how long this test will wait for network requests. MUST be an [RFC3339 `dur-time`](https://datatracker.ietf.org/doc/html/rfc3339#appendix-A)",
"required": true,
"type": [
"rfc3339:dur-time",
"TimeLimit"
]
}
{
"id": {
"help": "identifier of an ActivityPub Object hosted at an ActivityPub Server",
"type": "xsd:anyUri",
"rangeIncludes": [
"https://www.w3.org/ns/activitystreams#Actor"
],
"required": true
},
"authorization": {
"help": "proof of authorization to retrieve the object identified by input `id`"
},
"time": {
"help": "amount of time allowed to run test. This is meant to configure the limit for how long this test will wait for network requests. MUST be an [RFC3339 `dur-time`](https://datatracker.ietf.org/doc/html/rfc3339#appendix-A)",
"required": true,
"type": [
"rfc3339:dur-time",
"TimeLimit"
]
}
}
This test applies to a server hosting the ActivityPub Object identified by input `id`.
If input `id` is not a URI, outcome is `inapplicable`.
If input `id` URI scheme is not `https` or `http`, outcome is `inapplicable`. (This test may be revised later to handle other URI schemes).
If input `time` is not parseable as an RFC3339 `dur-time`, outcome is `inapplicable`.
### Test Targets
* `response` - the HTTP response that is the result of retrieving the ActivityPub Object identified by input `id`.
* how to derive `response` from inputs
1. start a timer with duration from input `time`. If the timer reaches zero before this derivation is complete, the whole test outcome is `inapplicable` because we weren't able to determine the `response` test target within the required time.
2. let `request` be a new HTTP Request whose URI is input `id`
3. add an http request header to `request` whose name is `Accept` and whose value is `application/ld+json; profile="https://www.w3.org/ns/activitystreams"`
4. if input `authorization` was provided, add an http request header to `request` whose name is `Authorization` and whose value is input `authorization`
5. send the HTTP request and await a response
6. assign the HTTP response to the `response` test target
This test applies to a server hosting the ActivityPub Object identified by input `id`.
If input `id` is not a URI, outcome is `inapplicable`.
If input `id` URI scheme is not `https` or `http`, outcome is `inapplicable`. (This test may be revised later to handle other URI schemes).
If input `time` is not parseable as an RFC3339 `dur-time`, outcome is `inapplicable`.
### Test Targets
* `response` - the HTTP response that is the result of retrieving the ActivityPub Object identified by input `id`.
* how to derive `response` from inputs
1. start a timer with duration from input `time`. If the timer reaches zero before this derivation is complete, the whole test outcome is `inapplicable` because we weren't able to determine the `response` test target within the required time.
2. let `request` be a new HTTP Request whose URI is input `id`
3. add an http request header to `request` whose name is `Accept` and whose value is `application/ld+json; profile="https://www.w3.org/ns/activitystreams"`
4. if input `authorization` was provided, add an http request header to `request` whose name is `Authorization` and whose value is input `authorization`
5. send the HTTP request and await a response
6. assign the HTTP response to the `response` test target
* `response` body is parseable as JSON * `response` body parsed JSON is a JSON Object * `response` body must be an "ActivityStreams object representation"
* `response` body is parseable as JSON * `response` body parsed JSON is a JSON Object * `response` body must be an "ActivityStreams object representation"
This Test has been derived from these specified requirements.
{
"source": "https://www.w3.org/TR/activitypub/",
"section": {
"id": "https://www.w3.org/TR/activitypub/#retrieving-objects",
"branch": [
3,
2
]
},
"selector": {
"type": "TextQuoteSelector",
"exact": "MUST present the ActivityStreams object representation in response to application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
"prefix": "in response to a request, but",
"suffix": ", and SHOULD also present"
}
}
{
"id": "urn:uuid:08549639-2888-4ee2-a320-97fc7ee32e00",
"type": "Behavior",
"name": "ActivityPub server must present object in response to GET request with AS2 Accept Header",
"content": "Servers… *MUST* present the ActivityStreams object representation in response\nto `application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"`\n",
"tag": [
{
"name": "ActivityPubServer",
"id": "https://socialweb.coop/tag/ActivityPubServer"
}
],
"origin": {
"source": "https://www.w3.org/TR/activitypub/",
"section": {
"id": "https://www.w3.org/TR/activitypub/#retrieving-objects",
"branch": [
3,
2
]
},
"selector": {
"type": "TextQuoteSelector",
"exact": "MUST present the ActivityStreams object representation in response to application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
"prefix": "in response to a request, but",
"suffix": ", and SHOULD also present"
}
},
"uuid": "08549639-2888-4ee2-a320-97fc7ee32e00",
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://socialweb.coop/ns/testing/context.json"
]
}
This test is part of the following Test Suites:
{
"description": "This rule checks that URLs of ActivityPub objects can be resolved to a representation with well-known media type for further processing.",
"expectations": "* `response` body is parseable as JSON\n* `response` body parsed JSON is a JSON Object\n* `response` body must be an \"ActivityStreams object representation\"\n",
"assumptions": "",
"applicability": "This test applies to a server hosting the ActivityPub Object identified by input `id`.\nIf input `id` is not a URI, outcome is `inapplicable`.\nIf input `id` URI scheme is not `https` or `http`, outcome is `inapplicable`. (This test may be revised later to handle other URI schemes).\nIf input `time` is not parseable as an RFC3339 `dur-time`, outcome is `inapplicable`.\n### Test Targets\n* `response` - the HTTP response that is the result of retrieving the ActivityPub Object identified by input `id`.\n * how to derive `response` from inputs\n 1. start a timer with duration from input `time`. If the timer reaches zero before this derivation is complete, the whole test outcome is `inapplicable` because we weren't able to determine the `response` test target within the required time.\n 2. let `request` be a new HTTP Request whose URI is input `id`\n 3. add an http request header to `request` whose name is `Accept` and whose value is `application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"`\n 4. if input `authorization` was provided, add an http request header to `request` whose name is `Authorization` and whose value is input `authorization`\n 5. send the HTTP request and await a response\n 6. assign the HTTP response to the `response` test target\n",
"failedCases": [
{
"name": "nginx 404 response body",
"input": {
"id": "https://bengo.is/404",
"time": "T1M"
},
"result": {
"outcome": "failed"
},
"targets": {
"response": {
"httpVersion": "2.0",
"statusCodeValue": "404",
"headers": [
[
"content-type",
"text/html; charset=UTF-8"
],
[
"content-length",
"153"
]
],
"body": "\n <html>\n <head><title>404 Not Found</title></head>\n <body>\n <center><h1>404 Not Found</h1></center>\n <hr><center>nginx/1.25.2</center>\n </body>\n </html> \n "
}
}
}
],
"inapplicableCases": [
{
"name": "non-URI input id",
"input": {
"id": "bafybeib5mvfjatmpswc3jnh7ydz4zxe25cm63xp6aafpg3j2awakf63qma",
"time": "T1M"
},
"result": {
"outcome": "inapplicable"
}
},
{
"name": "non-RFC3339-duration input time",
"input": {
"id": "https://bengo.is/actor.json",
"time": "5 minutes"
},
"result": {
"outcome": "inapplicable"
}
}
],
"input": {
"id": {
"help": "identifier of an ActivityPub Object hosted at an ActivityPub Server",
"type": "xsd:anyUri",
"rangeIncludes": [
"https://www.w3.org/ns/activitystreams#Actor"
],
"required": true
},
"authorization": {
"help": "proof of authorization to retrieve the object identified by input `id`"
},
"time": {
"help": "amount of time allowed to run test. This is meant to configure the limit for how long this test will wait for network requests. MUST be an [RFC3339 `dur-time`](https://datatracker.ietf.org/doc/html/rfc3339#appendix-A)",
"required": true,
"type": [
"rfc3339:dur-time",
"TimeLimit"
]
}
},
"markdown": "---\n\nuuid: e7ee491d-88d7-4e67-80c8-f74781bb247c\ntype:\n- TestCase\n- ConformanceTestingRule\nruleType: atomic\nname: |\n ActivityPub server serves an object in response to GET request for AS2 media type\ndescription: |\n This rule checks that URLs of ActivityPub objects can be resolved to a representation with well-known media type for further processing.\nrequirementReference:\n- id: urn:uuid:00330762-59a2-4072-8d93-87ee4c30411c\n url: https://socialweb.coop/activitypub/behaviors/08549639-2888-4ee2-a320-97fc7ee32e00/\ninputs:\n- name: URL of AS2 Object served by an ActivityPub Server\n type: URL\n\n# https://www.w3.org/QA/WG/2005/01/test-faq#review\n# *submitted, accepted, reviewed*, *returned for revision*, or *rejected*\ntestCaseState: submitted\n\neleventyComputed:\n title: \"{{ name }}\"\n\nrespec:\n config:\n editors:\n - name: bengo\n url: \"https://bengo.is\"\n w3cid: 49026\n---\n\n# ActivityPub Servers Must Serve Objects in Response to an HTTP GET Request Accepting ActivityStreams 2.0 Media Type\n\n## Background\n\n[ActivityPub][activitypub] [§3.2 Retrieving Objects](https://www.w3.org/TR/activitypub/#retrieving-objects):\n> Servers... MUST present the ActivityStreams object representation in response to `application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"`\n\n## About This Test\n\nThis is a Test Case describing a rule to determine whether an ActivityPub Object is in partial conformance with the following behaviors required by [ActivityPub][activitypub].\n\n* [requirement 08549639-2888-4ee2-a320-97fc7ee32e00](https://socialweb.coop/activitypub/behaviors/08549639-2888-4ee2-a320-97fc7ee32e00/) - ActivityPub server must present object in response to GET request with AS2 Accept Header\n\n### Identifier\n\nThe identifier of this test is `urn:uuid:e7ee491d-88d7-4e67-80c8-f74781bb247c`.\n\n## Test Subject\n\nThe subject of this test is an ActivityPub Server.\n\nActivityPub Servers host ActivityPub Objects and are responsible for serving them to clients that request a representation of them.\n\nThe Test Subject can be identified by a URI for an ActivityPub Object. The ActivityPub Object can be requested via HTTP, and the ActivityPub Server is the system that is expected to respond to the HTTP request.\n\n## Inputs\n\nThis test requires the following [inputs](https://www.w3.org/TR/act-rules-format/#input):\n\n1. `id` - identifier of an ActivityPub Object hosted at an ActivityPub Server\n\n * required: yes\n * type: binary\n * constraints\n * MUST be a URI, i.e. an [ActivityPub Object Identifier](https://www.w3.org/TR/activitypub/#obj-id) that is not `null`\n\n2. `authorization` - proof of authorization to retrieve the object identified by input `id`\n\n * required: no\n * if this input is omitted, no `Authorization` will be provided in the HTTP request send by this test\n * type: binary\n * constraints\n * If present, this should be valid as the value of an HTTP `Authorization` header\n\n3. `time` - amount of time allowed to run test. This is meant to configure the limit for how long this test will wait for network requests.\n\n * required: yes\n * example: `T0.0021S`\n * type: binary\n * constraints\n * MUST be an [RFC3339 `dur-time`](https://datatracker.ietf.org/doc/html/rfc3339#appendix-A)\n\n## Applicability\n\nThis test applies to a server hosting the ActivityPub Object identified by input `id`.\n\nIf input `id` is not a URI, outcome is `inapplicable`.\n\nIf input `id` URI scheme is not `https` or `http`, outcome is `inapplicable`. (This test may be revised later to handle other URI schemes).\n\nIf input `time` is not parseable as an RFC3339 `dur-time`, outcome is `inapplicable`.\n\n### Test Targets\n\n* `response` - the HTTP response that is the result of retrieving the ActivityPub Object identified by input `id`.\n * how to derive `response` from inputs\n 1. start a timer with duration from input `time`. If the timer reaches zero before this derivation is complete, the whole test outcome is `inapplicable` because we weren't able to determine the `response` test target within the required time.\n 2. let `request` be a new HTTP Request whose URI is input `id`\n 3. add an http request header to `request` whose name is `Accept` and whose value is `application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"`\n 4. if input `authorization` was provided, add an http request header to `request` whose name is `Authorization` and whose value is input `authorization`\n 5. send the HTTP request and await a response\n 6. assign the HTTP response to the `response` test target\n\n## Expectations\n\n* `response` body is parseable as JSON\n* `response` body parsed JSON is a JSON Object\n* `response` body must be an \"ActivityStreams object representation\"\n * see issue [AP-ec50](#AP-ec50) below\n\n## Assumptions\n\n## Implementation Considerations\n\nThough the spec only *requires* serving the object in response to this `Accept` header value, servers seeking to interop widely may also want to serve the same object in response to Accept header values like:\n\n* `application/ld+json` (but without the `profile=` parameter)\n* `application/activity+json`\n* `application/json`\n\n## Test Cases\n\nThese are test cases for this test itself.\n\n### simple passed case\n\noutcome: `passed`\n\ninputs\n\n* `id`: `https://bengo.is/actor.json`\n* `time`: `T1M`\n\ntest targets\n\n* `response`:\n\n ```http\n HTTP/2 200 \n content-type: application/json\n content-length: 484\n\n {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/v1\"\n ],\n \"id\": \"https://bengo.is/\",\n \"type\": \"Person\",\n \"preferredUsername\": \"bengo\",\n \"name\": \"bengo\",\n \"url\": \"https://bengo.is/\",\n \"inbox\": \"https://mastodon.social/users/bengo/inbox\",\n \"attachments\": [\n {\n \"type\": \"PropertyValue\",\n \"name\": \"Website\",\n \"value\": \"https://bengo.is\"\n }\n ],\n \"outbox\": \"https://bengo.is/activitypub/actors/bengo/outbox.json\"\n }\n ```\n\n * outcome: `passed`\n\n### nginx 404 response body\n\noutcome: `failed`\n\n* rationale: test target `response` does not meet expectation of containing an ActivityPub Object representation in the response body\n\ninputs\n\n* `id`: `https://bengo.is/404`\n* `time`: `T1M`\n\ntest targets\n\n* `response`:\n\n ```http\n HTTP/2 404 \n content-type: text/html; charset=UTF-8\n content-length: 153\n \n <html>\n <head><title>404 Not Found</title></head>\n <body>\n <center><h1>404 Not Found</h1></center>\n <hr><center>nginx/1.25.2</center>\n </body>\n </html>\n ```\n\n * outcome: `failed`\n\n### non-URI input `id`\n\noutcome: `inapplicable`\n\n* rationale: input `id` is not a URI\n\ninputs\n\n* `id`: `bafybeib5mvfjatmpswc3jnh7ydz4zxe25cm63xp6aafpg3j2awakf63qma`\n* `time`: `T1M`\n\ntest targets\n\n* `response` - undefined\n * irrelevant because `id` does not meet syntax requirements\n\n### non-RFC3339-duration input `time`\n\noutcome: `inapplicable`\n\n* rationale: input `time` does not meet syntax requirements\n\ninputs\n\n* `id`: `https://bengo.is/actor.json`\n* `time`: `5 minutes`\n\ntest targets\n\n* `response` - undefined\n * irrelevant because `time` input is malformed regardless of resolved `resposne`\n\n## Glossary\n\n### `outcome`\n\nAn outcome is a conclusion that comes from evaluating a test on a test subject. An outcome can be one of the three following types:\n\n* `inapplicable`: No part of the test subject matches the applicability\n* `passed`: A test target meets all expectations\n* `failed`: A test target does not meet all expectations\n\n## Requirements Mapping\n\n* [ActivityPub requirement 08549639-2888-4ee2-a320-97fc7ee32e00](https://socialweb.coop/activitypub/behaviors/08549639-2888-4ee2-a320-97fc7ee32e00/) - ActivityPub server must present object in response to GET request with AS2 Accept Header\n * Required for Conformance to [ActivityPub][activitypub]\n * Outcome Mapping\n * when test target `response` has outcome `passed`, requirement is satisfied\n * when test target `response` has outcome `failed`, requirement is not satisfied\n * when test target `response` has outcome `inapplicable`, further testing is needed to determine requirement satisfaction\n\n## Change Log\n\n* (~) 2023-10-15T00:00:00Z - sketch as first test case\n* 2023-11-07T21:07:59.214Z - update to be internally consistent and a good first draft\n* 2023-12-29T21:08:32.432Z - clean up test case formatting\n\n## Issues List\n\n* <div id=\"AP-ec50\">AP-ec50: clarify how to verify response body is an \"ActivityStreams object representation\"</div>\n* When rendered to HTML and [published here](https://socialweb.coop/activitypub/test-cases/actor-must-serve-as2-object-to-get/#simple-passed-case), the `response` test targets HTTP response syntax highlighting separates the HTTP headers from the response body. This is a quirk of the rendering process.\n\n[activitypub]: https://www.w3.org/TR/activitypub/\n",
"name": "ActivityPub Servers Must Serve Objects in Response to an HTTP GET Request Accepting ActivityStreams 2.0 Media Type",
"passedCases": [
{
"name": "simple passed case",
"input": {
"id": "https://bengo.is/actor.json",
"time": "T1M"
},
"result": {
"outcome": "passed"
},
"targets": {
"response": {
"httpVersion": "2",
"statusCodeValue": "200",
"headers": [
[
"content-type",
"application/json"
],
[
"content-length",
"404"
]
],
"body": "\n {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/v1\"\n ],\n \"id\": \"https://bengo.is/\",\n \"type\": \"Person\",\n \"preferredUsername\": \"bengo\",\n \"name\": \"bengo\",\n \"url\": \"https://bengo.is/\",\n \"inbox\": \"https://mastodon.social/users/bengo/inbox\",\n \"attachments\": [\n {\n \"type\": \"PropertyValue\",\n \"name\": \"Website\",\n \"value\": \"https://bengo.is\"\n }\n ],\n \"outbox\": \"https://bengo.is/activitypub/actors/bengo/outbox.json\"\n }\n "
}
}
}
],
"slug": "actor-must-serve-as2-object-to-get",
"uuid": "e7ee491d-88d7-4e67-80c8-f74781bb247c",
"isPartOf": [
"https://socialweb.coop/activitypub/test-cases/"
],
"requirementReference": [
{
"id": "urn:uuid:08549639-2888-4ee2-a320-97fc7ee32e00",
"url": "https://socialweb.coop/activitypub/behaviors/08549639-2888-4ee2-a320-97fc7ee32e00/"
}
]
}