package service import ( "context" "encoding/json" "strings" "testing" "time" "git.yuntuo.hk/k2safe/orbit/internal/fault" core "git.yuntuo.hk/k2safe/orbit/model" "git.yuntuo.hk/k2safe/orbit/platform/auth" pulse "git.yuntuo.hk/k2safe/orbit/pulse/model" ) type recoveryValidationStore struct { Store reviews, retries int retry ManualRetryInput } func (s *recoveryValidationStore) ReviewTask(context.Context, auth.Token, core.ID, string, ReviewInput) (ReviewResult, error) { s.reviews++ return ReviewResult{}, nil } func (s *recoveryValidationStore) RetryTask(_ context.Context, _ auth.Token, _ core.ID, _ string, in ManualRetryInput) (ManualRetryResult, error) { s.retries++ s.retry = in return ManualRetryResult{}, nil } func TestRecoveryRequiresExplicitEvidenceAndConfirmation(t *testing.T) { id := core.ID(strings.Repeat("1", 32)) s := &recoveryValidationStore{} api := New(s) base := ReviewInput{ExpectedRevision: 7, Reason: "verified externally", EvidenceRef: "evidence:record-1", ExecutionSettled: true, Outcome: pulse.TaskFailed, Result: json.RawMessage(`{"n":9007199254740993}`)} for name, change := range map[string]func(*ReviewInput){ "no revision": func(v *ReviewInput) { v.ExpectedRevision = 0 }, "blank reason": func(v *ReviewInput) { v.Reason = " " }, "large reason": func(v *ReviewInput) { v.Reason = strings.Repeat("x", 1025) }, "empty evidence": func(v *ReviewInput) { v.EvidenceRef = "" }, "secret whitespace": func(v *ReviewInput) { v.EvidenceRef = "receipt:\nvalue" }, "too long evidence": func(v *ReviewInput) { v.EvidenceRef = strings.Repeat("x", 513) }, "unsettled": func(v *ReviewInput) { v.ExecutionSettled = false }, "reopen": func(v *ReviewInput) { v.Outcome = pulse.TaskWaiting }, "cancel": func(v *ReviewInput) { v.Outcome = pulse.TaskCancelled }, "null": func(v *ReviewInput) { v.Result = json.RawMessage(`null`) }, "duplicate fields": func(v *ReviewInput) { v.Result = json.RawMessage(`{"n":1,"n":2}`) }, "large result": func(v *ReviewInput) { v.Result = json.RawMessage(`"` + strings.Repeat("a", 256<<10) + `"`) }, } { t.Run(name, func(t *testing.T) { in := base change(&in) if _, err := api.ReviewTask(context.Background(), auth.Token{}, id, "review", in); fault.Code(err) != "INVALID_ARGUMENT" { t.Fatal(err) } }) } if s.reviews != 0 { t.Fatal("invalid review reached persistence") } if _, err := api.ReviewTask(context.Background(), auth.Token{}, id, "review", base); err != nil || s.reviews != 1 { t.Fatal(err) } in := ManualRetryInput{ExpectedRevision: 7, Reason: "verified retry", EvidenceRef: "evidence:retry", SafeToRetry: true, TaskTimeoutMS: 30000} for _, bad := range []ManualRetryInput{{ExpectedRevision: 7, Reason: in.Reason, EvidenceRef: in.EvidenceRef, TaskTimeoutMS: 30000}, {ExpectedRevision: 7, Reason: in.Reason, EvidenceRef: in.EvidenceRef, SafeToRetry: true}, {ExpectedRevision: 7, Reason: in.Reason, EvidenceRef: in.EvidenceRef, SafeToRetry: true, TaskTimeoutMS: 30*86400000 + 1}} { if _, err := api.RetryTask(context.Background(), auth.Token{}, id, "retry", bad); fault.Code(err) != "INVALID_ARGUMENT" { t.Fatal(err) } } tm := time.Date(2026, 9, 17, 12, 0, 0, 0, time.FixedZone("example", 8*60*60)) in.AvailableAt = &tm if _, err := api.RetryTask(context.Background(), auth.Token{}, id, "retry", in); err != nil || s.retries != 1 || s.retry.AvailableAt.Location() != time.UTC { t.Fatal(err) } }