package recovery import ( "context" "encoding/json" "strings" "testing" core "git.yuntuo.hk/k2safe/orbit/model" "git.yuntuo.hk/k2safe/orbit/platform/auth" ) type captureStore struct{ calls int } func (s *captureStore) ReviewTask(context.Context, auth.Token, core.ID, string, ReviewTaskInput) (Result, error) { s.calls++ return Result{}, nil } func (s *captureStore) ResolveStep(context.Context, auth.Token, core.ID, string, ResolveStepInput) (Result, error) { s.calls++ return Result{}, nil } func (s *captureStore) Continue(context.Context, auth.Token, core.ID, string, ContinueInput) (Result, error) { s.calls++ return Result{}, nil } func (s *captureStore) RetryStep(context.Context, auth.Token, core.ID, string, RetryStepInput) (Result, error) { s.calls++ return Result{}, nil } func (s *captureStore) ContinuePoll(context.Context, auth.Token, core.ID, string, ContinuePollInput) (Result, error) { s.calls++ return Result{}, nil } func (s *captureStore) RearmWait(context.Context, auth.Token, core.ID, string, RearmWaitInput) (Result, error) { s.calls++ return Result{}, nil } func TestRecoveryValidation(t *testing.T) { id := core.ID(strings.Repeat("a", 32)) d := Decision{ExpectedRevision: 2, Reason: "核实外部结果", EvidenceRef: "evidence://case/1"} step := StepDecision{Decision: d, StepKey: "stage-1.v2", ActivationID: id, ExpectedStepRevision: 3} store := &captureStore{} s := New(store) ctx := context.Background() token := auth.Token{} if _, err := s.ReviewTask(ctx, token, id, "operation", ReviewTaskInput{Decision: d, TaskID: id, ExpectedTaskRevision: 4, ExecutionSettled: true, Outcome: "SUCCEEDED", Result: json.RawMessage(`{"n":9007199254740993}`)}); err != nil { t.Fatal(err) } if _, err := s.ResolveStep(ctx, token, id, "operation", ResolveStepInput{StepDecision: step, ExecutionSettled: true, Outcome: "SUCCEEDED", Result: json.RawMessage(`null`)}); err != nil { t.Fatal(err) } if _, err := s.Continue(ctx, token, id, "operation", ContinueInput{Decision: d, ReviewResolved: true}); err != nil { t.Fatal(err) } if _, err := s.RetryStep(ctx, token, id, "operation", RetryStepInput{StepDecision: step, SafeToRetry: true}); err != nil { t.Fatal(err) } for _, change := range []func(*Decision){func(v *Decision) { v.ExpectedRevision = 0 }, func(v *Decision) { v.Reason = " " }, func(v *Decision) { v.Reason = string([]byte{255}) }, func(v *Decision) { v.EvidenceRef = "secret bad" }, func(v *Decision) { v.EvidenceRef = "" }} { bad := d change(&bad) if _, err := s.Continue(ctx, token, id, "operation", ContinueInput{Decision: bad, ReviewResolved: true}); err == nil { t.Fatal("invalid decision accepted") } } for _, bad := range []ReviewTaskInput{ {Decision: d, TaskID: id, ExpectedTaskRevision: 4, Outcome: "SUCCEEDED", Result: json.RawMessage(`{}`)}, {Decision: d, TaskID: id, ExpectedTaskRevision: 4, ExecutionSettled: true, Outcome: "UNKNOWN", Result: json.RawMessage(`{}`)}, {Decision: d, TaskID: id, ExpectedTaskRevision: 4, ExecutionSettled: true, Outcome: "SUCCEEDED", Result: json.RawMessage(`null`)}, {Decision: d, TaskID: id, ExpectedTaskRevision: 4, ExecutionSettled: true, Outcome: "SUCCEEDED", Result: json.RawMessage(`{"n":1,"n":2}`)}, } { if _, err := s.ReviewTask(ctx, token, id, "operation", bad); err == nil { t.Fatal("unsafe execution review accepted") } } if _, err := s.Continue(ctx, token, id, "operation", ContinueInput{Decision: d}); err == nil { t.Fatal("unresolved review continued") } if _, err := s.RetryStep(ctx, token, id, "operation", RetryStepInput{StepDecision: step}); err == nil { t.Fatal("unsafe retry accepted") } if _, err := s.ContinuePoll(ctx, token, id, "operation", ContinuePollInput{StepDecision: step}); err == nil { t.Fatal("unconfirmed pending accepted") } if _, err := s.RearmWait(ctx, token, id, "operation", RearmWaitInput{StepDecision: step}); err == nil { t.Fatal("unverified wait accepted") } if _, err := s.ContinuePoll(ctx, token, id, "operation", ContinuePollInput{StepDecision: step, PendingConfirmed: true}); err != nil { t.Fatal(err) } if _, err := s.RearmWait(ctx, token, id, "operation", RearmWaitInput{StepDecision: step, WaitVerified: true}); err != nil { t.Fatal(err) } if store.calls != 6 { t.Fatal("invalid inputs reached storage", store.calls) } }