| index f8e19f634..d29bdef57 100644 |
| --- a/channels_digests_test.go |
| +++ b/channels_digests_test.go |
| @@ -5,10 +5,12 @@ import ( |
| "context" |
| "crypto/ed25519" |
| "encoding/json" |
| + "fmt" |
| "net/http" |
| "net/http/httptest" |
| "strings" |
| "testing" |
| + "time" |
| |
| "github.com/carlosframework/platform/internal/release" |
| ) |
| @@ -158,8 +160,8 @@ func TestChannelsDigestsAPIMode(t *testing.T) { |
| runChannelsDigests(nil) |
| out := buf.String() |
| for _, want := range []string{ |
| - "pointer digests: on for bab since 2026-10-07T00:00:00Z", |
| - "pointer digests: off for bah", |
| + fmt.Sprintf(textStatusOn, "bab", "2026-10-07T00:00:00Z"), |
| + fmt.Sprintf(textStatusOff, "bah"), |
| strings.Split(textStatusHeader, "\t")[0], textStateStraggler, "beta", |
| } { |
| if !strings.Contains(out, want) { |
| @@ -175,7 +177,7 @@ func TestChannelsDigestsAPIMode(t *testing.T) { |
| if posted["on"] != true || len(posted["accounts"].([]any)) != 1 { |
| t.Fatalf("posted = %v", posted) |
| } |
| - if want := "pointer digests on for bab."; !strings.Contains(buf.String(), want) { |
| + if want := fmt.Sprintf(textTurnedOn, "bab"); !strings.Contains(buf.String(), want) { |
| t.Fatalf("on output lacks %q:\n%s", want, buf.String()) |
| } |
| } |
| @@ -228,9 +230,116 @@ func TestResignAPIMode(t *testing.T) { |
| if bodies[1]["digest"] != strings.Repeat("a", 64) || bodies[1]["channel"] != "stable" { |
| t.Fatalf("signing body = %v", bodies[1]) |
| } |
| - for _, want := range []string{textResignComputed, textResignMissing, "re-signed titogo stable with digests"} { |
| + for _, want := range []string{textResignComputed, textResignMissing, fmt.Sprintf(textResignDone, "titogo", "stable")} { |
| if !strings.Contains(out.String(), want) { |
| t.Fatalf("output lacks %q:\n%s", want, out.String()) |
| } |
| } |
| } |
| + |
| +// An account whose report the console could not read is "unknown", never |
| +// "off", and never earns the none-left line. |
| +func TestChannelsDigestsAPIModeFailedAccount(t *testing.T) { |
| + useTempCredentials(t) |
| + clearDeploymentEnv(t) |
| + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| + json.NewEncoder(w).Encode(map[string]any{ |
| + "accounts": []map[string]any{ |
| + {"account": "bab", "on": true, "since": "2026-10-07T00:00:00Z"}, |
| + {"account": "bah", "on": false, "error": "marker did not verify"}, |
| + }, |
| + "channels": []map[string]any{}, |
| + }) |
| + })) |
| + defer ts.Close() |
| + loginTo(t, ts.URL, "vicky@keymail.dev") |
| + |
| + buf := captureStdout(t) |
| + runChannelsDigests(nil) |
| + out := buf.String() |
| + if want := fmt.Sprintf(textStatusError, "bah", "marker did not verify"); !strings.Contains(out, want) { |
| + t.Fatalf("output lacks %q:\n%s", want, out) |
| + } |
| + if strings.Contains(out, fmt.Sprintf(textStatusOff, "bah")) || strings.Contains(out, textStatusNoneLeft) { |
| + t.Fatalf("a failed account read as off, or the none-left line printed:\n%s", out) |
| + } |
| + if !strings.Contains(out, fmt.Sprintf(textStatusOn, "bab", "2026-10-07T00:00:00Z")) { |
| + t.Fatalf("the healthy account is missing:\n%s", out) |
| + } |
| +} |
| + |
| +func TestCheckNamedAccounts(t *testing.T) { |
| + rep := apiPointerDigests{Accounts: []apiDigestAccount{{Account: "bab"}, {Account: "bah"}}} |
| + if err := checkNamedAccounts(rep, []string{"bab", "bah"}); err != nil { |
| + t.Fatalf("known accounts: %v", err) |
| + } |
| + if err := checkNamedAccounts(rep, nil); err != nil { |
| + t.Fatalf("no names: %v", err) |
| + } |
| + err := checkNamedAccounts(rep, []string{"bab", "zzz"}) |
| + if err == nil || err.Error() != fmt.Sprintf(textUnknownAccount, "zzz") { |
| + t.Fatalf("unknown account: %v", err) |
| + } |
| +} |
| + |
| +func runDigests(t *testing.T, args ...string) string { |
| + t.Helper() |
| + buf := captureStdout(t) |
| + runChannelsDigests(args) |
| + return buf.String() |
| +} |
| + |
| +// Direct-store `channels digests`: bare lists, off removes the marker, on |
| +// writes it again (an account with no apps still reads on), and an account |
| +// whose marker cannot be verified is reported unknown. |
| +func TestChannelsDigestsDirectStore(t *testing.T) { |
| + s, pub := directDigestStore(t) // account bab: marker on, km/stable at v1 with no digest |
| + ctx := context.Background() |
| + m, err := release.ReadDigestMarker(ctx, s, pub, "bab") |
| + if err != nil || m == nil { |
| + t.Fatalf("marker: %v %v", m, err) |
| + } |
| + since := m.At.UTC().Format(time.RFC3339) |
| + |
| + out := runDigests(t, "--account-id", "bab") |
| + for _, want := range []string{fmt.Sprintf(textStatusOn, "bab", since), "km", "stable", "v1", textStateNo} { |
| + if !strings.Contains(out, want) { |
| + t.Fatalf("listing lacks %q:\n%s", want, out) |
| + } |
| + } |
| + if strings.Contains(out, textStatusNoneLeft) { |
| + t.Fatalf("none-left printed with a channel still without digests:\n%s", out) |
| + } |
| + |
| + out = runDigests(t, "off", "--account-id", "bab") |
| + if !strings.Contains(out, fmt.Sprintf(textTurnedOff, "bab")) || !strings.Contains(out, fmt.Sprintf(textStatusOff, "bab")) { |
| + t.Fatalf("off:\n%s", out) |
| + } |
| + if m, _ := release.ReadDigestMarker(ctx, s, pub, "bab"); m != nil { |
| + t.Fatal("off left the marker in place") |
| + } |
| + |
| + out = runDigests(t, "on", "--account-id", "bab", "--account-id", "bah") |
| + if !strings.Contains(out, fmt.Sprintf(textTurnedOn, "bab, bah")) { |
| + t.Fatalf("on:\n%s", out) |
| + } |
| + if m, _ := release.ReadDigestMarker(ctx, s, pub, "bab"); m == nil { |
| + t.Fatal("on did not write the marker") |
| + } |
| + // bah has no apps at all: on, straight after turning it on. |
| + if m, _ := release.ReadDigestMarker(ctx, s, pub, "bah"); m == nil || !strings.Contains(out, "pointer digests: on for bah since ") { |
| + t.Fatalf("an account with no apps does not read on:\n%s", out) |
| + } |
| + |
| + // An unverifiable marker is unknown, and the none-left line stays away. |
| + if _, err := s.Put(ctx, release.PointerDigestsKey("bai"), []byte("junk"), ""); err != nil { |
| + t.Fatal(err) |
| + } |
| + out = runDigests(t, "--account-id", "bai") |
| + if !strings.HasPrefix(strings.Split(out, "\n")[0], fmt.Sprintf(textStatusError, "bai", "")[:len("pointer digests: unknown for bai")]) { |
| + t.Fatalf("unverifiable marker:\n%s", out) |
| + } |
| + if strings.Contains(out, textStatusNoneLeft) || strings.Contains(out, fmt.Sprintf(textStatusOff, "bai")) { |
| + t.Fatalf("unverifiable marker read as off or done:\n%s", out) |
| + } |
| +} |