Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/cli. OpenStatus CLI www.openstatus.dev
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289package incident_test
import ( "context" "encoding/json" "net/http" "os" "path/filepath" "slices" "strings" "testing"
incidentv1 "buf.build/gen/go/openstatus/api/protocolbuffers/go/openstatus/incident/v1"
"github.com/openstatusHQ/cli/internal/incident")
const postmortemResponse = `{"postmortem":{ "incidentId": "42", "status": "POSTMORTEM_STATUS_DRAFT", "content": "# Summary\n\nBad deploy.", "draftedBy": "POSTMORTEM_AUTHOR_USER", "createdAt": "2026-10-16T12:00:00Z", "updatedAt": "2026-10-16T12:30:00Z"}}`
func Test_PostmortemCmd(t *testing.T) { t.Parallel()
var pm []string for _, sub := range incident.IncidentCmd().Commands { if sub.Name != "postmortem" { continue } if !slices.Contains(sub.Aliases, "pm") { t.Error("expected alias 'pm'") } for _, c := range sub.Commands { pm = append(pm, c.Name) } } if !slices.Equal(pm, []string{"show", "edit", "approve"}) { t.Errorf("postmortem subcommands = %v", pm) }}
func Test_ShowPostmortem(t *testing.T) { t.Run("piped output is the raw body", func(t *testing.T) { srv := newServer(http.StatusOK, postmortemResponse) var err error out := captureStdout(t, func() { err = incident.ShowPostmortemWithHTTPClient(context.Background(), srv.client(), "token", "42") }) if err != nil { t.Fatalf("unexpected error: %v", err) } req := decodeBody(t, srv.reqBody, &incidentv1.GetPostmortemRequest{}) if req.GetIncidentId() != "42" { t.Errorf("unexpected request %v", req) } if out != "# Summary\n\nBad deploy.\n" { t.Errorf("unexpected output %q", out) } })
t.Run("none yet when piped fails and keeps stdout empty", func(t *testing.T) { srv := newServer(http.StatusOK, `{}`) var err error out := captureStdout(t, func() { err = incident.ShowPostmortemWithHTTPClient(context.Background(), srv.client(), "token", "42") }) if err == nil || !strings.Contains(err.Error(), "no postmortem yet for incident 42") || !strings.Contains(err.Error(), "postmortem edit 42") { t.Errorf("unexpected error %v", err) } if out != "" { t.Errorf("expected empty stdout, got %q", out) } })
t.Run("none yet json is null", func(t *testing.T) { withOutputModes(t, true, false) srv := newServer(http.StatusOK, `{}`) out := captureStdout(t, func() { if err := incident.ShowPostmortemWithHTTPClient(context.Background(), srv.client(), "token", "42"); err != nil { t.Errorf("unexpected error: %v", err) } }) if strings.TrimSpace(out) != "null" { t.Errorf("unexpected output %q", out) } })
t.Run("json", func(t *testing.T) { withOutputModes(t, true, false) srv := newServer(http.StatusOK, postmortemResponse) out := captureStdout(t, func() { if err := incident.ShowPostmortemWithHTTPClient(context.Background(), srv.client(), "token", "42"); err != nil { t.Errorf("unexpected error: %v", err) } }) var got struct { IncidentID string `json:"incident_id"` Status string `json:"status"` DraftedBy string `json:"drafted_by"` Content string `json:"content"` } if err := json.Unmarshal([]byte(out), &got); err != nil { t.Fatalf("invalid JSON: %v\n%s", err, out) } if got.IncidentID != "42" || got.Status != "draft" || got.DraftedBy != "user" || !strings.HasPrefix(got.Content, "# Summary") { t.Errorf("unexpected payload %+v", got) } })}
func Test_ResolvePostmortemContent(t *testing.T) { t.Parallel()
dir := t.TempDir() path := filepath.Join(dir, "pm.md") if err := os.WriteFile(path, []byte("# From file\n"), 0o600); err != nil { t.Fatal(err) }
tests := []struct { name string file string content string fileSet bool contentSet bool stdin string want string wantErr string }{ {name: "file", file: path, fileSet: true, want: "# From file"}, {name: "stdin", file: "-", fileSet: true, stdin: "# From stdin\n", want: "# From stdin"}, {name: "content", content: "# Inline", contentSet: true, want: "# Inline"}, {name: "neither", wantErr: "exactly one of --file or --content"}, {name: "both", file: path, fileSet: true, content: "x", contentSet: true, wantErr: "exactly one of --file or --content"}, {name: "missing file", file: filepath.Join(dir, "nope.md"), fileSet: true, wantErr: "read postmortem file"}, {name: "empty content", content: " ", contentSet: true, wantErr: "cannot be empty"}, {name: "empty file flag", file: "", fileSet: true, wantErr: "--file cannot be empty"}, {name: "too long", content: strings.Repeat("a", 100001), contentSet: true, wantErr: "too long"}, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() got, err := incident.ResolvePostmortemContent(tt.file, tt.content, tt.fileSet, tt.contentSet, strings.NewReader(tt.stdin)) if tt.wantErr != "" { if err == nil || !strings.Contains(err.Error(), tt.wantErr) { t.Errorf("got %v, want error containing %q", err, tt.wantErr) } return } if err != nil { t.Fatalf("unexpected error: %v", err) } if got != tt.want { t.Errorf("got %q, want %q", got, tt.want) } }) }}
func Test_UpdatePostmortem(t *testing.T) { srv := newServer(http.StatusOK, postmortemResponse) var err error out := captureStdout(t, func() { err = incident.UpdatePostmortemWithHTTPClient(context.Background(), srv.client(), "token", "42", "# Summary") }) if err != nil { t.Fatalf("unexpected error: %v", err) } req := decodeBody(t, srv.reqBody, &incidentv1.UpdatePostmortemRequest{}) if req.GetIncidentId() != "42" || req.GetContent() != "# Summary" { t.Errorf("unexpected request %v", req) } if !strings.Contains(out, "Postmortem saved for incident 42 (draft)") || !strings.Contains(out, "postmortem approve 42") { t.Errorf("unexpected output %q", out) }}
func Test_ApprovePostmortem(t *testing.T) { approved := `{"postmortem":{"incidentId":"42","status":"POSTMORTEM_STATUS_APPROVED","draftedBy":"POSTMORTEM_AUTHOR_AGENT","approvedBy":{"email":"jane@example.com","name":"Jane"}},"incident":` + closedResponse[len(`{"incident":`):]
t.Run("close sent only when requested", func(t *testing.T) { for _, closeIncident := range []bool{false, true} { srv := newServer(http.StatusOK, approved) out := captureStdout(t, func() { if err := incident.ApprovePostmortemWithHTTPClient(context.Background(), srv.client(), "token", "42", closeIncident); err != nil { t.Errorf("unexpected error: %v", err) } }) req := decodeBody(t, srv.reqBody, &incidentv1.ApprovePostmortemRequest{}) if req.HasClose() != closeIncident || req.GetIncidentId() != "42" { t.Errorf("close=%v: unexpected request %v", closeIncident, req) } if !strings.Contains(out, "Postmortem approved for incident 42") || !strings.Contains(out, "Incident 42 closed") { t.Errorf("unexpected output %q", out) } } })
t.Run("json has postmortem and incident", func(t *testing.T) { withOutputModes(t, true, false) srv := newServer(http.StatusOK, approved) out := captureStdout(t, func() { if err := incident.ApprovePostmortemWithHTTPClient(context.Background(), srv.client(), "token", "42", true); err != nil { t.Errorf("unexpected error: %v", err) } }) var got struct { Postmortem struct { Status string `json:"status"` DraftedBy string `json:"drafted_by"` ApprovedBy struct { Email string `json:"email"` } `json:"approved_by"` } `json:"postmortem"` Incident struct { ClosedAt string `json:"closed_at"` } `json:"incident"` } if err := json.Unmarshal([]byte(out), &got); err != nil { t.Fatalf("invalid JSON: %v\n%s", err, out) } if got.Postmortem.Status != "approved" || got.Postmortem.DraftedBy != "agent" || got.Postmortem.ApprovedBy.Email != "jane@example.com" || got.Incident.ClosedAt == "" { t.Errorf("unexpected payload %+v", got) } })
t.Run("not found means no postmortem written", func(t *testing.T) { srv := errorServer(http.StatusNotFound, "not_found", "postmortem not found") err := incident.ApprovePostmortemWithHTTPClient(context.Background(), srv.client(), "token", "42", false) if err == nil || !strings.Contains(err.Error(), "no postmortem written yet for incident 42") { t.Errorf("unexpected error %v", err) } })
t.Run("permission denied explains roles", func(t *testing.T) { srv := errorServer(http.StatusForbidden, "permission_denied", "forbidden") err := incident.ApprovePostmortemWithHTTPClient(context.Background(), srv.client(), "token", "42", false) if err == nil || !strings.Contains(err.Error(), "or the incident commander") { t.Errorf("unexpected error %v", err) } })
t.Run("missing incident is not reported as missing postmortem", func(t *testing.T) { srv := errorServer(http.StatusNotFound, "not_found", "incident not found") err := incident.ApprovePostmortemWithHTTPClient(context.Background(), srv.client(), "token", "42", false) if err == nil || !strings.Contains(err.Error(), "incident 42 not found") { t.Errorf("unexpected error %v", err) } })
t.Run("permission denied keeps the server reason", func(t *testing.T) { srv := errorServer(http.StatusForbidden, "permission_denied", "api key lacks the incidents scope") err := incident.ApprovePostmortemWithHTTPClient(context.Background(), srv.client(), "token", "42", false) if err == nil || !strings.Contains(err.Error(), "api key lacks the incidents scope") || !strings.Contains(err.Error(), "or the incident commander") { t.Errorf("unexpected error %v", err) } })}
func Test_WritePostmortemHeader(t *testing.T) { t.Parallel()
approvedAt := "2026-10-16T13:00:00Z" pm := incidentv1.Postmortem_builder{ IncidentId: "42", Status: incidentv1.PostmortemStatus_POSTMORTEM_STATUS_APPROVED, Content: "# Summary", DraftedBy: incidentv1.PostmortemAuthor_POSTMORTEM_AUTHOR_AGENT, ApprovedBy: incidentv1.IncidentUser_builder{Name: "Jane", Email: "jane@example.com"}.Build(), ApprovedAt: &approvedAt, UpdatedAt: "2026-10-16T13:00:00Z", }.Build()
var buf strings.Builder incident.WritePostmortem(&buf, pm) out := buf.String() for _, want := range []string{"Status: approved", "Drafted by: agent", "Approved: 2026-10-16 13:00 UTC by Jane <jane@example.com>", "\n\n# Summary\n"} { if !strings.Contains(out, want) { t.Errorf("output missing %q:\n%s", want, out) } }}