Add CLI commands with structured errors and robot-mode behavior

Three cobra commands forming the CLI surface:
- root: fetch and filter weekly deals (--store/--zip with BOGO,
  category, department, query, and limit filters)
- stores: list nearby Publix locations by ZIP code
- categories: show available deal categories with counts

Structured error system with typed error codes (INVALID_ARGS,
NOT_FOUND, UPSTREAM_ERROR, INTERNAL_ERROR) and semantic exit codes
(0-4). Errors render as human-readable text or JSON depending on
output mode. Robot-mode features: auto-JSON when stdout is not a TTY,
compact quick-start help when invoked with no args, and JSON error
payloads for programmatic consumers.
This commit is contained in:
2026-02-22 21:12:19 -05:00
parent 73d55bc30e
commit cca04bc11c
5 changed files with 653 additions and 0 deletions

52
cmd/robot_mode_test.go Normal file
View File

@@ -0,0 +1,52 @@
package cmd
import (
"bytes"
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestShouldAutoJSON(t *testing.T) {
assert.True(t, shouldAutoJSON([]string{"stores", "--zip", "33101"}, false))
assert.False(t, shouldAutoJSON([]string{"stores", "--zip", "33101", "--json"}, false))
assert.False(t, shouldAutoJSON([]string{"completion", "zsh"}, false))
assert.False(t, shouldAutoJSON([]string{"--help"}, false))
assert.False(t, shouldAutoJSON([]string{"stores", "--zip", "33101"}, true))
}
func TestFirstCommand_SkipsFlagValues(t *testing.T) {
cmd := firstCommand([]string{"--zip", "33101", "stores"})
assert.Equal(t, "stores", cmd)
}
func TestPrintQuickStart_JSON(t *testing.T) {
var buf bytes.Buffer
err := printQuickStart(&buf, true)
require.NoError(t, err)
var payload quickStartJSON
err = json.Unmarshal(buf.Bytes(), &payload)
require.NoError(t, err)
assert.Equal(t, "pubcli", payload.Name)
assert.NotEmpty(t, payload.Usage)
assert.Len(t, payload.Examples, 3)
}
func TestPrintCLIErrorJSON(t *testing.T) {
var buf bytes.Buffer
err := printCLIErrorJSON(&buf, classifyCLIError(invalidArgsError("bad flag", "pubcli --zip 33101")))
require.NoError(t, err)
var payload map[string]any
err = json.Unmarshal(buf.Bytes(), &payload)
require.NoError(t, err)
errorObject, ok := payload["error"].(map[string]any)
require.True(t, ok)
assert.Equal(t, "INVALID_ARGS", errorObject["code"])
assert.Equal(t, "bad flag", errorObject["message"])
}