package depot import ( "bytes" "fmt" "net/http" "net/http/httptest" "os" "path/filepath" "testing" ) func testGetenv(vals map[string]string) func(string) string { return func(k string) string { return vals[k] } } func TestRunUsage(t *testing.T) { t.Run("no args", func(t *testing.T) { var out, errb bytes.Buffer code := Run(nil, &out, &errb, testGetenv(nil)) if code != 64 { t.Fatalf("expected 64, got %d (stderr=%s)", code, errb.String()) } }) t.Run("unknown subcommand", func(t *testing.T) { var out, errb bytes.Buffer code := Run([]string{"bogus"}, &out, &errb, testGetenv(nil)) if code != 64 { t.Fatalf("expected 64, got %d (stderr=%s)", code, errb.String()) } }) t.Run("status --target local without DEPOT_DIR", func(t *testing.T) { dir := t.TempDir() writeManifest(t, dir, shaOf("blob-a"), 5) var out, errb bytes.Buffer code := Run([]string{"status", "--manifests", dir, "--target", "local"}, &out, &errb, testGetenv(nil)) if code != 64 { t.Fatalf("expected 64, got %d (stderr=%s)", code, errb.String()) } if !bytesContains(errb.String(), "DEPOT_DIR") { t.Fatalf("expected stderr to mention DEPOT_DIR, got %s", errb.String()) } }) t.Run("push --target cdn rejected", func(t *testing.T) { dir := t.TempDir() var out, errb bytes.Buffer code := Run([]string{"push", "--manifests", dir, "--source", dir, "--target", "cdn"}, &out, &errb, testGetenv(nil)) if code != 64 { t.Fatalf("expected 64, got %d (stderr=%s)", code, errb.String()) } }) } func TestGetInvalidSHA(t *testing.T) { var out, errb bytes.Buffer dir := t.TempDir() code := Run([]string{"get", "not-a-sha", dir + "/dest", "--target", "local"}, &out, &errb, testGetenv(map[string]string{"DEPOT_DIR": dir})) if code != 64 { t.Fatalf("expected 64, got %d (stderr=%s)", code, errb.String()) } } func TestNoKeyStatusBunnyFailsClosedNoStdin(t *testing.T) { dir := t.TempDir() writeManifest(t, dir, shaOf("blob-a"), 5) var out, errb bytes.Buffer code := Run([]string{"status", "--manifests", dir, "--target", "bunny"}, &out, &errb, testGetenv(nil)) if code != 70 { t.Fatalf("expected 70, got %d (stdout=%s stderr=%s)", code, out.String(), errb.String()) } if !bytesContains(errb.String(), "BUNNY_STORAGE_HOST") { t.Fatalf("expected stderr to mention BUNNY_STORAGE_HOST, got %s", errb.String()) } } func TestStatusExitCodes(t *testing.T) { t.Run("drift", func(t *testing.T) { f := newFakeBunny() srv := httptest.NewServer(f.handler()) defer srv.Close() sha := shaOf("missing-blob") dir := t.TempDir() writeManifest(t, dir, sha, 5) var out, errb bytes.Buffer code := Run([]string{"status", "--manifests", dir, "--target", "bunny"}, &out, &errb, testGetenv(map[string]string{ "BUNNY_STORAGE_HOST": hostPort(srv), "BUNNY_STORAGE_READ_PASSWORD": "readkey", })) if code != 1 { t.Fatalf("expected 1, got %d (stdout=%s stderr=%s)", code, out.String(), errb.String()) } if !bytesContains(out.String(), "missing="+"1") { t.Fatalf("expected missing=1 in output, got %s", out.String()) } if !bytesContains(out.String(), "missing "+sha) { t.Fatalf("expected missing line for sha, got %s", out.String()) } }) t.Run("unconfirmed-only", func(t *testing.T) { stubProbeSleep(t) f := newFakeBunny() sha := shaOf("flaky-blob") f.statusFor[sha] = 428 srv := httptest.NewServer(f.handler()) defer srv.Close() dir := t.TempDir() writeManifest(t, dir, sha, 5) var out, errb bytes.Buffer code := Run([]string{"status", "--manifests", dir, "--target", "bunny"}, &out, &errb, testGetenv(map[string]string{ "BUNNY_STORAGE_HOST": hostPort(srv), "BUNNY_STORAGE_READ_PASSWORD": "readkey", "DEPOT_CONFIRM_RETRIES": "1", })) if code != 2 { t.Fatalf("expected 2, got %d (stdout=%s stderr=%s)", code, out.String(), errb.String()) } if !bytesContains(out.String(), "unconfirmed="+"1") { t.Fatalf("expected unconfirmed=1 in output, got %s", out.String()) } }) } func TestPushTransportErrorCountsFailed(t *testing.T) { // Probes 404 (blob absent), PUTs 500 (transport-level upload failure). srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodPut { w.WriteHeader(500) return } w.WriteHeader(404) })) defer srv.Close() sha := shaOf("push-fail-blob") manifestsDir := t.TempDir() writeManifest(t, manifestsDir, sha, 14) sourceDir := t.TempDir() blobPath := filepath.Join(sourceDir, BlobKey(sha)) if err := os.MkdirAll(filepath.Dir(blobPath), 0755); err != nil { t.Fatal(err) } if err := os.WriteFile(blobPath, []byte("push-fail-blob"), 0644); err != nil { t.Fatal(err) } var out, errb bytes.Buffer code := Run([]string{"push", "--manifests", manifestsDir, "--source", sourceDir, "--target", "bunny"}, &out, &errb, testGetenv(map[string]string{ "BUNNY_STORAGE_HOST": hostPort(srv), "BUNNY_STORAGE_PASSWORD": "writekey", })) if code != 70 { t.Fatalf("expected 70, got %d (stdout=%s stderr=%s)", code, out.String(), errb.String()) } if !bytesContains(out.String(), "uploaded=0 failed=1") { t.Fatalf("expected uploaded=0 failed=1, got %s", out.String()) } } func bytesContains(s, substr string) bool { return bytes.Contains([]byte(s), []byte(substr)) } func writeManifest(t *testing.T, dir, sha string, size int64) { t.Helper() content := fmt.Sprintf("assets:\n - path: foo\n sha256: %s\n size: %d\n", sha, size) if err := os.WriteFile(filepath.Join(dir, "manifest.yml"), []byte(content), 0644); err != nil { t.Fatal(err) } }