Report JSON output failures from recovery commands

This commit is contained in:
2026-11-02 22:23:15 +00:00
parent 648c87bb8a
commit 19a14bfeb6
4 changed files with 57 additions and 5 deletions

View File

@@ -102,14 +102,17 @@ func runRecovery(ctx context.Context, invocation cli.Invocation, stdout, stderr
for _, item := range listed.Diagnostics {
diagnostics = append(diagnostics, diagnostic(item))
}
writeJSON(stdout, attemptsDocument{
if writeErr := writeJSON(stdout, attemptsDocument{
SchemaVersion: 1,
Command: "publication-attempts",
RecordDir: git.DisplayDiagnostic(listed.RecordDir),
Attempts: attempts,
Diagnostics: diagnostics,
Diagnostic: diagnostic(err),
})
}); writeErr != nil {
fmt.Fprintf(stderr, "write recovery output: %v\n", git.DisplayError(writeErr))
return 1
}
} else {
fmt.Fprintf(stdout, "publication records in %s\n", git.DisplayDiagnostic(listed.RecordDir))
for _, item := range listed.Attempts {
@@ -149,7 +152,10 @@ func runReconcile(ctx context.Context, invocation cli.Invocation, stdout, stderr
dto.ObservedCommit = result.ObservedCommit
}
dto.Diagnostic = diagnostic(err)
writeJSON(stdout, dto)
if writeErr := writeJSON(stdout, dto); writeErr != nil {
fmt.Fprintf(stderr, "write recovery output: %v\n", git.DisplayError(writeErr))
return 1
}
} else if err == nil {
fmt.Fprintf(stdout, "reconciled %s module=%s tag=%s state=%s\n", git.DisplayDiagnostic(invocation.RecordPath), git.DisplayDiagnostic(evidence.ModulePath), git.DisplayDiagnostic(evidence.Tag), result.State)
if result.State == releasekit.PublicationConflict {
@@ -243,7 +249,7 @@ func humanDiagnostic(err error) string {
return git.DisplayError(err).Error()
}
func writeJSON(writer io.Writer, value any) {
func writeJSON(writer io.Writer, value any) error {
encoder := json.NewEncoder(writer)
_ = encoder.Encode(value)
return encoder.Encode(value)
}

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
@@ -14,6 +15,41 @@ import (
"go.maximumdirect.net/releasekit/internal/publish"
)
type failingRecoveryWriter struct{ err error }
func (writer failingRecoveryWriter) Write([]byte) (int, error) { return 0, writer.err }
func TestRecoveryCommandsReportJSONWriteFailure(t *testing.T) {
repo := newCLIRepository(t)
cliGitRun(t, repo, "tag", "-a", "v1.2.3", "-m", "Release")
store := t.TempDir()
path, err := publish.SaveEvidence(t.Context(), repo, store, publish.Evidence{
RepositoryDir: repo, Endpoint: repo, Tag: "v1.2.3",
CandidateCommit: cliGitOutput(t, repo, "rev-parse", "HEAD"),
TagObject: cliGitOutput(t, repo, "rev-parse", "v1.2.3"),
})
if err != nil {
t.Fatal(err)
}
failure := errors.New("output unavailable: https://user:audit-secret@example.invalid/output")
for _, args := range [][]string{{"reconcile", "--record", path, "--json"}, {"publication-attempts", "--record-dir", store, "--json"}} {
var normal, stderr bytes.Buffer
if status := run(t.Context(), args, &normal, &stderr); status != 0 {
t.Fatalf("normal output status=%d: %s", status, &stderr)
}
stderr.Reset()
if status := run(t.Context(), args, failingRecoveryWriter{failure}, &stderr); status != 1 {
t.Fatalf("failed output status=%d: %s", status, &stderr)
}
if !strings.Contains(stderr.String(), "output unavailable") || strings.Contains(stderr.String(), "audit-secret") {
t.Fatalf("output diagnostic: %s", &stderr)
}
if args[0] == "reconcile" && !strings.Contains(normal.String(), `"state":"published"`) {
t.Fatalf("observation changed: %s", &normal)
}
}
}
func TestRecoveryDisplaysRedactCredentialPathsAndMetadata(t *testing.T) {
secret := "audit-secret"
raw := "https://audit-user:" + secret + "@example.invalid/attempt.json"