{"id":37173166,"url":"https://github.com/jefflinse/melatonin","last_synced_at":"2026-01-14T20:14:35.273Z","repository":{"id":57636253,"uuid":"419590662","full_name":"jefflinse/melatonin","owner":"jefflinse","description":"Hassle-free REST API testing for Go","archived":false,"fork":false,"pushed_at":"2023-04-07T01:19:50.000Z","size":6114,"stargazers_count":15,"open_issues_count":2,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-06-20T05:12:37.110Z","etag":null,"topics":["api","e2e","framework","go","golang","http","integration","library","rest","test","testing"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jefflinse.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":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-10-21T05:17:08.000Z","updated_at":"2023-12-21T17:31:30.000Z","dependencies_parsed_at":"2024-06-20T04:25:13.099Z","dependency_job_id":"03039699-736e-4a91-a04d-0139c85a32e4","html_url":"https://github.com/jefflinse/melatonin","commit_stats":null,"previous_names":["jefflinse/go-itest"],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/jefflinse/melatonin","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jefflinse%2Fmelatonin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jefflinse%2Fmelatonin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jefflinse%2Fmelatonin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jefflinse%2Fmelatonin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jefflinse","download_url":"https://codeload.github.com/jefflinse/melatonin/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jefflinse%2Fmelatonin/sbom","scorecard":{"id":514179,"data":{"date":"2025-08-11","repo":{"name":"github.com/jefflinse/melatonin","commit":"3ffba4da704d2b4546810688beccf5a213d44157"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.7,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":0,"reason":"Found 0/29 approved changesets -- score normalized to 0","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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/ci.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:10: update your workflow using https://app.stepsecurity.io/secureworkflow/jefflinse/melatonin/ci.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/jefflinse/melatonin/ci.yml/main?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction 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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":9,"reason":"1 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GO-2022-0603 / GHSA-hp87-p4gw-j4gq"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 1 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-20T01:22:13.426Z","repository_id":57636253,"created_at":"2025-08-20T01:22:13.426Z","updated_at":"2025-08-20T01:22:13.426Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28434421,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T18:57:19.464Z","status":"ssl_error","status_checked_at":"2026-01-14T18:52:48.501Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: 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":["api","e2e","framework","go","golang","http","integration","library","rest","test","testing"],"created_at":"2026-01-14T20:14:34.636Z","updated_at":"2026-01-14T20:14:35.261Z","avatar_url":"https://github.com/jefflinse.png","language":"Go","readme":"# melatonin\n\n[![Build Status](https://github.com/jefflinse/melatonin/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/jefflinse/melatonin/actions/workflows/ci.yml)\n![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/jefflinse/melatonin)\n![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/jefflinse/melatonin)\n[![Go Report Card](https://goreportcard.com/badge/github.com/jefflinse/melatonin)](https://goreportcard.com/report/github.com/jefflinse/melatonin)\n[![Go Reference](https://pkg.go.dev/badge/github.com/jefflinse/melatonin/mt.svg)](https://pkg.go.dev/github.com/jefflinse/melatonin/mt)\n![License](https://img.shields.io/github/license/jefflinse/melatonin)\n\n**Melatonin is a flexible API testing library for Go.**\n\nIt provides syntactic sugar for writing table-based API tests at any level of testing.\n\nUse it to write:\n\n- **Native Go tests** that test your `http.Handler`s routes directly. Mock out your dependencies and test your handler logic in isolation. [More »](#native-go-tests)\n\n- **Component tests** that target any running service. Spin up your service with stubbed dependencies and test the API surface. [More »](#component-tests)\n\n- **E2E test suites** that target APIs across multiple running services. Perform acceptance tests against your entire system. [More »](#e2e-test-suites)\n\nSee the full [user guide](./USERGUIDE.md) and the [API documentation](https://pkg.go.dev/github.com/jefflinse/melatonin/mt) for more information.\n\n## Installation\n\n    go get github.com/jefflinse/melatonin/mt\n\n## Usage\n\n### Native Go tests\n\nA `HandlerContext` wraps a Go `http.Handler` (such as a mux/router) and provides methods for defining tests that run against the it. This is useful, for example, for testing the logic of your mux and individual handlers in isolation with mocked dependencies.\n\n```go\nfunc TestMyAPI(t *testing.T) {\n    // myHandler can be anything implementing http.Handler\n    myAPI := mt.NewHandlerContext(myHandler)\n    mt.RunTestsT(t, []mt.TestCase{\n\n        myAPI.GET(\"/resource\", \"Fetch a resource successfully\").\n            ExpectStatus(200).\n            ExpectBody(\"Hello, world!\"),\n    })\n}\n```\n\nRun these tests with `go test`, just like any other Go tests.\n\n### Component tests\n\nA `URLContext` wraps a base URL and provides methods for defining tests that run against the API at that URL. This is useful for blackbox testing the API surface of a service, either with real or stubbed external dependencies.\n\n```go\nfunc main() {\n    // myURL can be any valid base URL parsable by url.Parse()\n    myAPI := mt.NewURLContext(myURL)\n    results := mt.RunTests([]mt.TestCase{\n\n        myAPI.GET(\"/resource\", \"Fetch a resource successfully\").\n            ExpectStatus(200).\n            ExpectBody(\"Hello, world!\"),\n    })\n\n    mt.PrintResults(results)\n}\n```\n\n### E2E test suites\n\nSimilar to component tests, it's easy to create multiple test contexts (i.e. one per service) and define test suites that execute high-level user stories across your entire system.\n\n```go\nfunc main() {\n    authAPI := mt.NewURLContext(\"https://myapi.example.com/auth\")\n    usersAPI := mt.NewURLContext(\"https://myapi.example.com/users\")\n\n    var uid, token string\n\n    results := mt.RunTests([]mt.TestCase{\n\n        authAPI.POST(\"/login\", \"Can log in\").\n            WithBody(json.Object{\n                \"username\": \"someone@example.com\",\n                \"password\": \"password\",\n            }).\n            ExpectStatus(200).\n            ExpectBody(json.Object{\n                \"uid\":           bind.String(\u0026uid)\n                \"access_token\":  bind.String(\u0026token),\n                \"refresh_token\": expect.String(),\n            }),\n\n        usersAPI.GET(\"/:id/profile}\", \"Can fetch own profile\").\n            WithHeader(\"Authorization\", \"Bearer \" + \u0026token).\n            WithPathParam(\"id\", \u0026uid).\n            ExpectStatus(200).\n            ExpectBody(\"Hello, world!\"),\n    })\n\n    mt.PrintResults(results)\n}\n```\n\nMore on data binding and expectations can be found in the [user guide](./USERGUIDE.md).\n\n## Examples\n\nSee the [examples](examples) directory for full, runnable examples.\n\n### Test a Go HTTP handler\n\n```go\nmyAPI := mt.NewHandlerContext(http.NewServeMux())\nmt.RunTests(...)\n```\n\n### Test a base URL endpoint\n\n```go\nmyAPI := mt.NewURLContext(\"http://example.com\")\nmt.RunTests(...)\n```\n\n### Define tests\n\n```go\nmyAPI := mt.NewURLContext(\"http://example.com\")\ntests := []mt.TestCase{\n\n    myAPI.GET(\"/resource\").\n       ExpectStatus(200).\n       ExpectBody(String(\"Hello, World!\")),\n    \n    myAPI.POST(\"/resource\").\n       WithBody(Object{\n         \"name\": \"Burt Macklin\",\n         \"age\":  32,\n       }).\n       ExpectStatus(201),\n    \n    myAPI.DELETE(\"/resource/42\").\n       ExpectStatus(204),\n}\n```\n\n### Use a custom HTTP client for requests\n\n```go\nclient := \u0026http.Client{}\nmyAPI := mt.NewURLContext(\"http://example.com\").WithHTTPClient(client)\n```\n\n### Use a custom timeout for all tests\n\n```go\ntimeout := time.Duration(5 * time.Second)\nmyAPI := mt.NewURLContext(\"http://example.com\").WithTimeout(timeout)\n```\n\n### Specify a timeout for a specific test\n\n```go\nmyAPI.GET(\"/resource\").\n    WithTimeout(5 * time.Second).\n    ExpectStatus(200).\n```\n\n### Specify query parameters for a test\n\nInline:\n\n```go\nmyAPI.GET(\"/resource?first=foo\u0026second=bar\")\n```\n\nIndividually:\n\n```go\nmyAPI.GET(\"/resource\").\n    WithQueryParam(\"first\", \"foo\").\n    WithQueryParam(\"second\", \"bar\")\n```\n\nAll At Once:\n\n```go\nmyAPI.GET(\"/resource\").\n    WithQueryParams(url.Values{\n        \"first\": []string{\"foo\"},\n        \"second\": []string{\"bar\"},\n    })\n```\n\n### Allow or disallow further tests to run after a failure\n\n```go\nrunner := mt.NewURLContext(\"http://example.com\").WithContinueOnFailure(true)\n```\n\n### Create a test case with a custom HTTP request\n\n```go\nreq, err := http.NewRequest(\"GET\", \"http://example.com/resource\", nil)\nmyAPI.DO(req).\n    ExpectStatus(200)\n```\n\n### Expect exact headers and JSON body content\n\nAny unexpected headers or JSON keys or values present in the response will cause the test case to fail.\n\n```go\nmyAPI.GET(\"/resource\").\n    ExpectExactHeaders(http.Header{\n        \"Content-Type\": []string{\"application/json\"},\n    }).\n    ExpectExactBody(mt.Object{\n        \"foo\": \"bar\",\n    })\n```\n\n### Load expectations for a test case from a golden file\n\n```go\nmyAPI.GET(\"/resource\").\n    ExpectGolden(\"path/to/file.golden\")\n```\n\nGolden files keep your test definitions short and concise by storing expectations in a file. See the [golden file format specification](./golden/README.md).\n\n## Planned Features\n\n- Output test results in different formats (e.g. JSON, XML, YAML)\n- Generate test cases from an OpenAPI specification\n- Support for testing GraphQL APIs\n- Support for testing gRPC APIs\n- Support for testing websockets\n\nSee the full [V1 milestone](https://github.com/jefflinse/melatonin/milestone/1) for more.\n\n## Contributing\n\nPlease [open an issue](https://github.com/jefflinse/melatonin/issues) if you find a bug or have a feature request.\n\n## License\n\nMIT License (MIT) - see [`LICENSE`](./LICENSE) for details.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjefflinse%2Fmelatonin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjefflinse%2Fmelatonin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjefflinse%2Fmelatonin/lists"}