package main import ( "context" "errors" "net/http" "net/http/httptest" "strings" "testing" "git.cin.su/m/Assistant/adapters/postgres" commitcontract "git.cin.su/m/Assistant/contracts/commit" "git.cin.su/m/Assistant/modules/commit/ports" ) type fakeExecutionRepository struct { action commitcontract.AuthorizedAction attempts []commitcontract.ActionAttempt } func (f *fakeExecutionRepository) LoadAuthorized(context.Context, commitcontract.AuthorizedActionID) (commitcontract.AuthorizedAction, error) { return f.action, nil } func (f *fakeExecutionRepository) LoadLatestAttempt(context.Context, commitcontract.AuthorizedActionID) (commitcontract.ActionAttempt, error) { if len(f.attempts) == 0 { return commitcontract.ActionAttempt{}, postgres.ErrActionAttemptNotFound } return f.attempts[len(f.attempts)-1], nil } func (f *fakeExecutionRepository) BeginAttempt(_ context.Context, attempt commitcontract.ActionAttempt) error { f.attempts = append(f.attempts, attempt) return nil } func (f *fakeExecutionRepository) FinishAttempt(_ context.Context, attempt commitcontract.ActionAttempt) error { if len(f.attempts) == 0 || f.attempts[len(f.attempts)-1].ID != attempt.ID { return errors.New("attempt not begun") } f.attempts[len(f.attempts)-1] = attempt return nil } func (f *fakeExecutionRepository) SaveAuthorized(_ context.Context, action commitcontract.AuthorizedAction) error { f.action = action return nil } type knownExecutor struct{ calls int } func (e *knownExecutor) Execute(commitcontract.AuthorizedAction) ports.ExecutionResult { e.calls++ return ports.ExecutionResult{Known: true, Succeeded: true} } func (e *knownExecutor) Reconcile(commitcontract.AuthorizedAction) ports.ExecutionResult { return ports.ExecutionResult{Known: true, Succeeded: true} } func TestExecutionIsDurablyBegunAndCannotBeBlindlyRepeated(t *testing.T) { repository := &fakeExecutionRepository{action: commitcontract.AuthorizedAction{ID: "authorized-1", State: commitcontract.ActionAuthorized, Proposal: commitcontract.ActionProposal{IntentID: "intent-1"}}} executor := &knownExecutor{} mux := http.NewServeMux() registerExecutionRoutes(mux, repository, executor, executor, "secret") request := httptest.NewRequest(http.MethodPost, "/v1/internal/authorized-actions/authorized-1/execute", strings.NewReader(`{"AttemptID":"attempt-1"}`)) request.Header.Set("Authorization", "Bearer secret") result := httptest.NewRecorder() mux.ServeHTTP(result, request) if result.Code != http.StatusOK || executor.calls != 1 || repository.attempts[0].State != commitcontract.ActionSucceeded { t.Fatalf("status=%d calls=%d attempts=%#v", result.Code, executor.calls, repository.attempts) } repeated := httptest.NewRequest(http.MethodPost, "/v1/internal/authorized-actions/authorized-1/execute", strings.NewReader(`{"AttemptID":"attempt-2"}`)) repeated.Header.Set("Authorization", "Bearer secret") repeatedResult := httptest.NewRecorder() mux.ServeHTTP(repeatedResult, repeated) if repeatedResult.Code != http.StatusConflict || executor.calls != 1 { t.Fatalf("repeat status=%d calls=%d", repeatedResult.Code, executor.calls) } }