@cryptotaxi247 / kubo / commits / 90c2b5055

fix(test): mock GitHub API in TestUpdate (#11300)

These tests verify behavior that is independent of who serves the release JSON: TestUpdate exercises the `ipfs update` command tree, and TestUpdateWhileDaemonRuns checks that read-only subcommands still work while the daemon holds the repo lock. They hit the real GitHub Releases API only by accident, which makes them flake on rate limits, transient 5xx, or release-asset upload races. A flake panics the harness and takes every parallel test in test/cli down with it. Replace the network call with a shared `httptest.Server` helper (`newMockGitHubReleases`) and point the spawned binary at it via `TEST_KUBO_UPDATE_GITHUB_URL`, the same hook `TestUpdateInstall` already uses. The mock returns one stable release with a matching binary asset and follows the convention used by real kubo releases: `kubo_<tag>_<os>-<arch>.<ext>`, where ext is `zip` on Windows and `tar.gz` elsewhere. This must match `assetNameForPlatformTag` in `core/commands/update_github.go`, otherwise `findReleaseAsset` reports "no release found with a binary for <os>/<arch>". No network, no token, no flake. Local runtime drops from ~70s to under 1s.

Marcin Rataj committed Apr 27, 2026 at 21:44 UTC 90c2b50552c14a8385fabaa8c1a7f79ef03dc350
1 file changed +54 -5
test/cli/update_test.go
+54 -5
@@ -21,16 +21,20 @@ import (
21 "github.com/stretchr/testify/require"
22 )
23
24 -// TestUpdate exercises the built-in "ipfs update" command tree against
25 -// the real GitHub Releases API. Network access is required.
24 +// TestUpdate exercises the built-in "ipfs update" command tree.
25 //
27 -// The node is created without Init or daemon, so install/revert error
28 -// paths that don't depend on a running daemon can be tested.
26 +// A local httptest server replaces GitHub Releases so the test does not
27 +// depend on network reachability or rate limits. The node is created
28 +// without Init or daemon, so install/revert error paths that don't
29 +// depend on a running daemon can be tested.
30 func TestUpdate(t *testing.T) {
31 t.Parallel()
32 h := harness.NewT(t)
33 node := h.NewNode()
34
35 + srv := newMockGitHubReleases(t)
36 + node.Runner.Env["TEST_KUBO_UPDATE_GITHUB_URL"] = srv.URL
37 +
38 t.Run("help text describes the command", func(t *testing.T) {
39 t.Parallel()
40 res := node.IPFS("update", "--help")
@@ -131,9 +135,17 @@ func TestUpdate(t *testing.T) {
135 // (check, versions) work while the IPFS daemon holds the repo lock.
136 // These commands only query the GitHub API and never touch the repo,
137 // so they must succeed regardless of daemon state.
138 +//
139 +// A local httptest server replaces GitHub so the test does not depend
140 +// on network reachability or GitHub rate limits. The locking behavior
141 +// under test is independent of which endpoint serves the release JSON.
142 func TestUpdateWhileDaemonRuns(t *testing.T) {
143 t.Parallel()
136 - node := harness.NewT(t).NewNode().Init().StartDaemon()
144 +
145 + srv := newMockGitHubReleases(t)
146 + node := harness.NewT(t).NewNode()
147 + node.Runner.Env["TEST_KUBO_UPDATE_GITHUB_URL"] = srv.URL
148 + node.Init().StartDaemon()
149 defer node.StopDaemon()
150
151 t.Run("check succeeds with daemon running", func(t *testing.T) {
@@ -463,6 +475,43 @@ func TestUpdateClean(t *testing.T) {
475
476 // --- test helpers ---
477
478 +// newMockGitHubReleases returns an httptest server that mimics the GitHub
479 +// Releases listing API with a single stable release at v0.99.0 carrying
480 +// a binary asset for the current GOOS/GOARCH. This is enough to drive
481 +// "ipfs update check" and "ipfs update versions" without touching the
482 +// real api.github.com.
483 +//
484 +// The asset name follows the same convention used by real kubo releases
485 +// (see https://github.com/ipfs/kubo/releases): kubo_<tag>_<os>-<arch>.<ext>,
486 +// where ext is "zip" on Windows and "tar.gz" everywhere else. This must
487 +// match what assetNameForPlatformTag produces in core/commands/update_github.go,
488 +// otherwise findReleaseAsset cannot locate the binary and reports
489 +// "no release found with a binary for <os>/<arch>".
490 +func newMockGitHubReleases(t *testing.T) *httptest.Server {
491 + t.Helper()
492 + ext := "tar.gz"
493 + if runtime.GOOS == "windows" {
494 + ext = "zip"
495 + }
496 + asset := fmt.Sprintf("kubo_v0.99.0_%s-%s.%s", runtime.GOOS, runtime.GOARCH, ext)
497 + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
498 + // Both `update check` and `update versions` call
499 + // GET /releases?per_page=N. One stable release with a matching
500 + // platform asset exercises both paths.
501 + rels := []map[string]any{{
502 + "tag_name": "v0.99.0",
503 + "prerelease": false,
504 + "assets": []map[string]any{{
505 + "name": asset,
506 + }},
507 + }}
508 + w.Header().Set("Content-Type", "application/json")
509 + _ = json.NewEncoder(w).Encode(rels)
510 + }))
511 + t.Cleanup(srv.Close)
512 + return srv
513 +}
514 +
515 // copyBuiltBinary copies the built ipfs binary (cmd/ipfs/ipfs) to dst.
516 // It locates the project root the same way the test harness does.
517 func copyBuiltBinary(t *testing.T, dst string) {