{"id":13582229,"url":"https://github.com/nytimes/httptest","last_synced_at":"2026-01-11T21:55:24.324Z","repository":{"id":44752853,"uuid":"176756401","full_name":"nytimes/httptest","owner":"nytimes","description":"A simple concurrent HTTP testing tool","archived":false,"fork":false,"pushed_at":"2025-08-11T19:55:01.000Z","size":1344,"stargazers_count":51,"open_issues_count":4,"forks_count":13,"subscribers_count":35,"default_branch":"main","last_synced_at":"2025-10-31T01:31:44.753Z","etag":null,"topics":["cicd","container","drone-ci","github-actions","golang","http","testing"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nytimes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-03-20T14:52:15.000Z","updated_at":"2025-09-21T11:07:25.000Z","dependencies_parsed_at":"2023-11-13T16:29:56.067Z","dependency_job_id":"a815befd-f734-4cb8-bb1f-4f24f84e9210","html_url":"https://github.com/nytimes/httptest","commit_stats":{"total_commits":41,"total_committers":10,"mean_commits":4.1,"dds":0.6341463414634146,"last_synced_commit":"67e488de4b00073498c0988d691942891f25c2d4"},"previous_names":["blupig/httptest"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/nytimes/httptest","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nytimes%2Fhttptest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nytimes%2Fhttptest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nytimes%2Fhttptest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nytimes%2Fhttptest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nytimes","download_url":"https://codeload.github.com/nytimes/httptest/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nytimes%2Fhttptest/sbom","scorecard":{"id":700040,"data":{"date":"2024-07-15","repo":{"name":"github.com/nytimes/httptest","commit":"4947a6ca97376612da6434d7cae419333e66c2f8"},"scorecard":{"version":"v5.0.0-rc2-108-ga74ffc30","commit":"a74ffc30cf51b574bce1fea2e7ef502fd4a17fd0"},"score":4.5,"checks":[{"name":"Code-Review","score":9,"reason":"Found 21/23 approved changesets -- score normalized to 9","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/a74ffc30cf51b574bce1fea2e7ef502fd4a17fd0/docs/checks.md#code-review"}},{"name":"Maintained","score":1,"reason":"2 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/a74ffc30cf51b574bce1fea2e7ef502fd4a17fd0/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/a74ffc30cf51b574bce1fea2e7ef502fd4a17fd0/docs/checks.md#cii-best-practices"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/a74ffc30cf51b574bce1fea2e7ef502fd4a17fd0/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/a74ffc30cf51b574bce1fea2e7ef502fd4a17fd0/docs/checks.md#signed-releases"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/a74ffc30cf51b574bce1fea2e7ef502fd4a17fd0/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/a74ffc30cf51b574bce1fea2e7ef502fd4a17fd0/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/a74ffc30cf51b574bce1fea2e7ef502fd4a17fd0/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/a74ffc30cf51b574bce1fea2e7ef502fd4a17fd0/docs/checks.md#binary-artifacts"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/a74ffc30cf51b574bce1fea2e7ef502fd4a17fd0/docs/checks.md#branch-protection"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/a74ffc30cf51b574bce1fea2e7ef502fd4a17fd0/docs/checks.md#fuzzing"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/a74ffc30cf51b574bce1fea2e7ef502fd4a17fd0/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/a74ffc30cf51b574bce1fea2e7ef502fd4a17fd0/docs/checks.md#vulnerabilities"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: containerImage not pinned by hash: Dockerfile:2: pin your Docker image by updating golang:alpine to golang:alpine@sha256:8c9183f715b0b4eca05b8b3dbf59766aaedb41ec07477b132ee2891ac0110a07","Warn: containerImage not pinned by hash: Dockerfile:22: pin your Docker image by updating alpine to alpine@sha256:b89d9c93e9ed3597455c90a0b88a8bbb5cb7188438f70953fede212a0c4394e0","Info:   0 out of   2 containerImage dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/a74ffc30cf51b574bce1fea2e7ef502fd4a17fd0/docs/checks.md#pinned-dependencies"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/a74ffc30cf51b574bce1fea2e7ef502fd4a17fd0/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-22T04:59:09.630Z","repository_id":44752853,"created_at":"2025-08-22T04:59:09.630Z","updated_at":"2025-08-22T04:59:09.630Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28324462,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-11T18:42:50.174Z","status":"ssl_error","status_checked_at":"2026-01-11T18:39:13.842Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cicd","container","drone-ci","github-actions","golang","http","testing"],"created_at":"2024-08-01T15:02:30.597Z","updated_at":"2026-01-11T21:55:24.313Z","avatar_url":"https://github.com/nytimes.png","language":"Go","readme":"# httptest\n\n[![Build](https://github.com/nytimes/httptest/actions/workflows/pull-request.yml/badge.svg)](https://github.com/nytimes/httptest/actions/workflows/pull-request.yml)[![Release](https://github.com/nytimes/httptest/actions/workflows/tagged-release.yml/badge.svg)](https://github.com/nytimes/httptest/actions/workflows/tagged-release.yml)\n\n\nA simple concurrent HTTP testing tool\n\n## Usage\n\n### Write a simple test\n\nCreate a file `tests.yml` with the following content:\n\n```yml\ntests:\n  - description: 'root'  # Description, will be printed with test results\n    request:             # Request to send\n      path: '/'          # Path\n    response:            # Expected response\n      statusCodes: [200] # List of expected response status codes\n```\n\n### Run tests locally\n\nThis program is distributed as a Docker image. To run a container locally (under a folder contains tests related yml file):\n\n```bash\ndocker run --rm \\\n    -v $(pwd)/httpbin.yml:/tests/tests.yml \\\n    -e \"TEST_HOST=example.com\" \\\n    nytimes/httptest\n```\n\nYou should see an output similar to this:\n\n```shell\npassed:  tests.yml | root | /\n\n1 passed\n0 failed\n0 skipped\n```\n\nTip: If your test cases have conditions on environment variables (see `conditions` in [full example](#full-test-example)), remember to include `-e \"\u003cENV_VAR\u003e=\u003cvalue\u003e\"`. e.g.\n\n```shell\ndocker run --rm \\\n    -v $(pwd)/route-abc-tests.yml:/tests/tests.yml \\\n    -e \"TEST_HOST=www.stg.example.com\" \\\n    -e \"TEST_ENV=stg\" \\\n    nytimes/httptest\n```\n\nBy default, if your current working directory is `$(pwd)/tests`,  the program will parse all files in `$(pwd)/tests` recursively.\nThis can be changed using an environment variable.\n\n### Run tests in a CI/CD pipeline\n\nThis image can be used in any CI/CD system that supports Docker containers.\n\nExamples\n\n- Drone\n\n  ```yml\n  pipeline:\n    tests:\n      image: nytimes/httptest\n      pull: true\n      environment:\n        TEST_HOST: 'example.com'\n  ```\n\n- GitHub Actions\n\n  ```hcl\n  action \"httptest\" {\n    uses = \"docker://nytimes/httptest\"\n    env = {\n      TEST_HOST = \"example.com\"\n    }\n  }\n  ```\n\n### Configurations\n\nA few global configurations (applied to all tests) can be specified by\nenvironment variables:\n\n- `TEST_DIRECTORY`: Local directory that contains the test definition YML or YAML\n  files. Default: `tests`\n\n- `TEST_HOST`: Host to test. Can be overridden by `request.host` of individual\n  test definitions. If `TEST_HOST` and `request.host` are both not set, test\n  will fail.\n\n- `TEST_CONCURRENCY`: Maximum number of concurrent requests at a time.\n  Default: `2`.\n\n- `TEST_DNS_OVERRIDE`: Override the IP address for `TEST_HOST`. Does not work\n  for `request.host` specified in YML.\n\n- `TEST_PRINT_FAILED_ONLY`: Only print failed tests. Valid values: `false` or\n  `true`. Default: `false`.\n\n- `TEST_VERBOSITY`: Increase logging output for tests. Default: `0`.\n\n- `ENABLE_RETRIES`: Enables retrying requests if a test does not succeed.\n   Defaults: `false`.\n\n- `DEFAULT_RETRY_COUNT`: Specify the number of times to retry a test request if\n   the initial request does not succeed. Only applied if `ENABLE_RETRIES` is set\n   to `true` Defaults: `2`.\n\n### Environment variable substitution\n\nThis program supports variable substitution from environment variables in YML\nfiles. This is useful for handling secrets or dynamic values.\nVisit [here](https://github.com/drone/envsubst) for\nsupported functions.\n\nExample:\n\n```yml\ntests:\n  - description: 'get current user'\n    request:\n      path: '/user'\n      headers:\n        authorization: 'token ${SECRET_AUTH_TOKEN}'\n    response:\n      statusCodes: [200]\n```\n\n### Dynamic headers\n\nIn addition to defining header values statically (i.e. when a test is written), there is also support for determining the value of a header at runtime. This feature is useful if the value of a header must be determined when a test runs (for example, a header that must contain a recent timestamp). Such headers are specified as `dynamicHeaders` in the YAML, and each must be accompanied by the name of a function to call and a list of arguments for that function (if required). For example:\n\n```yaml\ndynamicHeaders:\n  - name: x-test-signature\n    function: myFunction\n    args:\n      - 'arg1'\n      - 'arg2'\n      - 'arg3'\n```\n\nThe arguments can be literal strings, environment variables, or values of previously set headers (see examples below). The function definitions are in the file `dynamic.go` and can be added to as necessary. Each function must implement the following function interface:\n\n```go\ntype resolveHeader func(existingHeaders map[string]string, args []string) (string, error)\n```\n\nThese are the functions that are currently supported:\n\n#### now\n\nReturns the number of seconds since the Unix epoch\n\nArgs: none\n\n#### signStringRS256PKCS8\n\nConstructs a string from args (delimited by newlines), signs it with the (possibly passphrase-encrypted) PKCS #8 private key, and returns the signature in base64\n\nArgs:\n\n- key\n- passphrase (can be empty string if key is not encrypted)\n- string(s)... (any amount of strings, from previously set headers or literal values)\n\n#### postFormURLEncoded\n\nSends an HTTP POST request to the given URL (with Content-Type `application/x-www-form-urlencoded`), returns either the whole response body or a specific JSON element, and treats the remaining arguments as data\n\nArgs:\n\n- url (the URL to send the request to)\n- element (see section on \"JSON Query Syntax\" below)\n- string(s)... (any amount of strings, from previously set headers or literal values, which will be concatenated and delimited with '\u0026' for the request body)\n\n##### JSON Query Syntax\n\nThe element to return from the JSON response body is specified by a dot-delimited string representing the path to the element. Each part of this string is a single step in that path, and array access is handled by providing a zero-based index. If the element selected has a primitive value, that value is returned. If the element selected is a JSON object or array, that object/array is returned in compact form (insignificant whitespace removed). To return the entire JSON response body, use an empty string.\n\n**Examples:**\n**Primitive data**\nResponse body:\n\n```json\n{\n    \"nested\": {\n        \"object\": {\n            \"data\": 123\n        }\n    }\n}\n```\n\nQuery: 'nested.object.data'\nResult: 123\n\n**Object**\nResponse body:\n\n```json\n{\n    \"nested\": {\n        \"object\": {\n            \"data\": 123\n        }\n    }\n}\n```\n\nQuery: 'nested.object'\nResult: {\"data\":123}\n\n**Array**\nResponse body:\n\n```json\n{\n    \"nested\": {\n        \"array\": [\n            \"abc\",\n            \"def\",\n            \"ghi\"\n        ]\n    }\n}\n```\n\nQuery: 'nested.array'\nResult: [\"abc\",\"def\",\"ghi\"]\n\n**Array element**\nResponse body:\n\n```json\n{\n    \"nested\": {\n        \"array\": [\n            \"abc\",\n            \"def\",\n            \"ghi\"\n        ]\n    }\n}\n```\n\nQuery: 'nested.array.1'\nResult: def\n\n**Whole response**\nResponse body:\n\n```plaintext\nJust a normal, non-JSON response\n```\n\nQuery: ''\nResult: Just a normal, non-JSON response\n\n#### concat\n\nConcatenates the arguments into a single string\n\nArgs:\n\n- string(s)... (any amount of strings, from previously set headers or literal values, which will be concatenated)\n\n### Full test example\n\nRequired fields for each test:\n\n- `description`\n- `request.path`\n\nAll other fields are optional. All matchings are case insensitive.\n\n```yml\ntests:\n  - description: 'root'                        # Description, will be printed with test results. Required\n    conditions:                                # Specify conditions. Test only runs when all conditions are met\n      env:                                     # Matches an environment variable\n        TEST_ENV: '^(dev|stg)$'                # Environment variable name : regular expression\n    skipCertVerification: false                # Set true to skip verification of server TLS certificate (insecure and not recommended)\n\n    request:                                   # Request to send\n      scheme: 'https'                          # URL scheme. Only http and https are supported. Default: https\n      host: 'example.com'                      # Host to test against (this overrides TEST_HOST for this specific test)\n      method: 'POST'                           # HTTP method. Default: GET\n      path: '/'                                # Path to hit. Required\n      headers:                                 # Headers\n        x-test-header-0: 'abc'\n        x-test: '${REQ_TEST}'                  # Environment variable substitution\n      dynamicHeaders:                          # Headers whose values are determined at runtime (see \"Dynamic Headers\" section above)\n        - name: x-test-timestamp               # Name of the header to set\n          function: now                        # Calling a no-arg function to get the header value\n        - name: x-test-signature\n          function: signStringRS256PKCS8       # Function called to determine the header value\n          args:                                # List of arguments for the function (can be omitted for functions that don't take arguments)\n            - '${TEST_KEY}'                    # Can use environment variable substitution\n            - '${TEST_PASS}'\n            - x-test-timestamp                 # Can use values of previously set headers\n            - '/svc/login'                     # Can use literals\n            - x-test-header-0\n            - x-test\n        - name: x-test-token\n          function: postFormURLEncoded         # Function called to retrieve the header value from an API\n          args:\n            - '${TOKEN_URL}'                   # The URL to POST to\n            - 'results.7.token.id_token'       # The dot-delimited string representing an element to return from the JSON response body; use integers for array elements or '' for the whole response body\n            - 'client_secret=${CLIENT_SECRET}' # Can use literals, environment variables, or a combination\n            - x-test-timestamp                 # Can use values of previously set headers\n            - 'client_id=123'                  # These arguments will be combined to send 'client_secret=${CLIENT_SECRET}\u0026x-test-timestamp\u0026client_id=123' in the body of the request\n        - name: authorization\n          function: concat                     # Function that concatenates strings, either literals or values from previously set headers\n          args:\n            - 'Bearer '\n            - x-test-token\n      body: ''                                 # Request body. Processed as string\n\n    response:                                  # Expected response\n      statusCodes: [201]                       # List of expected response status codes\n      headers:                                 # Expected response headers\n        patterns:                              # Match response header patterns\n          server: '^ECS$'                      # Header name : regular expression\n          cache-control: '.+'\n        notPresent:                            # Specify headers not expected to exist.\n          - 'set-cookie'                       # These are not regular expressions\n          - 'x-frame-options'\n        notMatching:\n          set-cookie: ^.*abc.*$                # Specify headers expected to exist but NOT match the given regex\n      body:                                    # Response body\n        patterns:                              # Response body has to match all patterns in this list in order to pass test\n          - 'charset=\"utf-8\"'                  # Regular expressions\n          - 'Example Domain'\n\n  - description: 'sign up page'                # Second test\n    request:\n      path: '/signup'\n    response:\n      statusCodes: [200]\n\n  - description: 'HTTPS GET - test if present not matching'\n    request:\n      path: '/get'\n    conditions:\n      env:\n        TEST_ENV: dev\n    response:\n      statusCodes: [200]\n      headers:\n        ifPresentNotMatching:\n          Content-Type: ^notreal/fake$         # If Content-Type does not exist or does not match this pattern the test passes, else it fails\n\n  - description: 'HTTPS GET - test if present not matching multiple possible matches'\n    request:\n      path: '/get'\n    conditions:\n      env:\n        TEST_ENV: dev\n    response:\n      headers:\n        notMatching:\n          Server: ^(.*(nginx|42)).*            # If Server does not match either portion of the pattern (nginx or 42) the test passes, else it fails\n      statusCodes:\n        - 200\n```\n\n## Development\n\n### Run locally\n\nDownload package\n\n```shell\ngo get -d -u github.com/nytimes/httptest\n```\n\nBuild and run\n\n```bash\n# In repo root directory\nmake run\n```\n\nThis will run the tests defined in `example-tests` directory.\n","funding_links":[],"categories":["Go"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnytimes%2Fhttptest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnytimes%2Fhttptest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnytimes%2Fhttptest/lists"}