ActivityPub Servers Must Serve Objects in Response to an HTTP GET Request Accepting ActivityStreams 2.0 Media Type

Description

This rule checks that URLs of ActivityPub objects can be resolved to a representation with well-known media type for further processing.

Table of Contents

Identifiers

Use these identifiers to refer to this Test.

URI

urn:uuid:e7ee491d-88d7-4e67-80c8-f74781bb247c

slug

This slug is memorable, but it is not guaranteed to be globally unique like a URI.

actor-must-serve-as2-object-to-get

Input

This describes the input that each test run will use to select test targets.

input.id

identifier of an ActivityPub Object hosted at an ActivityPub Server

required
true
type
xsd:anyUri
range
https://www.w3.org/ns/activitystreams#Actor

input.id as json
{
  "help": "identifier of an ActivityPub Object hosted at an ActivityPub Server",
  "type": "xsd:anyUri",
  "rangeIncludes": [
    "https://www.w3.org/ns/activitystreams#Actor"
  ],
  "required": true
}

input.authorization

proof of authorization to retrieve the object identified by input `id`

input.authorization as json
{
  "help": "proof of authorization to retrieve the object identified by input `id`"
}

input.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. MUST be an [RFC3339 `dur-time`](https://datatracker.ietf.org/doc/html/rfc3339#appendix-A)

required
true
type
rfc3339:dur-time, TimeLimit

input.time as json
{
  "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"
  ]
}

Input as JSON
{
  "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"
    ]
  }
}

Applicability ?

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

Expectations ?

* `response` body is parseable as JSON
* `response` body parsed JSON is a JSON Object
* `response` body must be an "ActivityStreams object representation"

Requirement Mapping

This Test has been derived from these specified requirements.

  • urn:uuid:08549639-2888-4ee2-a320-97fc7ee32e00

    content
    Servers… *MUST* present the ActivityStreams object representation in response to `application/ld+json; profile="https://www.w3.org/ns/activitystreams"`
    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"
      }
    }
    JSON
    {
      "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"
      ]
    }

Part of

This test is part of the following Test Suites:

JSON

Test Case as JSON
{
  "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/"
    }
  ]
}