json_test.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. package render
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "errors"
  6. "os"
  7. "path/filepath"
  8. "testing"
  9. "github.com/kotoyuuko/zenmux-usage-cli/internal/api"
  10. )
  11. func TestRenderJSONSingle_BytePassthrough(t *testing.T) {
  12. src, err := os.ReadFile(filepath.Join("..", "api", "testdata", "sample.json"))
  13. if err != nil {
  14. t.Fatal(err)
  15. }
  16. var buf bytes.Buffer
  17. if err := RenderJSONSingle(&buf, src); err != nil {
  18. t.Fatalf("RenderJSONSingle: %v", err)
  19. }
  20. // Output must parse as the same JSON body.
  21. var orig, got map[string]any
  22. if err := json.Unmarshal(src, &orig); err != nil {
  23. t.Fatalf("parse src: %v", err)
  24. }
  25. if err := json.Unmarshal(buf.Bytes(), &got); err != nil {
  26. t.Fatalf("parse out: %v", err)
  27. }
  28. origStr, _ := json.Marshal(orig)
  29. gotStr, _ := json.Marshal(got)
  30. if string(origStr) != string(gotStr) {
  31. t.Errorf("roundtrip mismatch:\nwant %s\n got %s", origStr, gotStr)
  32. }
  33. // Must end with a newline.
  34. if buf.Len() == 0 || buf.Bytes()[buf.Len()-1] != '\n' {
  35. t.Error("output should end with a newline")
  36. }
  37. }
  38. func TestRenderJSONMulti_ShapeAndOrder(t *testing.T) {
  39. src, _ := os.ReadFile(filepath.Join("..", "api", "testdata", "sample.json"))
  40. var r api.Response
  41. if err := json.Unmarshal(src, &r); err != nil {
  42. t.Fatalf("parse sample: %v", err)
  43. }
  44. results := []AccountResult{
  45. {Name: "personal", Response: &r},
  46. {Name: "work", Err: errors.New("authentication rejected: HTTP 401")},
  47. {Name: "extra", Response: &api.Response{Success: false, Error: "quota exhausted"}},
  48. }
  49. var buf bytes.Buffer
  50. if err := RenderJSONMulti(&buf, results); err != nil {
  51. t.Fatalf("RenderJSONMulti: %v", err)
  52. }
  53. var got []map[string]any
  54. if err := json.Unmarshal(buf.Bytes(), &got); err != nil {
  55. t.Fatalf("parse out: %v", err)
  56. }
  57. if len(got) != 3 {
  58. t.Fatalf("want 3 entries, got %d", len(got))
  59. }
  60. if got[0]["account"] != "personal" || got[1]["account"] != "work" || got[2]["account"] != "extra" {
  61. t.Errorf("order wrong: %+v", got)
  62. }
  63. // personal: success=true, data non-null, error=null
  64. if got[0]["success"] != true {
  65. t.Error("personal.success should be true")
  66. }
  67. if got[0]["data"] == nil {
  68. t.Error("personal.data should be populated")
  69. }
  70. if got[0]["error"] != nil {
  71. t.Errorf("personal.error should be null, got %v", got[0]["error"])
  72. }
  73. // work: success=false, data=null, error non-empty
  74. if got[1]["success"] != false {
  75. t.Error("work.success should be false")
  76. }
  77. if got[1]["data"] != nil {
  78. t.Error("work.data should be null")
  79. }
  80. if got[1]["error"] == nil || got[1]["error"].(string) == "" {
  81. t.Error("work.error should be non-empty")
  82. }
  83. // extra: API-level failure (success=false in body). data=null, error from payload.
  84. if got[2]["success"] != false {
  85. t.Error("extra.success should be false")
  86. }
  87. if got[2]["data"] != nil {
  88. t.Error("extra.data should be null")
  89. }
  90. if got[2]["error"].(string) != "quota exhausted" {
  91. t.Errorf("extra.error = %q, want quota exhausted", got[2]["error"])
  92. }
  93. }