diff --git a/e2e/edge_case_test.go b/e2e/edge_case_test.go index 8335fa3..f86ec93 100644 --- a/e2e/edge_case_test.go +++ b/e2e/edge_case_test.go @@ -134,11 +134,13 @@ func TestJSONOnAckCommand(t *testing.T) { id := extractIncidentID(t, r.Stdout) t.Cleanup(func() { runCLI(t, "incident", "close", id) }) - // ack is served by the generated twin; --json wraps the OK line as {"message":"..."}. + // ack is served by the generated twin. /incident/ack answers with an empty + // data envelope, so --json emits the one thing the call did report: its + // request id. r = runCLI(t, "incident", "ack", id, "--json") requireSuccess(t, r) requireValidJSON(t, r.Stdout) - requireContains(t, r.Stdout, "OK: POST /incident/ack") + requireRequestIDOnly(t, r.Stdout) } // Test 307: --json on close command diff --git a/e2e/helpers_test.go b/e2e/helpers_test.go index d619f20..48e1162 100644 --- a/e2e/helpers_test.go +++ b/e2e/helpers_test.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "strings" "testing" "time" @@ -217,6 +218,47 @@ func requireValidJSON(t *testing.T, output string) { } } +// noPayloadAckRe matches the whole single line a command prints when its +// endpoint answers with no payload. Unlike the unit tests, which pin the line +// by equality against a stub's fixed request id, e2e runs against the real API +// and cannot know the id — so it pins the shape AND requires the id to be +// non-empty, which is the part a live server must supply. +var noPayloadAckRe = regexp.MustCompile(`^OK: the API returned no payload \(request_id \S+\)\.$`) + +// requireNoPayloadAck fails the test unless output is exactly the no-payload +// acknowledgement line. Equality on the whole output (not a substring) is the +// point: a caller reading this through `head` must find the line first, so +// anything printed above it is a regression. +func requireNoPayloadAck(t *testing.T, output string) { + t.Helper() + got := strings.TrimRight(output, "\n") + if !noPayloadAckRe.MatchString(got) { + t.Fatalf("output must be exactly the no-payload acknowledgement line, got:\n%s", output) + } +} + +// requireRequestIDOnly fails the test unless output is the structured form of +// the no-payload acknowledgement: an object carrying a non-empty request_id and +// nothing a caller would mistake for a payload. It also guards against the old +// shape, which wrapped a rendered sentence as {"message": "OK: POST /..."} and +// gave a machine caller nothing to read. +func requireRequestIDOnly(t *testing.T, output string) { + t.Helper() + var ack struct { + RequestID string `json:"request_id"` + Message string `json:"message"` + } + if err := json.Unmarshal([]byte(strings.TrimSpace(output)), &ack); err != nil { + t.Fatalf("output is not a JSON object (%v):\n%s", err, output) + } + if ack.RequestID == "" { + t.Fatalf("structured acknowledgement carries no request_id:\n%s", output) + } + if ack.Message != "" { + t.Fatalf("structured acknowledgement still renders a prose message:\n%s", output) + } +} + // requireTableHeaders fails the test if any of the given headers are missing // from the first line of the output. func requireTableHeaders(t *testing.T, output string, headers ...string) { diff --git a/e2e/incident_extended_test.go b/e2e/incident_extended_test.go index 96e6660..0a662dd 100644 --- a/e2e/incident_extended_test.go +++ b/e2e/incident_extended_test.go @@ -195,7 +195,7 @@ func TestIncidentAckSingleID(t *testing.T) { // Served by the generated twin (positional id → incident_ids). r = runCLI(t, "incident", "ack", id) requireSuccess(t, r) - requireContains(t, r.Stdout, "OK: POST /incident/ack") + requireNoPayloadAck(t, r.Stdout) } // Test 204: close single ID diff --git a/e2e/incident_test.go b/e2e/incident_test.go index 2019b47..d340f97 100644 --- a/e2e/incident_test.go +++ b/e2e/incident_test.go @@ -104,7 +104,7 @@ func TestIncidentLifecycle(t *testing.T) { // Step 3: Ack (served by the generated twin; positional id → incident_ids). r = runCLI(t, "incident", "ack", id) requireSuccess(t, r) - requireContains(t, r.Stdout, "OK: POST /incident/ack") + requireNoPayloadAck(t, r.Stdout) // Step 4: Get - should be Processing r = runCLI(t, "incident", "get", id) diff --git a/internal/cli/alert.go b/internal/cli/alert.go index fc5a615..c82a8d9 100644 --- a/internal/cli/alert.go +++ b/internal/cli/alert.go @@ -393,11 +393,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /alert/merge") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/alert_test.go b/internal/cli/alert_test.go index d22a2e8..51e5a5c 100644 --- a/internal/cli/alert_test.go +++ b/internal/cli/alert_test.go @@ -154,8 +154,8 @@ func TestCommandAlertMergeCommentFileReachesWireByteForByte(t *testing.T) { if stub.lastBody["incident_id"] != "inc-1" { t.Fatalf("[alert-merge-comment-file] expected incident_id %q, got %#v", "inc-1", stub.lastBody["incident_id"]) } - if !strings.Contains(out, "OK: POST /alert/merge") { - t.Fatalf("[alert-merge-comment-file] unexpected output:\n%s", out) + if got := strings.TrimRight(out, "\n"); got != gfStubNoPayloadLine { + t.Fatalf("[alert-merge-comment-file] default output must be exactly %q, got:\n%s", gfStubNoPayloadLine, out) } } diff --git a/internal/cli/command.go b/internal/cli/command.go index d31b696..e08b567 100644 --- a/internal/cli/command.go +++ b/internal/cli/command.go @@ -295,8 +295,9 @@ func (ctx *RunContext) WriteResult(message string) { // WriteRaw writes a non-JSON response body (e.g. a CSV/file download surfaced // on Response.Raw by the *export endpoints) straight to the output writer, so -// shell redirection (`> file.csv`) captures the bytes verbatim instead of the -// canned "OK: POST ..." acknowledgment. +// shell redirection (`> file.csv`) captures the bytes verbatim rather than the +// no-payload acknowledgement those commands print when no bytes arrive (see +// printRawOrNoPayload). func (ctx *RunContext) WriteRaw(body []byte) error { _, err := ctx.Writer.Write(body) return err diff --git a/internal/cli/command_test.go b/internal/cli/command_test.go index b12af01..7b602ed 100644 --- a/internal/cli/command_test.go +++ b/internal/cli/command_test.go @@ -496,8 +496,8 @@ func TestCommandIncidentUnack(t *testing.T) { if got, want := strings.Join(stub.bodyStrings("incident_ids"), ","), "inc-1,inc-2"; got != want { t.Fatalf("[incident-unack] expected ids %q, got %q", want, got) } - if !strings.Contains(out, "OK: POST /incident/unack") { - t.Fatalf("[incident-unack] unexpected output:\n%s", out) + if got := strings.TrimRight(out, "\n"); got != gfStubNoPayloadLine { + t.Fatalf("[incident-unack] default output must be exactly %q, got:\n%s", gfStubNoPayloadLine, out) } } @@ -516,8 +516,8 @@ func TestCommandIncidentWake(t *testing.T) { if got, want := strings.Join(stub.bodyStrings("incident_ids"), ","), "inc-1"; got != want { t.Fatalf("[incident-wake] expected ids %q, got %q", want, got) } - if !strings.Contains(out, "OK: POST /incident/wake") { - t.Fatalf("[incident-wake] unexpected output:\n%s", out) + if got := strings.TrimRight(out, "\n"); got != gfStubNoPayloadLine { + t.Fatalf("[incident-wake] default output must be exactly %q, got:\n%s", gfStubNoPayloadLine, out) } } @@ -1312,8 +1312,8 @@ func TestCommandIncidentDisableMerge(t *testing.T) { if got, want := strings.Join(stub.bodyStrings("incident_ids"), ","), "inc-1,inc-2"; got != want { t.Fatalf("[incident-disable-merge] expected ids %q, got %q", want, got) } - if !strings.Contains(out, "OK: POST /incident/disable-merge") { - t.Fatalf("[incident-disable-merge] unexpected output:\n%s", out) + if got := strings.TrimRight(out, "\n"); got != gfStubNoPayloadLine { + t.Fatalf("[incident-disable-merge] default output must be exactly %q, got:\n%s", gfStubNoPayloadLine, out) } } diff --git a/internal/cli/gen_no_payload_test.go b/internal/cli/gen_no_payload_test.go new file mode 100644 index 0000000..d0bf71e --- /dev/null +++ b/internal/cli/gen_no_payload_test.go @@ -0,0 +1,250 @@ +package cli + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "regexp" + "strings" + "testing" + + "github.com/flashcatcloud/go-flashduty" +) + +// newRawStub points the client at a server that answers every request with the +// given status, headers and body bytes, bypassing newGFStub's envelope. Tests +// below use it for the two things an envelope stub cannot produce: a success +// body that is not envelope JSON (which the SDK surfaces on Response.Raw), and +// a 200 that reports no request id at all. +func newRawStub(t *testing.T, contentType string, body []byte, setRequestID bool) { + t.Helper() + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if setRequestID { + w.Header().Set("Flashcat-Request-Id", gfStubRequestID) + } + if contentType != "" { + w.Header().Set("Content-Type", contentType) + } + _, _ = w.Write(body) + })) + t.Cleanup(server.Close) + + newClientFn = func() (*flashduty.Client, error) { + return flashduty.NewClient("test-key", flashduty.WithBaseURL(server.URL)) + } +} + +// TestGeneratedNoPayloadLineIsTheWholeDefaultOutput pins the default output of a +// generated command whose endpoint returns no payload. Equality on the whole +// output, not a substring, is the contract: callers read this through `head`, +// so a line pushed down by anything printed above it is a regression a +// strings.Contains assertion would pass. +// +// What it must NOT be is the previous output, "OK: POST /channel/disable" — an +// echo of the request, which restated the path the caller had just typed and +// so read as a description of a result nobody had fetched. +func TestGeneratedNoPayloadLineIsTheWholeDefaultOutput(t *testing.T) { + saveAndResetGlobals(t) + newGFStub(t) + + stdout, stderr, err := execCommandSplit("channel", "disable", "1") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got := strings.TrimRight(stdout, "\n"); got != gfStubNoPayloadLine { + t.Fatalf("default output must be exactly %q, got:\n%s", gfStubNoPayloadLine, stdout) + } + if stderr != "" { + t.Errorf("a plain write must print nothing on stderr, got:\n%s", stderr) + } +} + +// TestGeneratedNoPayloadStructuredCarriesOnlyTheRequestID guards the machine +// path. The old shape was {"message": "OK: POST /channel/disable"} — a rendered +// English sentence in a JSON field, which a caller could only string-match. The +// new shape carries the request id as data, and carries nothing else, because +// nothing else is known: the endpoint's 200 envelope declares `data` as an +// empty object. +// +// The TOON subtest is not duplicate coverage. TOON is a different marshaller +// (toon.Marshal, vs json.MarshalIndent over output.NullUnsetInstants), so a +// change that broke only the TOON path would pass every JSON assertion. +func TestGeneratedNoPayloadStructuredCarriesOnlyTheRequestID(t *testing.T) { + t.Run("json", func(t *testing.T) { + saveAndResetGlobals(t) + newGFStub(t) + + stdout, _, err := execCommandSplit("channel", "disable", "1", "--json") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + var ack map[string]any + if err := json.Unmarshal([]byte(stdout), &ack); err != nil { + t.Fatalf("output is not a JSON object (%v):\n%s", err, stdout) + } + if got := ack["request_id"]; got != gfStubRequestID { + t.Errorf("request_id = %v, want %q", got, gfStubRequestID) + } + if len(ack) != 1 { + t.Errorf("structured acknowledgement must carry request_id alone, got %v", ack) + } + }) + + t.Run("toon", func(t *testing.T) { + saveAndResetGlobals(t) + newGFStub(t) + + stdout, _, err := execCommandSplit("channel", "disable", "1", "--output-format", "toon") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !strings.Contains(stdout, "request_id") || !strings.Contains(stdout, gfStubRequestID) { + t.Errorf("toon output missing the request id:\n%s", stdout) + } + if strings.Contains(stdout, "OK:") { + t.Errorf("toon output must not carry the table-mode sentence:\n%s", stdout) + } + }) +} + +// TestGeneratedNoPayloadOmitsAnAbsentRequestID covers the case the two wordings +// exist for. A 200 with an empty body never parses an envelope, so the id can +// only come from the Flashcat-Request-Id header; with neither, there is no id. +// The line must then say so rather than print a hole, "(request_id )", and the +// structured form must emit an empty object rather than invent request_id: "". +func TestGeneratedNoPayloadOmitsAnAbsentRequestID(t *testing.T) { + t.Run("table", func(t *testing.T) { + saveAndResetGlobals(t) + newRawStub(t, "", nil, false) + + stdout, _, err := execCommandSplit("channel", "disable", "1") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got := strings.TrimRight(stdout, "\n"); got != noPayloadLine { + t.Fatalf("output must be exactly %q, got:\n%s", noPayloadLine, stdout) + } + }) + + t.Run("json", func(t *testing.T) { + saveAndResetGlobals(t) + newRawStub(t, "", nil, false) + + stdout, _, err := execCommandSplit("channel", "disable", "1", "--json") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + var ack map[string]any + if err := json.Unmarshal([]byte(stdout), &ack); err != nil { + t.Fatalf("output is not a JSON object (%v):\n%s", err, stdout) + } + if len(ack) != 0 { + t.Errorf("an unknown request id must be absent, not empty; got %v", ack) + } + }) + + // TOON marshals an all-omitempty struct through its own code path, so what + // it prints for a record with nothing in it does not follow from the JSON + // case above. Whatever it is, it must not claim an id: a literal + // "request_id" with nothing after it would read as a server that answered + // with a blank one. + t.Run("toon", func(t *testing.T) { + saveAndResetGlobals(t) + newRawStub(t, "", nil, false) + + stdout, _, err := execCommandSplit("channel", "disable", "1", "--output-format", "toon") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if strings.Contains(stdout, "request_id") { + t.Errorf("toon output names a request id the server never sent:\n%q", stdout) + } + }) +} + +// exportCSV is the body newRawStub serves for the download tests below. Its +// bytes are deliberately not valid JSON, which is what makes the SDK surface +// them on Response.Raw. +const exportCSV = "issue_id,error_type\nabc,Error\n" + +// issueExportArgs drives the generated `rum issue-export` command, one of the +// eight endpoints whose 200 declares no application/json body at all. The +// window goes through --data because both bounds are required epoch +// milliseconds. +var issueExportArgs = []string{ + "rum", "issue-export", + "--data", `{"start_time":1772611200000,"end_time":1775961914595}`, +} + +// TestGeneratedDownloadStreamsRawBodyInsteadOfTheNoPayloadLine is the other +// half of the branch printRawOrNoPayload chooses between. Response.Raw is +// populated only for a success body that is not envelope JSON, so that guard — +// not anything in the spec read at generation time — is what tells a CSV +// download apart from a mutation at runtime. The bytes must reach stdout +// verbatim, with no acknowledgement line wrapped around them, or `> file.csv` +// writes a corrupt file. +func TestGeneratedDownloadStreamsRawBodyInsteadOfTheNoPayloadLine(t *testing.T) { + saveAndResetGlobals(t) + newRawStub(t, "text/csv", []byte(exportCSV), true) + + stdout, _, err := execCommandSplit(issueExportArgs...) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if stdout != exportCSV { + t.Fatalf("CSV body must reach stdout verbatim:\nwant: %q\n got: %q", exportCSV, stdout) + } +} + +// TestGeneratedDownloadReportsNoPayloadWhenTheServerSendsNoBytes is the same command +// answered with an empty body. There is nothing to stream, so it falls through +// to the acknowledgement — and "the API returned no payload" is the literal +// truth of a zero-byte download, which is why one wording serves both the +// mutations and this case instead of two. +func TestGeneratedDownloadReportsNoPayloadWhenTheServerSendsNoBytes(t *testing.T) { + saveAndResetGlobals(t) + newRawStub(t, "text/csv", nil, true) + + stdout, _, err := execCommandSplit(issueExportArgs...) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got := strings.TrimRight(stdout, "\n"); got != gfStubNoPayloadLine { + t.Fatalf("output must be exactly %q, got:\n%s", gfStubNoPayloadLine, stdout) + } +} + +// cannedPathEcho matches the output this change removed: a literal +// acknowledgement that names an HTTP verb and a request path. +var cannedPathEcho = regexp.MustCompile(`"OK: (GET|POST|PUT|PATCH|DELETE) /`) + +// TestNoCommandEchoesItsRequestPath is the regression guard on the committed +// artifact rather than on the generator: the generated files are derived, so a +// generator change that is not regenerated would leave these untouched. It +// covers the curated commands too, because three of them carried the same +// canned line and a curated command WINS over its generated twin at +// registration (genAddLeaf keeps the first name it sees), so fixing only the +// generator would have left the command users actually reach unfixed. +func TestNoCommandEchoesItsRequestPath(t *testing.T) { + entries, err := os.ReadDir(".") + if err != nil { + t.Fatalf("read package dir: %v", err) + } + for _, e := range entries { + name := e.Name() + if e.IsDir() || !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") { + continue + } + src, err := os.ReadFile(filepath.Join(".", name)) + if err != nil { + t.Fatalf("read %s: %v", name, err) + } + if loc := cannedPathEcho.FindIndex(src); loc != nil { + t.Errorf("%s still acknowledges a write by echoing its request path (%q) — "+ + "report what the API returned, or say it returned nothing (printRawOrNoPayload)", + name, src[loc[0]:loc[1]]) + } + } +} diff --git a/internal/cli/gen_support.go b/internal/cli/gen_support.go index 54cbe4f..92adaea 100644 --- a/internal/cli/gen_support.go +++ b/internal/cli/gen_support.go @@ -13,6 +13,7 @@ import ( "strings" "time" + "github.com/flashcatcloud/go-flashduty" "github.com/spf13/cobra" "github.com/flashcatcloud/flashduty-cli/internal/output" @@ -433,6 +434,73 @@ func bindURLTagged(body map[string]any, rv reflect.Value) { } } +// noPayloadLine is the table-mode line for a call whose body carried +// nothing: the mutation endpoints whose 200 envelope declares `data` as an +// empty object, and an export/download endpoint that answered with zero bytes. +// It replaces an echo of the request — the verb and path the caller had just +// typed — which stated nothing the caller did not already know, yet read as a +// description of a stored result that was never fetched. +const noPayloadLine = "OK: the API returned no payload." + +// noPayloadLineFmt is noPayloadLine carrying the request id: the one +// fact of such a call a caller cannot derive from its own arguments, and what +// it has to quote when reporting a write that went wrong. The id can be +// genuinely absent — a 200 with an empty body never parses an envelope, leaving +// only the Flashcat-Request-Id header — which is why the no-id wording above +// exists rather than printing "(request_id )". +const noPayloadLineFmt = "OK: the API returned no payload (request_id %s)." + +// printRawOrNoPayload renders the response of a command whose SDK method +// returns no typed data (methodInfo.HasData false, reflected at generation +// time), which is every generated mutation plus the CSV/file downloads. Its two +// names are its two outcomes, and which one applies is only knowable at +// runtime: the SDK fills Response.Raw exactly when a 2xx body is not envelope +// JSON, so a download's bytes land there while a mutation's empty envelope +// leaves it nil. +// +// Non-empty Raw streams verbatim, so shell redirection (`> file.csv`) captures +// a download byte for byte. Otherwise the call really did carry nothing, and the +// output says so — the alternative being to print a sentence that contradicts +// bytes the command is holding. Deciding that here rather than in each caller is +// what keeps the claim and the response in step: a caller cannot forget the +// check, and the acknowledgement cannot outrun what arrived. +// +// A command that does something else with a download's bytes — insight +// incident-export counts its rows before writing them — keeps its own Raw +// branch and calls printNoPayload directly for the empty case. +func printRawOrNoPayload(ctx *RunContext, resp *flashduty.Response) error { + if resp != nil && len(resp.Raw) > 0 { + return ctx.WriteRaw(resp.Raw) + } + return printNoPayload(ctx, resp) +} + +// printNoPayload states that a call carried nothing back, and reports the +// request id when the server gave one. Structured mode emits that id as data +// rather than a rendered sentence, so `--json` stays parseable; an absent id is +// left out instead of invented as "". +func printNoPayload(ctx *RunContext, resp *flashduty.Response) error { + ack := noPayloadAck{} + if resp != nil { + ack.RequestID = resp.RequestID + } + message := noPayloadLine + if ack.RequestID != "" { + message = fmt.Sprintf(noPayloadLineFmt, ack.RequestID) + } + return ctx.WriteResultJSON(ack, message) +} + +// noPayloadAck is the structured form of the no-payload branch +// above. It carries only what the API actually reported; there is no payload +// field, because the absence of one is the statement. The toon tag is not +// redundant with the json one: toon.Marshal reads only its own tag, so without +// it TOON output would spell the field RequestID while JSON spelled it +// request_id. +type noPayloadAck struct { + RequestID string `json:"request_id,omitempty" toon:"request_id,omitempty"` +} + // printGenericResult renders a generated command's typed response. In // machine-readable mode (TOON/JSON) it marshals the whole value — which is what // the agent reads. A list-shaped response (a top-level array of objects, or an diff --git a/internal/cli/gfstub_test.go b/internal/cli/gfstub_test.go index 814d1fc..a20da82 100644 --- a/internal/cli/gfstub_test.go +++ b/internal/cli/gfstub_test.go @@ -2,6 +2,7 @@ package cli import ( "encoding/json" + "fmt" "io" "net/http" "net/http/httptest" @@ -11,6 +12,15 @@ import ( "github.com/flashcatcloud/go-flashduty" ) +// gfStubRequestID is the request id newGFStub reports in every envelope. A +// command whose endpoint returns no payload prints it (see gfStubNoPayloadLine), +// so the two are pinned to one constant rather than two literals that can drift. +const gfStubRequestID = "test-request-id" + +// gfStubNoPayloadLine is the exact line a command prints against newGFStub when +// its endpoint answered with no payload. +var gfStubNoPayloadLine = fmt.Sprintf(noPayloadLineFmt, gfStubRequestID) + // gfStub is an httptest-backed stand-in for the go-flashduty API. Migrated // commands build a *flashduty.Client (a concrete type, not an interface), so // they can't be mocked the way the legacy flashdutyClient interface is — they @@ -107,7 +117,7 @@ func newGFStub(t *testing.T) *gfStub { } resp := map[string]any{ - "request_id": "test-request-id", + "request_id": gfStubRequestID, "error": map[string]any{"code": "OK", "message": ""}, "data": payload, } diff --git a/internal/cli/insight.go b/internal/cli/insight.go index 5e2ea6d..3202654 100644 --- a/internal/cli/insight.go +++ b/internal/cli/insight.go @@ -311,8 +311,7 @@ Request fields: return err } if resp == nil || len(resp.Raw) == 0 { - ctx.WriteResult("OK: POST /insight/incident/export") - return nil + return printNoPayload(ctx, resp) } rows, err := countCSVDataRows(resp.Raw) if err != nil { diff --git a/internal/cli/outputfields_test.go b/internal/cli/outputfields_test.go index 5dd9e59..d732d43 100644 --- a/internal/cli/outputfields_test.go +++ b/internal/cli/outputfields_test.go @@ -124,7 +124,7 @@ func TestGlobalFieldsOnAcknowledgement(t *testing.T) { if err != nil { t.Fatalf("execCommand: %v", err) } - if !strings.Contains(stdout, `"message"`) { + if !strings.Contains(stdout, `"request_id"`) { t.Errorf("acknowledgement should print unchanged, got %q", stdout) } if !strings.Contains(stderr, `note: --fields does not apply to the output of "flashduty channel disable"`) { diff --git a/internal/cli/upload.go b/internal/cli/upload.go index 715f57c..75bb4e9 100644 --- a/internal/cli/upload.go +++ b/internal/cli/upload.go @@ -70,7 +70,7 @@ API: POST /enrichment/mapping/data/upload (mapping-data-write-upload)`, } defer func() { _ = file.Close() }() - _, err = ctx.Client.AlertEnrichment.MappingDataWriteUpload(cmdContext(ctx.Cmd), &flashduty.MappingDataUploadRequest{ + resp, err := ctx.Client.AlertEnrichment.MappingDataWriteUpload(cmdContext(ctx.Cmd), &flashduty.MappingDataUploadRequest{ SchemaID: schemaID, DoNotTruncateFirst: doNotTruncateFirst, File: file, @@ -79,8 +79,7 @@ API: POST /enrichment/mapping/data/upload (mapping-data-write-upload)`, if err != nil { return err } - ctx.WriteResult("OK: POST /enrichment/mapping/data/upload") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_alert_enrichment.go b/internal/cli/zz_generated_alert_enrichment.go index 479aabb..e53707e 100644 --- a/internal/cli/zz_generated_alert_enrichment.go +++ b/internal/cli/zz_generated_alert_enrichment.go @@ -386,11 +386,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /enrichment/upsert") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -662,11 +658,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /field/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -730,11 +722,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /field/update") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -987,11 +975,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /enrichment/mapping/api/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1075,11 +1059,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /enrichment/mapping/api/update") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1134,11 +1114,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /enrichment/mapping/data/download") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1281,11 +1257,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /enrichment/mapping/data/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1334,11 +1306,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /enrichment/mapping/data/truncate") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1621,11 +1589,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /enrichment/mapping/schema/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1688,11 +1652,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /enrichment/mapping/schema/update") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_alert_rules.go b/internal/cli/zz_generated_alert_rules.go index 317cdc4..228b755 100644 --- a/internal/cli/zz_generated_alert_rules.go +++ b/internal/cli/zz_generated_alert_rules.go @@ -793,11 +793,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /monit/rule/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -841,11 +837,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /monit/rule/delete/batch") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_alerts.go b/internal/cli/zz_generated_alerts.go index c154a68..1536e25 100644 --- a/internal/cli/zz_generated_alerts.go +++ b/internal/cli/zz_generated_alerts.go @@ -1095,11 +1095,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /alert/merge") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1166,11 +1162,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /alert/pipeline/upsert") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_analytics.go b/internal/cli/zz_generated_analytics.go index 802f349..0434518 100644 --- a/internal/cli/zz_generated_analytics.go +++ b/internal/cli/zz_generated_analytics.go @@ -991,11 +991,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /insight/channel/export") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1153,11 +1149,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /insight/incident/export") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1576,11 +1568,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /insight/responder/export") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1759,11 +1747,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /insight/team/export") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_applications.go b/internal/cli/zz_generated_applications.go index c4f21de..a9a6aa7 100644 --- a/internal/cli/zz_generated_applications.go +++ b/internal/cli/zz_generated_applications.go @@ -911,11 +911,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /rum/application/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1016,11 +1012,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /rum/application/update") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_artifacts.go b/internal/cli/zz_generated_artifacts.go index e02cae5..145be12 100644 --- a/internal/cli/zz_generated_artifacts.go +++ b/internal/cli/zz_generated_artifacts.go @@ -345,11 +345,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: GET /safari/artifact/stream") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_calendars.go b/internal/cli/zz_generated_calendars.go index a9ca9f2..38da9a3 100644 --- a/internal/cli/zz_generated_calendars.go +++ b/internal/cli/zz_generated_calendars.go @@ -62,11 +62,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /calendar/event/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -366,11 +362,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /calendar/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -580,11 +572,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /calendar/update") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_channels.go b/internal/cli/zz_generated_channels.go index e6044c5..a3fe7f4 100644 --- a/internal/cli/zz_generated_channels.go +++ b/internal/cli/zz_generated_channels.go @@ -196,11 +196,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -248,11 +244,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/disable") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -300,11 +292,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/enable") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -456,11 +444,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/escalate/rule/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -510,11 +494,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/escalate/rule/disable") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -564,11 +544,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/escalate/rule/enable") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -796,11 +772,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/escalate/rule/reorder") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -901,11 +873,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/escalate/rule/update") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1193,11 +1161,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/inhibit/rule/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1247,11 +1211,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/inhibit/rule/disable") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1301,11 +1261,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/inhibit/rule/enable") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1442,11 +1398,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/inhibit/rule/update") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1760,11 +1712,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/silence/rule/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1814,11 +1762,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/silence/rule/disable") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1868,11 +1812,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/silence/rule/enable") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -2027,11 +1967,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/silence/rule/update") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -2150,11 +2086,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/unsubscribe/rule/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -2204,11 +2136,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/unsubscribe/rule/disable") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -2258,11 +2186,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/unsubscribe/rule/enable") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -2385,11 +2309,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/unsubscribe/rule/update") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -2739,11 +2659,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /route/upsert") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_data_sources.go b/internal/cli/zz_generated_data_sources.go index 300afd1..19f3078 100644 --- a/internal/cli/zz_generated_data_sources.go +++ b/internal/cli/zz_generated_data_sources.go @@ -652,11 +652,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /monit/datasource/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_dedicated_integrations.go b/internal/cli/zz_generated_dedicated_integrations.go index a0f3be1..fdd120f 100644 --- a/internal/cli/zz_generated_dedicated_integrations.go +++ b/internal/cli/zz_generated_dedicated_integrations.go @@ -296,11 +296,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/integration/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -348,11 +344,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/integration/disable") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -400,11 +392,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /channel/integration/enable") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_error_ingestion_rules.go b/internal/cli/zz_generated_error_ingestion_rules.go index ec8157b..0231ca3 100644 --- a/internal/cli/zz_generated_error_ingestion_rules.go +++ b/internal/cli/zz_generated_error_ingestion_rules.go @@ -116,11 +116,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /rum/error-ingestion/rules/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -170,11 +166,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /rum/error-ingestion/rules/disable") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -224,11 +216,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /rum/error-ingestion/rules/enable") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -386,11 +374,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /rum/error-ingestion/rules/history/revert") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -515,11 +499,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /rum/error-ingestion/rules/update") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_incidents.go b/internal/cli/zz_generated_incidents.go index 5b6fa9b..03e8897 100644 --- a/internal/cli/zz_generated_incidents.go +++ b/internal/cli/zz_generated_incidents.go @@ -386,11 +386,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/ack") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -598,11 +594,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/assign") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -666,11 +658,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/comment") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -784,11 +772,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/comment-type/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -881,11 +865,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/comment-type/reorder") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -943,11 +923,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/comment-type/update") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1128,11 +1104,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/disable-merge") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1382,11 +1354,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/field/reset") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -2245,11 +2213,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/merge") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -2519,11 +2483,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/post-mortem/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -2781,11 +2741,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/remove") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -2838,11 +2794,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/reopen") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -2921,11 +2873,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/reset") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -3004,11 +2952,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/resolve") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -3069,11 +3013,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/responder/add") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -3127,11 +3067,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/snooze") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -3180,11 +3116,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/unack") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -3232,11 +3164,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/wake") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -3362,11 +3290,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/war-room/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -4483,11 +4407,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/post-mortem/template/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -4657,11 +4577,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/post-mortem/basics/reset") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -4719,11 +4635,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/post-mortem/follow-ups/reset") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -4777,11 +4689,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/post-mortem/status/reset") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -4835,11 +4743,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /incident/post-mortem/title/reset") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_integrations.go b/internal/cli/zz_generated_integrations.go index 6057add..34719ec 100644 --- a/internal/cli/zz_generated_integrations.go +++ b/internal/cli/zz_generated_integrations.go @@ -559,11 +559,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /integration/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -611,11 +607,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /integration/disable") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -663,11 +655,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /integration/enable") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_issue_preset_severity_rules.go b/internal/cli/zz_generated_issue_preset_severity_rules.go index c605ebd..b33fed6 100644 --- a/internal/cli/zz_generated_issue_preset_severity_rules.go +++ b/internal/cli/zz_generated_issue_preset_severity_rules.go @@ -123,11 +123,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /rum/issue/preset-severity/rules/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -177,11 +173,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /rum/issue/preset-severity/rules/disable") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -231,11 +223,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /rum/issue/preset-severity/rules/enable") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -395,11 +383,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /rum/issue/preset-severity/rules/history/revert") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -517,11 +501,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /rum/issue/preset-severity/rules/reorder") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -591,11 +571,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /rum/issue/preset-severity/rules/update") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_issues.go b/internal/cli/zz_generated_issues.go index 2f57003..8df4f03 100644 --- a/internal/cli/zz_generated_issues.go +++ b/internal/cli/zz_generated_issues.go @@ -128,11 +128,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /rum/issue/export") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -465,11 +461,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /rum/issue/update") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_members.go b/internal/cli/zz_generated_members.go index b220ee9..f55c21e 100644 --- a/internal/cli/zz_generated_members.go +++ b/internal/cli/zz_generated_members.go @@ -73,11 +73,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /member/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -136,11 +132,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /member/role/grant") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -514,11 +506,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /member/info/reset") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -577,11 +565,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /member/role/revoke") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -635,11 +619,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /member/role/update") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_notification_templates.go b/internal/cli/zz_generated_notification_templates.go index 2dfea64..81f56ac 100644 --- a/internal/cli/zz_generated_notification_templates.go +++ b/internal/cli/zz_generated_notification_templates.go @@ -547,11 +547,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /template/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -720,11 +716,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /template/update") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_roles_permissions.go b/internal/cli/zz_generated_roles_permissions.go index 1019be9..0b7721f 100644 --- a/internal/cli/zz_generated_roles_permissions.go +++ b/internal/cli/zz_generated_roles_permissions.go @@ -292,11 +292,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /role/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -345,11 +341,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /role/disable") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -397,11 +389,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /role/enable") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -454,11 +442,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /role/member/grant") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -512,11 +496,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /role/member/revoke") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_rule_folders.go b/internal/cli/zz_generated_rule_folders.go index ae9df7b..8b06ae1 100644 --- a/internal/cli/zz_generated_rule_folders.go +++ b/internal/cli/zz_generated_rule_folders.go @@ -204,11 +204,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /monit/folder/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -261,11 +257,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /monit/folder/move") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_schedules.go b/internal/cli/zz_generated_schedules.go index 25fd931..c970f28 100644 --- a/internal/cli/zz_generated_schedules.go +++ b/internal/cli/zz_generated_schedules.go @@ -264,11 +264,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /schedule/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1354,11 +1350,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /schedule/update") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_status_pages.go b/internal/cli/zz_generated_status_pages.go index 2a6179f..9c79dae 100644 --- a/internal/cli/zz_generated_status_pages.go +++ b/internal/cli/zz_generated_status_pages.go @@ -348,11 +348,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /status-page/change/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -688,11 +684,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /status-page/change/timeline/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -762,11 +754,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /status-page/change/timeline/update") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -834,11 +822,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /status-page/change/update") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -895,11 +879,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /status-page/component/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1117,11 +1097,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /status-page/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1441,11 +1417,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /status-page/migration/cancel") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1567,11 +1539,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /status-page/section/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1683,11 +1651,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /status-page/subscriber/export") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1747,11 +1711,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /status-page/subscriber/import") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -1889,11 +1849,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /status-page/template/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } @@ -2145,11 +2101,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /status-page/update") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cli/zz_generated_teams.go b/internal/cli/zz_generated_teams.go index f08cddd..e103fb6 100644 --- a/internal/cli/zz_generated_teams.go +++ b/internal/cli/zz_generated_teams.go @@ -277,11 +277,7 @@ Request fields: if err != nil { return err } - if resp != nil && len(resp.Raw) > 0 { - return ctx.WriteRaw(resp.Raw) - } - ctx.WriteResult("OK: POST /team/delete") - return nil + return printRawOrNoPayload(ctx, resp) }) }, } diff --git a/internal/cmd/cligen/main.go b/internal/cmd/cligen/main.go index b496680..c939cac 100644 --- a/internal/cmd/cligen/main.go +++ b/internal/cmd/cligen/main.go @@ -1394,14 +1394,20 @@ func emitCmd(fn string, s service, o specOp, mi methodInfo) string { fmt.Fprintf(&b, "\t\t\t\treturn printGenericResult(ctx, out, body, listDefaultsFor(%q, %q))\n", s.Name, o.Method) default: // (*Response, error): a mutation, OR an export/attachment endpoint - // whose CSV/file body the SDK surfaces on Response.Raw. Capture the - // response and stream Raw verbatim when present (so `> file.csv` - // works); otherwise fall back to the canned OK acknowledgment. + // whose CSV/file body the SDK surfaces on Response.Raw. Which one a + // given call turned out to be is not knowable here, only at runtime — + // Raw is filled exactly when a 2xx body is not envelope JSON — so + // printRawOrNoPayload decides, streaming a download verbatim and + // otherwise saying the call carried nothing. + // + // For the mutations the spec says it will carry nothing: every one of + // their 200 envelopes declares `data` as an empty object. There is no + // stored object and no id to report, so the output states that and + // hands back the request id rather than echoing the path the caller + // typed. fmt.Fprintf(&b, "\t\t\t\tresp, err := %s\n", call) b.WriteString("\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n") - b.WriteString("\t\t\t\tif resp != nil && len(resp.Raw) > 0 {\n\t\t\t\t\treturn ctx.WriteRaw(resp.Raw)\n\t\t\t\t}\n") - fmt.Fprintf(&b, "\t\t\t\tctx.WriteResult(%q)\n", "OK: "+o.HTTP+" "+o.Path) - b.WriteString("\t\t\t\treturn nil\n") + b.WriteString("\t\t\t\treturn printRawOrNoPayload(ctx, resp)\n") } b.WriteString("\t\t\t})\n\t\t},\n\t}\n") diff --git a/internal/cmd/cligen/no_payload_test.go b/internal/cmd/cligen/no_payload_test.go new file mode 100644 index 0000000..addd620 --- /dev/null +++ b/internal/cmd/cligen/no_payload_test.go @@ -0,0 +1,104 @@ +package main + +import ( + "strings" + "testing" +) + +// loadRealServices builds the exact service set run() generates from: the spec +// shipped in the linked go-flashduty module, read against the compiled SDK. +func loadRealServices(t *testing.T) ([]service, map[string]map[string]methodInfo) { + t.Helper() + path, err := specPath() + if err != nil { + t.Fatalf("locate spec: %v", err) + } + spec, err := loadSpec(path) + if err != nil { + t.Fatalf("load spec: %v", err) + } + sdk := reflectSDK() + return collectServices(asMap(spec["paths"]), asMap(asMap(spec["components"])["schemas"]), sdk), sdk +} + +// TestNoPayloadOpsDeclareNoPayload pins the premise the acknowledgement rests +// on. An operation whose SDK method returns no typed data (methodInfo.HasData +// false) is routed to printRawOrNoPayload, which tells the caller the API returned +// nothing. That is only honest while the spec agrees, and today it does: every +// such operation either declares its 200 `data` as an empty object (the +// mutations) or declares no application/json 200 body at all (the CSV/file +// downloads, whose bytes arrive on Response.Raw instead). Either way RespTree, +// the generator's own read of the declared `data` fields, is empty. +// +// If an endpoint later grows a real payload, this fails rather than letting the +// CLI keep reporting "no payload" over a response it silently dropped — the SDK +// method must gain a data return first, which moves the command to +// printGenericResult on its own. +// +// What it does not prove: a spec that grew real fields while still declaring +// `data` as a free-form object would leave RespTree empty and slip past. The +// SDK method signature is the backstop there — a method with no data return has +// nowhere to decode such a payload into, so the CLI could not have printed it +// either way, and the fix is to declare the fields in the spec. +// +// Operations the SDK map does not answer for are skipped, which loses nothing: +// emitService skips those too (with a warning) and emits no command at all, and +// coverage_test in internal/cli fails when any spec operation has no command. +func TestNoPayloadOpsDeclareNoPayload(t *testing.T) { + services, sdk := loadRealServices(t) + + checked := 0 + for _, s := range services { + for _, o := range s.Ops { + mi, ok := sdk[s.Name][o.Method] + if !ok || mi.HasData { + continue + } + checked++ + if len(o.RespTree) > 0 { + names := make([]string, 0, len(o.RespTree)) + for _, f := range o.RespTree { + names = append(names, f.Wire) + } + t.Errorf("%s %s (%s) declares a 200 payload (%s) but %s.%s returns no data, so its command would acknowledge a response it discards", + o.HTTP, o.Path, o.OpID, strings.Join(names, ","), s.Name, o.Method) + } + } + } + if checked == 0 { + t.Fatal("found no operation without a data return — the lookup is broken, not the spec clean") + } + t.Logf("checked %d operations whose command acknowledges an empty response", checked) +} + +// TestEmittedNoDataCommandDelegatesToTheSharedHelper guards the generator side +// of the same contract: the emitted body must hand its response to the one +// helper that decides between streaming a download and reporting no payload, +// and must carry no acknowledgement string of its own. The committed generated +// files are checked separately (TestNoCommandEchoesItsRequestPath, in +// internal/cli), which would not catch a generator edited without regenerating. +func TestEmittedNoDataCommandDelegatesToTheSharedHelper(t *testing.T) { + services, sdk := loadRealServices(t) + + emitted := 0 + for _, s := range services { + for _, o := range s.Ops { + mi, ok := sdk[s.Name][o.Method] + if !ok || mi.HasData { + continue + } + emitted++ + src := emitCmd("genTestCmd", s, o, mi) + if !strings.Contains(src, "return printRawOrNoPayload(ctx, resp)") { + t.Fatalf("%s %s: emitted command does not render its response through printRawOrNoPayload:\n%s", o.HTTP, o.Path, src) + } + if strings.Contains(src, `"OK: `) { + t.Fatalf("%s %s: emitted command still carries a canned acknowledgement string:\n%s", o.HTTP, o.Path, src) + } + } + } + if emitted == 0 { + t.Fatal("emitted no command without a data return — the lookup is broken") + } + t.Logf("checked the emitted body of %d commands", emitted) +}