Outbox Servers handling activity submissions MUST return a 201 created HTTP status code

Description

This test checks that an ActivityPub Outbox responds with a 201 status code when sent a POST request with an Activity submission.

Table of Contents

Identifiers

Use these identifiers to refer to this Test.

URI

urn:uuid:723afcbb-118d-433e-8ab4-560ffca93582

slug

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

outbox-post-servers-must-return-a-201-created-http-code

Input

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

input.outbox

the url to the Outbox handling an Activity POST request

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

input.outbox as json
{
  "help": "the url to the Outbox handling an Activity POST request",
  "required": true,
  "type": "xsd:anyUri",
  "rangeIncludes": [
    "https://www.w3.org/ns/activitystreams#outbox"
  ]
}

input.authorization

HTTP Authorization header value to include in outbox POST request

required
false
type
xsd:string, https://activitypub-testing.socialweb.coop/ns/HiddenInTestResults
range
https://www.rfc-editor.org/rfc/rfc9110#field.authorization.value

input.authorization as json
{
  "help": "HTTP Authorization header value to include in outbox POST request",
  "required": false,
  "type": [
    "xsd:string",
    "https://activitypub-testing.socialweb.coop/ns/HiddenInTestResults"
  ],
  "rangeIncludes": [
    "https://www.rfc-editor.org/rfc/rfc9110#field.authorization.value"
  ]
}

input.time

amount of time allowed to run test, as IETF RFC3339 dur-time time duration. This is meant to configure the limit for how long this test will wait for network requests.

required
true
type
rfc3339:dur-time, TimeLimit
constraints
[
  {
    "content": " MUST be an [RFC3339 `dur-time`](https://datatracker.ietf.org/doc/html/rfc3339#appendix-A)",
    "mediaType": "test/markdown"
  }
]

input.time as json
{
  "help": "amount of time allowed to run test, as IETF RFC3339 dur-time time duration. This is meant to configure the limit for how long this test will wait for network requests.",
  "required": true,
  "default": "T5S",
  "type": [
    "rfc3339:dur-time",
    "TimeLimit"
  ],
  "constraints": [
    {
      "content": " MUST be an [RFC3339 `dur-time`](https://datatracker.ietf.org/doc/html/rfc3339#appendix-A)",
      "mediaType": "test/markdown"
    }
  ]
}

Input as JSON
{
  "outbox": {
    "help": "the url to the Outbox handling an Activity POST request",
    "required": true,
    "type": "xsd:anyUri",
    "rangeIncludes": [
      "https://www.w3.org/ns/activitystreams#outbox"
    ]
  },
  "authorization": {
    "help": "HTTP Authorization header value to include in outbox POST request",
    "required": false,
    "type": [
      "xsd:string",
      "https://activitypub-testing.socialweb.coop/ns/HiddenInTestResults"
    ],
    "rangeIncludes": [
      "https://www.rfc-editor.org/rfc/rfc9110#field.authorization.value"
    ]
  },
  "time": {
    "help": "amount of time allowed to run test, as IETF RFC3339 dur-time time duration. This is meant to configure the limit for how long this test will wait for network requests.",
    "required": true,
    "default": "T5S",
    "type": [
      "rfc3339:dur-time",
      "TimeLimit"
    ],
    "constraints": [
      {
        "content": " MUST be an [RFC3339 `dur-time`](https://datatracker.ietf.org/doc/html/rfc3339#appendix-A)",
        "mediaType": "test/markdown"
      }
    ]
  }
}

Requirement Mapping

This Test has been derived from these specified requirements.

  • urn:uuid:3b925cdf-89fe-4f51-b41f-26df23f58e0c

    content
    Servers MUST return a 201 Created HTTP code
    origin
    {
      "source": "https://www.w3.org/TR/activitypub/",
      "section": {
        "id": "https://www.w3.org/TR/activitypub/#x6-client-to-server-interactions",
        "branch": [
          6
        ]
      },
      "selector": {
        "type": "TextQuoteSelector",
        "prefix": "If an Activity is submitted with a value in the id property, servers MUST ignore this and generate a new id for the Activity. \n",
        "exact": "Servers MUST return a 201 Created HTTP code\n",
        "suffix": ", and unless the activity is transient, MUST include the new id in the Location header.\n"
      }
    }
    JSON
    {
      "id": "urn:uuid:3b925cdf-89fe-4f51-b41f-26df23f58e0c",
      "type": "Behavior",
      "uuid": "3b925cdf-89fe-4f51-b41f-26df23f58e0c",
      "content": "Servers MUST return a 201 Created HTTP code\n",
      "tag": [
        {
          "name": "ActivityPubServer",
          "id": "https://socialweb.coop/tag/ActivityPubServer"
        }
      ],
      "context": [
        "https://www.w3.org/TR/activitypub/",
        {
          "name": "Client to server interaction"
        },
        {
          "name": "server responding to client posting Activities to an actor's outbox"
        }
      ],
      "origin": {
        "source": "https://www.w3.org/TR/activitypub/",
        "section": {
          "id": "https://www.w3.org/TR/activitypub/#x6-client-to-server-interactions",
          "branch": [
            6
          ]
        },
        "selector": {
          "type": "TextQuoteSelector",
          "prefix": "If an Activity is submitted with a value in the id property, servers MUST ignore this and generate a new id for the Activity. \n",
          "exact": "Servers MUST return a 201 Created HTTP code\n",
          "suffix": ", and unless the activity is transient, MUST include the new id in the Location header.\n"
        }
      },
      "@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 test checks that an ActivityPub Outbox responds with a 201 status code when sent a POST request with an Activity submission.",
  "failedCases": [
    {
      "name": "with authorization, response status 403 Forbidden",
      "input": {
        "outbox": "https://socialweb.coop/activitypub/testing/utilities/response?status=403",
        "submission": "{\"type\":\"Create\"}",
        "authorization": "Bearer foo"
      },
      "result": {
        "outcome": "failed"
      }
    }
  ],
  "inapplicableCases": [
    {
      "name": "non-URI input `id`",
      "input": {
        "outbox": "bafybeib5mvfjatmpswc3jnh7ydz4zxe25cm63xp6aafpg3j2awakf63qma",
        "submission": "{\"type\":\"Create\"}"
      },
      "result": {
        "outcome": "inapplicable"
      }
    }
  ],
  "input": {
    "outbox": {
      "help": "the url to the Outbox handling an Activity POST request",
      "required": true,
      "type": "xsd:anyUri",
      "rangeIncludes": [
        "https://www.w3.org/ns/activitystreams#outbox"
      ]
    },
    "authorization": {
      "help": "HTTP Authorization header value to include in outbox POST request",
      "required": false,
      "type": [
        "xsd:string",
        "https://activitypub-testing.socialweb.coop/ns/HiddenInTestResults"
      ],
      "rangeIncludes": [
        "https://www.rfc-editor.org/rfc/rfc9110#field.authorization.value"
      ]
    },
    "time": {
      "help": "amount of time allowed to run test, as IETF RFC3339 dur-time time duration. This is meant to configure the limit for how long this test will wait for network requests.",
      "required": true,
      "default": "T5S",
      "type": [
        "rfc3339:dur-time",
        "TimeLimit"
      ],
      "constraints": [
        {
          "content": " MUST be an [RFC3339 `dur-time`](https://datatracker.ietf.org/doc/html/rfc3339#appendix-A)",
          "mediaType": "test/markdown"
        }
      ]
    }
  },
  "markdown": "---\n\nuuid: 723afcbb-118d-433e-8ab4-560ffca93582\n\ntype:\n- TestCase\n\nname: |\n  Outbox Servers handling activity submissions MUST return a 201 created HTTP status code\n\nslug: |\n  outbox-post-servers-must-return-a-201-created-http-code\n\ndescription: |\n  This test checks that an ActivityPub Outbox responds with a 201 status code when sent a POST request with an Activity submission.\n\ntestsRequirement:\n- id: urn:uuid:3b925cdf-89fe-4f51-b41f-26df23f58e0c\n  url: https://socialweb.coop/activitypub/behaviors/3b925cdf-89fe-4f51-b41f-26df23f58e0c\n  name: Servers MUST return a 201 Created HTTP code\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\nrespec:\n  config:\n    editors:\n    - name: bengo\n      url: \"https://bengo.is\"\n      w3cid: 49026\n---\n\n# Outbox Servers handling activity submissions MUST return a 201 created HTTP status code\n\n## Background\n\nIn the context of [client-to-server interactions](https://www.w3.org/TR/activitypub/#client-to-server-interactions) via Activity submission, [ActivityPub][activitypub] says\n> Servers MUST return a 201 Created HTTP code\n\n[activitypub]: https://www.w3.org/TR/activitypub/\n\n## About this Test\n\nThis is a Test describing a rule to determine whether an ActivityPub Server is in conformance with the following behaviors required by [ActivityPub][activitypub]:\n\n* Servers MUST return a 201 Created HTTP code aka [requirement 3b925cdf-89fe-4f51-b41f-26df23f58e0c](https://socialweb.coop/activitypub/behaviors/3b925cdf-89fe-4f51-b41f-26df23f58e0c)\n\n### Identifier\n\nThe identifier of this test is `723afcbb-118d-433e-8ab4-560ffca93582`.\n\n## Test Subject\n\nThe subject of this test is an ActivityPub Server serving activity submission requests for an ActivityPub Outbox.\n\nThe Test Subject can be identified by a URI for an ActivityPub Outbox. The ActivityPub Outbox has an `id` property and the ActivityPub server is responsible for responding to requests sent to it.\n\n## Inputs\n\nThis test requires the following [inputs](https://www.w3.org/TR/act-rules-format/#input):\n\n1. `outbox` - identifier of an ActivityPub Outbox\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 submit an activity to the outbox identified by input `outbox`\n    * required: no\n      * if this input is omitted, no `Authorization` header 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. `submission` - ActivityPub message that will be submitted to the Outbox.\n    * required: no\n      * if this input is omitted, the test will use a simple valid submission\n    * type: binary\n      * constraints\n        * should be JSON\n      * note\n        * this will be sent as the HTTP request body of an ActivityPub [client-to-server](https://www.w3.org/TR/activitypub/#client-to-server-interactions) request with header `Content-Type: application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"`.\n\n## Applicability\n\nThis test applies to the server hosting the ActivityPub Outbox identified by input `outbox` and that support requests using HTTP method `POST`.\n\nIf input `outbox` is not a URI, outcome is `inapplicable`.\n\nIf input `outbox` URI scheme is not `https` or `http`, outcome is `inapplicable`. (This test may be revised later to handle other URI schemes).\n\nIf the server indicates it does not support `POST` requests, the test outcome MUST be `inapplicable`.\n\nIf the server indicates that the provided input includes insufficient proof of authorization to write to the outbox, the test outcome MUST be a [`CannotTell`](https://www.w3.org/TR/EARL10-Schema/#CannotTell).\n\n### Test Targets\n\n* `response` - the HTTP response that is the result of submitting the input `submission` to the ActivityPub Outbox identified by input `outbox`.\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 `outbox`\n      3. add an http request header to `request` whose name is `Content-Type` 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. let `submission` be the input `submission`, if provided, otherwise use the json value\n\n          ```json\n          {\n            \"@context\": \"https://www.w3.org/ns/activitystreams\",\n            \"type\": \"Note\",\n            \"content\": \"Say, did you finish reading that book I lent you?\"\n          }\n          ```\n\n      6. let the http request body of `request` be the value of `submission`\n      7. send the HTTP request and await a response\n      8. assign the HTTP response to the `response` test target\n      9. If the status code of `response` is 401, the test outcome MUST be a [`CannotTell`](https://www.w3.org/TR/EARL10-Schema/#CannotTell)\n      10. If the status code of `response` is 403,\n          * if input `authorization` was provided, the test outcome MAY be [`Fail`](https://www.w3.org/TR/EARL10-Schema/#Fail)\n          * else (input `authorization` was not provided) the test outcome MUST be a [`CannotTell`](https://www.w3.org/TR/EARL10-Schema/#CannotTell)\n      11. If the status code of `response` is 405 (e.g. 'Method Not Allowed'), the test outcome MUST be `inapplicable`\n          * rationale: The server is explicitly indicating it does not support requests with method `POST`, and this test is only applicable to servers that do.\n\n## Expectations\n\n* `response` http status code is `201`\n\n## Assumptions\n\n### Default Outbox Submission\n\nIf an input `submission` is not provided, the test will use an activity based on Example 2 in [ActivityPub](https://www.w3.org/TR/activitypub/). If this causes problems, provide an input `submission` that will work to produce a 201 response status code and satisfy the requirement being tested.\n\n### 403 Forbidden Semantics Varies on Authorization\n\nWhen interpreting a HTTP 403 response resulting sending a POST request to the input `outbox`, this test varies its outcome depending on whether an input `authorization` was provided to the test.\n\nA rationale is that HTTP semantics in general defines 403 to mean different things depending on whether authentication credentials were provided in the request:\n\n> The 403 (Forbidden) status code indicates that the server understood the request but refuses to fulfill it. A server that wishes to make public why the request has been forbidden can describe that reason in the response content (if any).\n>\n> If authentication credentials were provided in the request, the server considers them insufficient to grant access. The client SHOULD NOT automatically repeat the request with the same credentials. The client MAY repeat the request with new or different credentials. However, a request might be forbidden for reasons unrelated to the credentials.\n\n<cite><a href=\"https://www.rfc-editor.org/rfc/rfc9110.html#name-403-forbidden\">RFC9110 HTTP Semantics</a></cite>\n\nWhen authentication credentials are provided, and the server returns 403, the test outcome is `failed`. This is based on \"If authentication credentials were provided in the request, the server considers them insufficient to grant access\" from RFC9110 above. `failed` seems appropriate *for the provided inputs* because status 403 communicates that the server received the provided credentials, determined they cannot authorize the request, and that the client SHOULD NOT retrying the request with the same credentials. `failed` should draw attention to either flawed authorization logic or, more likely, an inappropriate `authorization` input passed to the test.\n\nIf no input `authorization` is provided, the test outcome is `CannotTell`. This is because there isn't enough evidence for another outcome, and it's best for a human to take a look at the rest of the result and decide what to do next.\n\nServer implementers are encouraged to review [RFC7235 for HTTP Authentication](https://datatracker.ietf.org/doc/html/rfc7235), which describes a way of responding to requests that lack proof of authorization by using a `401` status code + hints about what authorization is required.\n\n## Test Cases\n\nThese are test cases for this test itself, i.e. each test case is a set of test inputs and the corresponding test results.\n\n### simple passed case\n\ninputs\n\n* `outbox`\n\n    ```url\n    https://socialweb.coop/activitypub/testing/utilities/response?status=201`\n    ```\n\n* `submission`\n\n    ```json\n    {\n      \"type\": \"Create\"\n    }\n    ```\n\ntest targets\n\n* `response`\n\n    ```http\n    HTTP/2 201 \n    content-type: application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\n\n    {}\n    ```\n\n    * outcome: `passed`\n\n### nginx 404 response body\n\ninputs\n\n* `outbox`: `https://socialweb.coop/activitypub/testing/utilities/response?status=404`\n* `submission`\n\n    ```json\n    {\n      \"type\": \"Create\"\n    }\n    ```\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: `cantTell`\n\n### non-URI input `outbox`\n\ninputs\n\n* `outbox`: `bafybeib5mvfjatmpswc3jnh7ydz4zxe25cm63xp6aafpg3j2awakf63qma`\n* `submission`\n\n    ```json\n    {\n      \"type\": \"Create\"\n    }\n    ```\n\nresult\n\n* outcome: `inapplicable`\n    * rationale: input `outbox` is not a URI\n\n### without authorization, response status 401\n\ninputs\n\n* `outbox`: `https://socialweb.coop/activitypub/testing/utilities/response?status=401`\n* `submission`\n\n    ```json\n    {\n      \"type\": \"Create\"\n    }\n    ```\n\ntest targets\n\n* `response`:\n\n    ```http\n    HTTP/1.1 401 Forbidden\n    \n    401\n    ```\n\nresult\n\n* outcome: [`cantTell`](https://www.w3.org/TR/EARL10-Schema/#cantTell)\n\n### response status 405 Method Not Allowed\n\ninputs\n\n* `outbox`: `https://socialweb.coop/activitypub/testing/utilities/response?status=405`\n* `submission`\n\n    ```json\n    {\n      \"type\": \"Create\"\n    }\n    ```\n\ntest targets\n\n* `response`:\n\n    ```http\n    HTTP/1.1 405 Not Allowed\n    \n    405\n    ```\n\n* outcome: `inapplicable`\n    * rationale: input `response` status 405 indicates that requests with method `POST` are not supported, so this test does not apply (see 'Applicability')\n\n### without authorization, response status 403 Forbidden\n\ninputs\n\n* `outbox`: `https://socialweb.coop/activitypub/testing/utilities/response?status=403`\n* `submission`\n\n    ```json\n    {\n      \"type\": \"Create\"\n    }\n    ```\n\ntest targets\n\n* `response`:\n\n    ```http\n    HTTP/1.1 403 Forbidden\n    \n    403\n    ```\n\nresult\n\n* outcome: [`cantTell`](https://www.w3.org/TR/EARL10-Schema/#cantTell)\n\n### with authorization, response status 403 Forbidden\n\ninputs\n\n* `outbox`: `https://socialweb.coop/activitypub/testing/utilities/response?status=403`\n* `authorization`: `Bearer foo`\n* `submission`\n\n    ```json\n    {\n      \"type\": \"Create\"\n    }\n    ```\n\ntest targets\n\n* `response`:\n\n    ```http\n    HTTP/1.1 403 Forbidden\n    \n    403\n    ```\n\nresult\n\n* outcome: [`failed`](https://www.w3.org/TR/EARL10-Schema/#failed)\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 3b925cdf-89fe-4f51-b41f-26df23f58e0c](https://socialweb.coop/activitypub/behaviors/3b925cdf-89fe-4f51-b41f-26df23f58e0c) - Servers MUST return a 201 Created HTTP code\n    * Required for Conformance to [ActivityPub][activitypub]\n    * Outcome Mapping\n        * when test target `response` has outcome `passed`, further testing is needed to determine requirement satisfaction\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## Issues\n\n* [x] host an example outbox that can be used as the `passed` test case for this\n    * it should always respond with http response 201\n    * 2023-12-22T18:20:55.076Z: done: <https://socialweb.coop/activitypub/testing/utilities/response?status=201>\n\n## Change Log\n\n* 2024-01-04T00:25:23.095Z - add support for read-only outboxes - i.e. a response with status 405 MUST result in an `inapplicable` test outcome.\n* 2024-01-07T21:39:50.838Z - add support for http responses with status 401, 403, 404\n",
  "name": "Outbox Servers handling activity submissions MUST return a 201 created HTTP status code",
  "passedCases": [
    {
      "name": "1. simple passed case",
      "input": {
        "outbox": "https://socialweb.coop/activitypub/testing/utilities/response?status=201",
        "submission": "{\"type\":\"Create\"}"
      },
      "result": {
        "outcome": "passed"
      }
    }
  ],
  "slug": "outbox-post-servers-must-return-a-201-created-http-code",
  "uuid": "723afcbb-118d-433e-8ab4-560ffca93582",
  "isPartOf": [
    "https://socialweb.coop/activitypub/test-cases/"
  ],
  "requirementReference": [
    {
      "id": "urn:uuid:3b925cdf-89fe-4f51-b41f-26df23f58e0c",
      "url": "https://socialweb.coop/activitypub/behaviors/3b925cdf-89fe-4f51-b41f-26df23f58e0c/"
    }
  ]
}