package render import ( "bytes" "encoding/json" "errors" "os" "path/filepath" "testing" "github.com/kotoyuuko/zenmux-usage-cli/internal/api" ) func TestRenderJSONSingle_BytePassthrough(t *testing.T) { src, err := os.ReadFile(filepath.Join("..", "api", "testdata", "sample.json")) if err != nil { t.Fatal(err) } var buf bytes.Buffer if err := RenderJSONSingle(&buf, src); err != nil { t.Fatalf("RenderJSONSingle: %v", err) } // Output must parse as the same JSON body. var orig, got map[string]any if err := json.Unmarshal(src, &orig); err != nil { t.Fatalf("parse src: %v", err) } if err := json.Unmarshal(buf.Bytes(), &got); err != nil { t.Fatalf("parse out: %v", err) } origStr, _ := json.Marshal(orig) gotStr, _ := json.Marshal(got) if string(origStr) != string(gotStr) { t.Errorf("roundtrip mismatch:\nwant %s\n got %s", origStr, gotStr) } // Must end with a newline. if buf.Len() == 0 || buf.Bytes()[buf.Len()-1] != '\n' { t.Error("output should end with a newline") } } func TestRenderJSONMulti_ShapeAndOrder(t *testing.T) { src, _ := os.ReadFile(filepath.Join("..", "api", "testdata", "sample.json")) var r api.Response if err := json.Unmarshal(src, &r); err != nil { t.Fatalf("parse sample: %v", err) } results := []AccountResult{ {Name: "personal", Response: &r}, {Name: "work", Err: errors.New("authentication rejected: HTTP 401")}, {Name: "extra", Response: &api.Response{Success: false, Error: "quota exhausted"}}, } var buf bytes.Buffer if err := RenderJSONMulti(&buf, results); err != nil { t.Fatalf("RenderJSONMulti: %v", err) } var got []map[string]any if err := json.Unmarshal(buf.Bytes(), &got); err != nil { t.Fatalf("parse out: %v", err) } if len(got) != 3 { t.Fatalf("want 3 entries, got %d", len(got)) } if got[0]["account"] != "personal" || got[1]["account"] != "work" || got[2]["account"] != "extra" { t.Errorf("order wrong: %+v", got) } // personal: success=true, data non-null, error=null if got[0]["success"] != true { t.Error("personal.success should be true") } if got[0]["data"] == nil { t.Error("personal.data should be populated") } if got[0]["error"] != nil { t.Errorf("personal.error should be null, got %v", got[0]["error"]) } // work: success=false, data=null, error non-empty if got[1]["success"] != false { t.Error("work.success should be false") } if got[1]["data"] != nil { t.Error("work.data should be null") } if got[1]["error"] == nil || got[1]["error"].(string) == "" { t.Error("work.error should be non-empty") } // extra: API-level failure (success=false in body). data=null, error from payload. if got[2]["success"] != false { t.Error("extra.success should be false") } if got[2]["data"] != nil { t.Error("extra.data should be null") } if got[2]["error"].(string) != "quota exhausted" { t.Errorf("extra.error = %q, want quota exhausted", got[2]["error"]) } }