@@ -1594,6 +1594,131 @@ type readerFunc func([]byte) (int, error)
15941594
15951595func (f readerFunc ) Read (p []byte ) (int , error ) { return f (p ) }
15961596
1597+ // ---------------------------------------------------------------------------
1598+ // Structured list truncation indicator
1599+ // ---------------------------------------------------------------------------
1600+
1601+ // TestCommandAlertListStructuredAnnouncesTruncation pins that a structured
1602+ // list page which doesn't cover the server-reported total says so on stderr:
1603+ // stdout is reserved for the jq/toon pipeline, so without the note a consumer
1604+ // sees the default --limit page as the whole set. Table mode keeps its
1605+ // "Showing N results" footer on stdout and emits no stderr note.
1606+ func TestCommandAlertListStructuredAnnouncesTruncation (t * testing.T ) {
1607+ twoOfFive := map [string ]any {"items" : []any {alertRow (), alertRow ()}, "total" : 5 }
1608+
1609+ t .Run ("json page short of total" , func (t * testing.T ) {
1610+ saveAndResetGlobals (t )
1611+ stub := newGFStub (t )
1612+ stub .data = twoOfFive
1613+
1614+ out , stderrText , err := execCommandSplit ("alert" , "list" , "--output-format" , "json" )
1615+ if err != nil {
1616+ t .Fatalf ("execCommandSplit: %v" , err )
1617+ }
1618+ var rows []map [string ]any
1619+ if err := json .Unmarshal ([]byte (strings .TrimSpace (out )), & rows ); err != nil {
1620+ t .Fatalf ("stdout must stay parseable JSON: %v\n %s" , err , out )
1621+ }
1622+ if len (rows ) != 2 {
1623+ t .Fatalf ("got %d rows, want the 2 the page carries" , len (rows ))
1624+ }
1625+ if ! strings .Contains (stderrText , "note: showing 2 of 5 total results (page 1)" ) {
1626+ t .Errorf ("truncated structured page should announce itself on stderr, got:\n %s" , stderrText )
1627+ }
1628+ })
1629+
1630+ t .Run ("json page covers total" , func (t * testing.T ) {
1631+ saveAndResetGlobals (t )
1632+ stub := newGFStub (t )
1633+ stub .data = map [string ]any {"items" : []any {alertRow (), alertRow ()}, "total" : 2 }
1634+
1635+ _ , stderrText , err := execCommandSplit ("alert" , "list" , "--output-format" , "json" )
1636+ if err != nil {
1637+ t .Fatalf ("execCommandSplit: %v" , err )
1638+ }
1639+ if strings .Contains (stderrText , "note: showing" ) {
1640+ t .Errorf ("a page covering the total must not cry truncation, got:\n %s" , stderrText )
1641+ }
1642+ })
1643+
1644+ t .Run ("table keeps the footer on stdout" , func (t * testing.T ) {
1645+ saveAndResetGlobals (t )
1646+ stub := newGFStub (t )
1647+ stub .data = twoOfFive
1648+
1649+ out , stderrText , err := execCommandSplit ("alert" , "list" )
1650+ if err != nil {
1651+ t .Fatalf ("execCommandSplit: %v" , err )
1652+ }
1653+ if ! strings .Contains (out , "Showing 2 results (page 1, total 5)." ) {
1654+ t .Errorf ("table mode should keep the stdout footer, got:\n %s" , out )
1655+ }
1656+ if strings .Contains (stderrText , "note: showing" ) {
1657+ t .Errorf ("table mode already footers the count; no stderr note wanted, got:\n %s" , stderrText )
1658+ }
1659+ })
1660+ }
1661+
1662+ // ---------------------------------------------------------------------------
1663+ // Projection overflow is a hard failure
1664+ // ---------------------------------------------------------------------------
1665+
1666+ // TestCommandListProjectionOverflowFails pins that a compact list projection
1667+ // which cannot fit the byte budget fails the command instead of emitting
1668+ // anything: Execute returns the error and stdout stays empty, so a pipeline
1669+ // reading stdout sees a failed call, never an empty page masquerading as
1670+ // "no data".
1671+ func TestCommandListProjectionOverflowFails (t * testing.T ) {
1672+ t .Run ("incident list" , func (t * testing.T ) {
1673+ saveAndResetGlobals (t )
1674+ stub := newGFStub (t )
1675+ items := make ([]any , 100 )
1676+ for i := range items {
1677+ row := incidentRow ()
1678+ row ["incident_id" ] = fmt .Sprintf ("inc-%024d" , i )
1679+ row ["title" ] = strings .Repeat ("x" , 200 )
1680+ items [i ] = row
1681+ }
1682+ stub .data = map [string ]any {"items" : items , "total" : len (items )}
1683+
1684+ out , stderrText , err := execCommandSplit ("incident" , "list" , "--limit" , "100" , "--output-format" , "json" )
1685+ if err == nil || ! strings .Contains (err .Error (), "exceeds the 16384-byte limit" ) {
1686+ t .Fatalf ("irreducible projection error = %v, want the byte-limit refusal" , err )
1687+ }
1688+ if out != "" {
1689+ t .Errorf ("a failed projection must write nothing to stdout, got %d bytes" , len (out ))
1690+ }
1691+ if strings .Contains (stderrText , "exceeds the" ) {
1692+ t .Errorf ("the error is returned for the entrypoint to report, not printed mid-run, got:\n %s" , stderrText )
1693+ }
1694+ })
1695+
1696+ t .Run ("alert-event list" , func (t * testing.T ) {
1697+ saveAndResetGlobals (t )
1698+ stub := newGFStub (t )
1699+ items := make ([]any , 100 )
1700+ for i := range items {
1701+ items [i ] = map [string ]any {
1702+ "event_id" : fmt .Sprintf ("%024x" , i ),
1703+ "alert_id" : fmt .Sprintf ("%024x" , i + 1_000_000 ),
1704+ "event_severity" : "Warning" ,
1705+ "event_status" : "Triggered" ,
1706+ "event_time" : 1712000000 + i ,
1707+ "title" : strings .Repeat ("x" , 200 ),
1708+ }
1709+ }
1710+ stub .data = map [string ]any {"items" : items , "total" : len (items )}
1711+
1712+ out , _ , err := execCommandSplit ("alert-event" , "list" , "--limit" , "100" , "--output-format" , "json" )
1713+ if err == nil || ! strings .Contains (err .Error (), "exceeds the 16384-byte limit" ) {
1714+ t .Fatalf ("irreducible projection error = %v, want the byte-limit refusal" , err )
1715+ }
1716+ if out != "" {
1717+ t .Errorf ("a failed projection must write nothing to stdout, got %d bytes" , len (out ))
1718+ }
1719+ })
1720+ }
1721+
15971722// ---------------------------------------------------------------------------
15981723// Helpers
15991724// ---------------------------------------------------------------------------
0 commit comments