alt/apps/cli/internal/operator/handoff_test.go
toki 473934212c refactor: operator workflow G07 completed and archived
- Move G07 import CLI scenario docs to archive
- Update operator client, runner, scenario implementations
- Add kis_daily_import_smoke test fixtures
- Update handoff and output tests
2026-06-03 21:26:06 +09:00

142 lines
4.2 KiB
Go

package operator
import (
"bufio"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
)
// requiredScenarios are the six headless validation scenarios the milestone
// handoff table requires evidence for. The handoff matrix and the expected
// output fixtures must cover all of them so the Flutter Operator Console MVP
// can validate each workflow before building any screen.
var requiredScenarios = []string{
"api_connection_smoke",
"market_data_status_query",
"backtest_run_request",
"backtest_run_polling",
"backtest_result_summary",
"invalid_request_matrix",
"kis_daily_import_smoke",
}
func handoffMatrixPath() string {
return filepath.Join("..", "..", "testdata", "operator", "headless_validation.md")
}
func expectedFixturePath(scenario string) string {
return filepath.Join("..", "..", "testdata", "operator", "expected", scenario+".jsonl")
}
// TestHeadlessValidationHandoffCoversRequiredScenarios fails if the handoff
// matrix drops any required scenario, which would leave the Flutter MVP without
// command/field evidence for that workflow.
func TestHeadlessValidationHandoffCoversRequiredScenarios(t *testing.T) {
data, err := os.ReadFile(handoffMatrixPath())
if err != nil {
t.Fatalf("read handoff matrix: %v", err)
}
doc := string(data)
for _, s := range requiredScenarios {
if !strings.Contains(doc, "`"+s+"`") {
t.Errorf("handoff matrix missing scenario %q", s)
}
}
}
// TestExpectedOutputFixturesAreValidJSONLines checks that every expected output
// fixture parses as JSON lines and that each record carries the machine-readable
// keys the handoff contract promises: scenario/status on every line, action on
// step lines, plus at least one step and one summary line.
func TestExpectedOutputFixturesAreValidJSONLines(t *testing.T) {
for _, s := range requiredScenarios {
path := expectedFixturePath(s)
f, err := os.Open(path)
if err != nil {
t.Errorf("open expected fixture %q: %v", path, err)
continue
}
scanner := bufio.NewScanner(f)
lineNo := 0
sawStep := false
sawSummary := false
for scanner.Scan() {
lineNo++
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
var rec map[string]any
if err := json.Unmarshal([]byte(line), &rec); err != nil {
t.Errorf("%s:%d not valid JSON: %v", path, lineNo, err)
continue
}
if rec["scenario"] != s {
t.Errorf("%s:%d scenario = %v, want %q", path, lineNo, rec["scenario"], s)
}
if _, ok := rec["status"]; !ok {
t.Errorf("%s:%d missing status field", path, lineNo)
}
switch rec["type"] {
case "step":
sawStep = true
if _, ok := rec["action"]; !ok {
t.Errorf("%s:%d step line missing action field", path, lineNo)
}
case "summary":
sawSummary = true
default:
t.Errorf("%s:%d unexpected type %v", path, lineNo, rec["type"])
}
}
if err := scanner.Err(); err != nil {
t.Errorf("scan %q: %v", path, err)
}
f.Close()
if !sawStep {
t.Errorf("%s has no step line", path)
}
if !sawSummary {
t.Errorf("%s has no summary line", path)
}
}
}
// TestHandoffDocumentedScenarioFixturesExist guards the handoff matrix against
// referencing fixture paths that do not exist. Each required scenario must have
// an input YAML fixture and an expected output fixture on disk, and the matrix
// must reference both relative paths so the documented commands stay runnable.
func TestHandoffDocumentedScenarioFixturesExist(t *testing.T) {
matrix, err := os.ReadFile(handoffMatrixPath())
if err != nil {
t.Fatalf("read handoff matrix: %v", err)
}
doc := string(matrix)
for _, s := range requiredScenarios {
inputPath := filepath.Join("..", "..", "testdata", "operator", s+".yaml")
if _, err := os.Stat(inputPath); err != nil {
t.Errorf("input fixture for %q missing: %v", s, err)
}
if _, err := os.Stat(expectedFixturePath(s)); err != nil {
t.Errorf("expected fixture for %q missing: %v", s, err)
}
inputRel := "testdata/operator/" + s + ".yaml"
expectedRel := "testdata/operator/expected/" + s + ".jsonl"
if !strings.Contains(doc, inputRel) {
t.Errorf("handoff matrix does not reference input path %q", inputRel)
}
if !strings.Contains(doc, expectedRel) {
t.Errorf("handoff matrix does not reference expected path %q", expectedRel)
}
}
}