|
1 | 1 | package cli |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bytes" |
4 | 5 | "encoding/json" |
5 | 6 | "strings" |
6 | 7 | "testing" |
| 8 | + |
| 9 | + "github.com/spf13/cobra" |
7 | 10 | ) |
8 | 11 |
|
9 | 12 | // incidentRow / alertRow are multi-field stub payloads with the nested blobs |
@@ -235,3 +238,155 @@ func TestFieldsUnknownFieldErrors(t *testing.T) { |
235 | 238 | }) |
236 | 239 | } |
237 | 240 | } |
| 241 | + |
| 242 | +func TestIncidentSimilarStructuredProjection(t *testing.T) { |
| 243 | + saveAndResetGlobals(t) |
| 244 | + stub := newGFStub(t) |
| 245 | + items := make([]any, 20) |
| 246 | + for i := range items { |
| 247 | + row := incidentRow() |
| 248 | + row["incident_id"] = "similar-" + string(rune('a'+i)) |
| 249 | + row["close_time"] = 1712000060 |
| 250 | + row["ack_time"] = 1712000030 |
| 251 | + row["alert_cnt"] = 3 |
| 252 | + row["root_cause"] = "disk exhaustion" |
| 253 | + row["score"] = 0.9 |
| 254 | + row["description"] = strings.Repeat("large description ", 100) |
| 255 | + row["images"] = []map[string]any{{"src": strings.Repeat("https://example.test/image/", 100)}} |
| 256 | + items[i] = row |
| 257 | + } |
| 258 | + stub.data = map[string]any{"items": items, "total": len(items)} |
| 259 | + |
| 260 | + out, err := execCommand("incident", "similar", "inc-1", "--limit", "20", "--output-format", "json") |
| 261 | + if err != nil { |
| 262 | + t.Fatalf("execCommand: %v", err) |
| 263 | + } |
| 264 | + if len(out) >= 16*1024 { |
| 265 | + t.Fatalf("compact similar output is %d bytes, want <16 KiB", len(out)) |
| 266 | + } |
| 267 | + |
| 268 | + var rows []map[string]json.RawMessage |
| 269 | + if err := json.Unmarshal([]byte(strings.TrimSpace(out)), &rows); err != nil { |
| 270 | + t.Fatalf("parse compact similar json: %v\n%s", err, out) |
| 271 | + } |
| 272 | + want := []string{"incident_id", "title", "incident_severity", "progress", "start_time", "close_time", "ack_time", "alert_cnt", "root_cause", "score"} |
| 273 | + if len(rows) != len(items) { |
| 274 | + t.Fatalf("got %d rows, want %d", len(rows), len(items)) |
| 275 | + } |
| 276 | + for _, row := range rows { |
| 277 | + if len(row) != len(want) { |
| 278 | + t.Fatalf("got keys %v, want exactly %v", row, want) |
| 279 | + } |
| 280 | + for _, field := range want { |
| 281 | + if _, ok := row[field]; !ok { |
| 282 | + t.Errorf("projected row missing %q", field) |
| 283 | + } |
| 284 | + } |
| 285 | + if _, ok := row["description"]; ok { |
| 286 | + t.Errorf("projected row includes description: %v", row) |
| 287 | + } |
| 288 | + } |
| 289 | + |
| 290 | +} |
| 291 | + |
| 292 | +func TestIncidentDetailFieldsProjection(t *testing.T) { |
| 293 | + saveAndResetGlobals(t) |
| 294 | + stub := newGFStub(t) |
| 295 | + row := incidentRow() |
| 296 | + row["description"] = strings.Repeat("large description ", 500) |
| 297 | + row["images"] = []map[string]any{{"src": strings.Repeat("https://example.test/image/", 100)}} |
| 298 | + stub.data = row |
| 299 | + |
| 300 | + out, err := execCommand("incident", "detail", "inc-1", "--fields", "incident_id,title,root_cause", "--output-format", "json") |
| 301 | + if err != nil { |
| 302 | + t.Fatalf("execCommand: %v", err) |
| 303 | + } |
| 304 | + if len(out) >= 8*1024 { |
| 305 | + t.Fatalf("projected detail output is %d bytes, want <8 KiB", len(out)) |
| 306 | + } |
| 307 | + var detail map[string]json.RawMessage |
| 308 | + if err := json.Unmarshal([]byte(strings.TrimSpace(out)), &detail); err != nil { |
| 309 | + t.Fatalf("parse projected detail json: %v\n%s", err, out) |
| 310 | + } |
| 311 | + if len(detail) != 3 || detail["incident_id"] == nil || detail["title"] == nil || detail["root_cause"] == nil { |
| 312 | + t.Fatalf("projected detail = %v, want exactly incident_id,title,root_cause", detail) |
| 313 | + } |
| 314 | + if _, ok := detail["description"]; ok { |
| 315 | + t.Errorf("projected detail includes description: %v", detail) |
| 316 | + } |
| 317 | + |
| 318 | +} |
| 319 | + |
| 320 | +func TestAlertEventListStructuredProjection(t *testing.T) { |
| 321 | + saveAndResetGlobals(t) |
| 322 | + stub := newGFStub(t) |
| 323 | + items := make([]any, 30) |
| 324 | + for i := range items { |
| 325 | + items[i] = map[string]any{ |
| 326 | + "event_id": "event-" + string(rune('a'+i)), |
| 327 | + "alert_id": "alert-1", |
| 328 | + "event_severity": "Warning", |
| 329 | + "event_status": "Triggered", |
| 330 | + "event_time": 1712000000, |
| 331 | + "title": "CPU high", |
| 332 | + "description": strings.Repeat("large description ", 100), |
| 333 | + "labels": map[string]any{"host": "web-01"}, |
| 334 | + "images": []map[string]any{{"src": strings.Repeat("https://example.test/image/", 100)}}, |
| 335 | + } |
| 336 | + } |
| 337 | + stub.data = map[string]any{"items": items, "total": len(items)} |
| 338 | + |
| 339 | + out, err := execCommand("alert-event", "list", "--limit", "30", "--output-format", "json") |
| 340 | + if err != nil { |
| 341 | + t.Fatalf("execCommand: %v", err) |
| 342 | + } |
| 343 | + if len(out) >= 16*1024 { |
| 344 | + t.Fatalf("compact alert-event output is %d bytes, want <16 KiB", len(out)) |
| 345 | + } |
| 346 | + var rows []map[string]json.RawMessage |
| 347 | + if err := json.Unmarshal([]byte(strings.TrimSpace(out)), &rows); err != nil { |
| 348 | + t.Fatalf("parse compact alert-event json: %v\n%s", err, out) |
| 349 | + } |
| 350 | + want := []string{"event_id", "alert_id", "event_severity", "event_status", "event_time", "title"} |
| 351 | + if len(rows) != len(items) { |
| 352 | + t.Fatalf("got %d rows, want %d", len(rows), len(items)) |
| 353 | + } |
| 354 | + for _, row := range rows { |
| 355 | + if len(row) != len(want) { |
| 356 | + t.Fatalf("got keys %v, want exactly %v", row, want) |
| 357 | + } |
| 358 | + for _, field := range want { |
| 359 | + if _, ok := row[field]; !ok { |
| 360 | + t.Errorf("projected row missing %q", field) |
| 361 | + } |
| 362 | + } |
| 363 | + } |
| 364 | + |
| 365 | +} |
| 366 | + |
| 367 | +func TestStructuredFieldsEmptyErrors(t *testing.T) { |
| 368 | + cases := []struct { |
| 369 | + name string |
| 370 | + cmd func() *cobra.Command |
| 371 | + args []string |
| 372 | + }{ |
| 373 | + {"incident similar", newIncidentSimilarCmd, []string{"inc-1", "--fields", ""}}, |
| 374 | + {"incident detail", newIncidentDetailCmd, []string{"inc-1", "--fields", ""}}, |
| 375 | + {"alert-event list", newAlertEventListCmd, []string{"--fields", ""}}, |
| 376 | + } |
| 377 | + for _, tc := range cases { |
| 378 | + t.Run(tc.name, func(t *testing.T) { |
| 379 | + saveAndResetGlobals(t) |
| 380 | + newGFStub(t) |
| 381 | + flagOutputFormat = "json" |
| 382 | + cmd := tc.cmd() |
| 383 | + cmd.SetOut(new(bytes.Buffer)) |
| 384 | + cmd.SetErr(cmd.OutOrStderr()) |
| 385 | + cmd.SetArgs(tc.args) |
| 386 | + err := cmd.Execute() |
| 387 | + if err == nil || !strings.Contains(err.Error(), "--fields") { |
| 388 | + t.Fatalf("empty --fields error = %v, want --fields validation", err) |
| 389 | + } |
| 390 | + }) |
| 391 | + } |
| 392 | +} |
0 commit comments