package execution_test import ( "errors" "sync" "testing" "time" "github.com/kami/hexis/internal/domain" ) // The audit trail is only useful if it survives a round trip through storage: // "which confirmation authorized this destructive act" must be answerable from // a plain read of the execution, not just from the in-memory struct the engine // happened to return. func TestExecute_AuditFieldsRoundTripThroughStorage(t *testing.T) { prov := &fakeProvider{name: "fake"} eng, store := newTestEngine(t, prov) cap := mustCreateCapability(t, store, func(c *domain.Capability) { c.RequiresConfirmation = true c.Risk = "destructive" }) args := map[string]any{"foo": "bar"} conf, err := eng.CreateConfirmation(cap.ID, "ent_x", "kami", args) if err != nil { t.Fatalf("create confirmation: %v", err) } res, err := eng.Execute(&domain.ExecuteRequest{ CapabilityID: cap.ID, TargetEntityID: "ent_x", Arguments: args, ConfirmationID: conf.ID, CorrelationID: "corr_1", CausationID: "caus_1", IdempotencyKey: "idem_1", }) if err != nil { t.Fatalf("execute: %v", err) } // This is what GET /api/v1/executions/{id} serves. got, err := store.GetExecution(res.Execution.ID) if err != nil { t.Fatalf("get execution: %v", err) } if got.ConfirmationID != conf.ID { t.Errorf("confirmation_id: got %q, want %q", got.ConfirmationID, conf.ID) } if got.CausationID != "caus_1" { t.Errorf("causation_id: got %q, want %q", got.CausationID, "caus_1") } if got.CorrelationID != "corr_1" { t.Errorf("correlation_id: got %q, want %q", got.CorrelationID, "corr_1") } if got.CapabilityVersion != cap.Version { t.Errorf("capability_version: got %d, want %d", got.CapabilityVersion, cap.Version) } // The idempotency-key read path serves the same record and must agree. byKey, err := store.GetExecutionByIdempotencyKey("idem_1") if err != nil { t.Fatalf("get by idempotency key: %v", err) } if byKey.ConfirmationID != conf.ID || byKey.CausationID != "caus_1" || byKey.CapabilityVersion != cap.Version { t.Errorf("idempotency read path lost audit fields: %+v", byKey) } } // The in-flight guard used to be check-then-insert. Two concurrent identical // executes must produce exactly one execution row, with the loser reported as // already-in-flight rather than a 500. func TestExecute_ConcurrentDuplicateProducesExactlyOneRow(t *testing.T) { prov := &fakeProvider{name: "fake", delay: 150 * time.Millisecond} eng, store := newTestEngine(t, prov) cap := mustCreateCapability(t, store, nil) const n = 4 var wg sync.WaitGroup errs := make([]error, n) start := make(chan struct{}) for i := 0; i < n; i++ { wg.Add(1) go func(i int) { defer wg.Done() <-start _, errs[i] = eng.Execute(&domain.ExecuteRequest{ CapabilityID: cap.ID, TargetEntityID: "ent_x", }) }(i) } close(start) wg.Wait() succeeded := 0 for i, err := range errs { switch { case err == nil: succeeded++ case errors.Is(err, domain.ErrExecutionInFlight): default: t.Errorf("goroutine %d: unexpected error %v (want nil or ErrExecutionInFlight)", i, err) } } if succeeded != 1 { t.Errorf("expected exactly 1 successful execute, got %d", succeeded) } var rows int if err := store.DB().QueryRow( `SELECT COUNT(*) FROM executions WHERE capability_id = ? AND target_entity_id = ?`, cap.ID, "ent_x", ).Scan(&rows); err != nil { t.Fatalf("count executions: %v", err) } if rows != 1 { t.Errorf("expected exactly 1 execution row, got %d", rows) } } // Directly exercise the partial unique index, bypassing the engine's advisory // pre-check, so the constraint itself is what is under test. func TestStore_SecondInFlightRowRejectedByIndex(t *testing.T) { _, store := newTestEngine(t, &fakeProvider{name: "fake"}) now := time.Now().UTC() mk := func(status domain.ExecutionStatus) *domain.Execution { return &domain.Execution{ ID: domain.NewExecutionID(), CapabilityID: "cap_1", TargetEntityID: "ent_x", Status: status, CreatedAt: now, UpdatedAt: now, } } if err := store.CreateExecution(mk(domain.ExecutionStarted)); err != nil { t.Fatalf("first insert: %v", err) } if err := store.CreateExecution(mk(domain.ExecutionStarted)); !errors.Is(err, domain.ErrExecutionInFlight) { t.Fatalf("second in-flight insert: got %v, want ErrExecutionInFlight", err) } // The index is partial: terminal rows for the same pair stay insertable. if err := store.CreateExecution(mk(domain.ExecutionSucceeded)); err != nil { t.Fatalf("terminal-status insert should be allowed: %v", err) } if err := store.CreateExecution(mk(domain.ExecutionSucceeded)); err != nil { t.Fatalf("second terminal-status insert should be allowed: %v", err) } }