package operator import ( "path/filepath" "strings" "testing" ) func fixture(t *testing.T, name string) string { t.Helper() return filepath.Join("..", "..", "testdata", "operator", name) } func TestLoadScenarioValidatesExample(t *testing.T) { sc, err := LoadScenario(fixture(t, "api_connection_smoke.yaml")) if err != nil { t.Fatalf("LoadScenario returned error: %v", err) } if sc.Name != "api_connection_smoke" { t.Errorf("name = %q, want api_connection_smoke", sc.Name) } if len(sc.Steps) != 1 { t.Fatalf("steps = %d, want 1", len(sc.Steps)) } if sc.Steps[0].Action != ActionHello { t.Errorf("step action = %q, want %q", sc.Steps[0].Action, ActionHello) } } func TestLoadScenarioRejectsUnknownAction(t *testing.T) { data := []byte("name: unknown_action\ntimeout: 1s\nsteps:\n - id: unknown\n action: send_unsupported_request\n expect:\n status: error\n") _, err := ParseScenario(data) if err == nil { t.Fatal("expected error for unknown action, got nil") } if !strings.Contains(err.Error(), "unknown action") { t.Errorf("error = %v, want it to mention unknown action", err) } } func TestLoadScenarioRejectsMissingStepID(t *testing.T) { data := []byte("name: missing_id\ntimeout: 1s\nsteps:\n - action: hello\n") _, err := ParseScenario(data) if err == nil { t.Fatal("expected error for missing step id, got nil") } if !strings.Contains(err.Error(), "id is required") { t.Errorf("error = %v, want it to mention id is required", err) } } func TestParseScenarioRejectsInvalidDuration(t *testing.T) { data := []byte("name: invalid_timeout\ntimeout: not-a-duration\nsteps:\n - id: hello\n action: hello\n") _, err := ParseScenario(data) if err == nil { t.Fatal("expected error for invalid timeout, got nil") } if !strings.Contains(err.Error(), "invalid timeout") { t.Errorf("error = %v, want it to mention invalid timeout", err) } } func TestValidateRejectsUnknownExpectStatus(t *testing.T) { data := []byte("name: bad_expect\ntimeout: 1s\nsteps:\n - id: hello\n action: hello\n expect:\n status: maybe\n") _, err := ParseScenario(data) if err == nil { t.Fatal("expected error for unknown expect.status, got nil") } if !strings.Contains(err.Error(), "expect.status") { t.Errorf("error = %v, want it to mention expect.status", err) } } func TestValidateRejectsErrorCodeWithoutErrorStatus(t *testing.T) { data := []byte("name: bad_code\ntimeout: 1s\nsteps:\n - id: hello\n action: hello\n expect:\n status: ok\n error_code: invalid_request\n") _, err := ParseScenario(data) if err == nil { t.Fatal("expected error for error_code without error status, got nil") } if !strings.Contains(err.Error(), "error_code") { t.Errorf("error = %v, want it to mention error_code", err) } } func TestInvalidRequestMatrixDryRunCoverage(t *testing.T) { tests := []struct { name string yaml string expectedErr string }{ { name: "missing_run_id", yaml: ` name: missing_run_id timeout: 1s steps: - id: step1 action: get_backtest_result request: {} `, expectedErr: "requires request.run_id", }, { name: "missing_strategy_id", yaml: ` name: missing_strategy_id timeout: 1s steps: - id: step1 action: start_backtest request: market: kr timeframe: daily from_unix_ms: 100 to_unix_ms: 200 `, expectedErr: "requires request.strategy_id", }, { name: "empty_compare_id", yaml: ` name: empty_compare_id timeout: 1s steps: - id: step1 action: compare_backtest_runs request: run_ids: [""] `, expectedErr: "cannot contain empty or blank values", }, { name: "invalid_market", yaml: ` name: invalid_market timeout: 1s steps: - id: step1 action: start_backtest request: strategy_id: strat1 market: invalid_market timeframe: daily from_unix_ms: 100 to_unix_ms: 200 `, expectedErr: "unsupported market", }, { name: "invalid_timeframe", yaml: ` name: invalid_timeframe timeout: 1s steps: - id: step1 action: start_backtest request: strategy_id: strat1 market: kr timeframe: invalid_timeframe from_unix_ms: 100 to_unix_ms: 200 `, expectedErr: "unsupported timeframe", }, { name: "invalid_date_range", yaml: ` name: invalid_date_range timeout: 1s steps: - id: step1 action: start_backtest request: strategy_id: strat1 market: kr timeframe: daily from_unix_ms: 200 to_unix_ms: 100 `, expectedErr: "cannot be after request.to_unix_ms", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := ParseScenario([]byte(tt.yaml)) if err == nil { t.Fatalf("expected error containing %q, got nil", tt.expectedErr) } if !strings.Contains(err.Error(), tt.expectedErr) { t.Errorf("error = %v, want it to contain %q", err, tt.expectedErr) } }) } }