package execution_test import ( "encoding/json" "errors" "path/filepath" "strings" "testing" "time" "github.com/kami/hexis/internal/domain" "github.com/kami/hexis/internal/execution" "github.com/kami/hexis/internal/provider" "github.com/kami/hexis/internal/storage" ) func newContractTestEngine(t *testing.T) (*execution.Engine, *storage.Store) { t.Helper() prov := &fakeProvider{name: "fake"} dir := t.TempDir() store, err := storage.Open(filepath.Join(dir, "hexis.db")) if err != nil { t.Fatalf("open storage: %v", err) } t.Cleanup(func() { store.Close() }) reg := provider.NewRegistry() reg.Register(prov) return execution.New(store, reg), store } // TestContract_IdempotencyKeyReplay validates that re-executing with the // same idempotency_key returns the existing execution without side effects. func TestContract_IdempotencyKeyReplay(t *testing.T) { eng, store := newContractTestEngine(t) cap := mustCreateCapability(t, store, nil) // First execution with idempotency key res1, err := eng.Execute(&domain.ExecuteRequest{ CapabilityID: cap.ID, TargetEntityID: "ent_x", IdempotencyKey: "idem-unique-1", }) if err != nil { t.Fatalf("first execute: %v", err) } if res1.Execution.Status != domain.ExecutionSucceeded { t.Fatalf("expected succeeded, got %s", res1.Execution.Status) } // Replay with same idempotency key must return the same execution res2, err := eng.Execute(&domain.ExecuteRequest{ CapabilityID: cap.ID, TargetEntityID: "ent_x", IdempotencyKey: "idem-unique-1", }) if err != nil { t.Fatalf("replay execute: %v", err) } if res2.Execution.ID != res1.Execution.ID { t.Errorf("expected same execution ID on replay, got %s vs %s", res2.Execution.ID, res1.Execution.ID) } if res2.Execution.Status != res1.Execution.Status { t.Errorf("expected same status on replay, got %s vs %s", res2.Execution.Status, res1.Execution.Status) } } // TestContract_EmptyIdempotencyKeyNotReplayed validates that requests // without an idempotency key always create new executions. func TestContract_EmptyIdempotencyKeyNotReplayed(t *testing.T) { eng, store := newContractTestEngine(t) cap := mustCreateCapability(t, store, nil) res1, err := eng.Execute(&domain.ExecuteRequest{ CapabilityID: cap.ID, TargetEntityID: "ent_x", }) if err != nil { t.Fatalf("execute 1: %v", err) } res2, err := eng.Execute(&domain.ExecuteRequest{ CapabilityID: cap.ID, TargetEntityID: "ent_x", }) if err != nil { t.Fatalf("execute 2: %v", err) } if res2.Execution.ID == res1.Execution.ID { t.Error("expected different execution IDs for same request without idempotency key") } } // TestContract_EventPayloadFormat validates that all emitted events have // the correct event type prefix and non-empty payloads where expected. func TestContract_EventPayloadFormat(t *testing.T) { eng, store := newContractTestEngine(t) cap := mustCreateCapability(t, store, nil) eng.Execute(&domain.ExecuteRequest{ CapabilityID: cap.ID, TargetEntityID: "ent_x", }) events, err := store.EventsAfter(0, 100) if err != nil { t.Fatalf("events: %v", err) } var seenStart, seenSucceed bool for _, evt := range events { if !strings.HasPrefix(string(evt.Type), "hexis.") { t.Errorf("event type %q doesn't start with 'hexis.'", evt.Type) } switch evt.Type { case domain.EventExecutionStarted: seenStart = true if _, ok := evt.Payload["capability_id"]; !ok { t.Error("execution.started event missing capability_id in payload") } case domain.EventExecutionSucceeded: seenSucceed = true case domain.EventCapabilityRegistered: // Registered by mustCreateCapability } } if !seenStart { t.Error("expected execution.started event") } if !seenSucceed { t.Error("expected execution.succeeded event") } } // TestContract_CapabilityRegisteredEmittedOnce validates that // EventCapabilityRegistered is emitted only once per capability creation, // not also by the execution engine. func TestContract_CapabilityRegisteredEmittedOnce(t *testing.T) { _, store := newContractTestEngine(t) // This test validates that creating a capability emits exactly one // registration event. The handler calls AppendEvent directly with // EventCapabilityRegistered. The engine does not emit this event. // We verify by looking at the event log after a capability is created. // Create capability directly through store (simulating handler behavior) now := time.Now().UTC() cap := &domain.Capability{ ID: domain.NewCapabilityID(), Name: "test.cap.unique", Provider: "fake", Operation: "noop", Risk: "low", ReadOnly: false, Enabled: true, CreatedAt: now, UpdatedAt: now, Version: 1, TargetTypes: []string{}, Attributes: map[string]any{}, } if err := store.CreateCapability(cap); err != nil { t.Fatalf("create capability: %v", err) } // Emit registration event (as handler does) store.AppendEvent(&domain.Event{ ID: domain.NewEventID(), Type: domain.EventCapabilityRegistered, Timestamp: now, Payload: map[string]any{"capability_id": cap.ID, "name": cap.Name}, }) events, err := store.EventsAfter(0, 100) if err != nil { t.Fatalf("events: %v", err) } count := 0 for _, evt := range events { if evt.Type == domain.EventCapabilityRegistered { payload, _ := json.Marshal(evt.Payload) if strings.Contains(string(payload), cap.ID) { count++ } } } if count != 1 { t.Errorf("expected exactly 1 EventCapabilityRegistered for cap %s, got %d", cap.ID, count) } } // TestContract_EventCorrelationFieldPopulated validates that correlation_id // and causation_id set on an ExecuteRequest propagate onto the execution's // emitted events. func TestContract_EventCorrelationFieldPopulated(t *testing.T) { eng, store := newContractTestEngine(t) cap := mustCreateCapability(t, store, nil) eng.Execute(&domain.ExecuteRequest{ CapabilityID: cap.ID, TargetEntityID: "ent_x", CorrelationID: "corr-789", CausationID: "cause-012", }) events, err := store.EventsAfter(0, 100) if err != nil { t.Fatalf("events: %v", err) } found := false for _, evt := range events { if evt.Type == domain.EventExecutionStarted { found = true if evt.CorrelationID != "corr-789" { t.Errorf("expected correlation_id corr-789, got %q", evt.CorrelationID) } if evt.CausationID != "cause-012" { t.Errorf("expected causation_id cause-012, got %q", evt.CausationID) } } } if !found { t.Fatalf("expected an execution.started event") } } // TestContract_ExecutionStatusDeniedNotEmitted documents that the // execution.denied event type is defined but never emitted. func TestContract_ExecutionDeniedNotEmitted(t *testing.T) { eng, store := newContractTestEngine(t) cap := mustCreateCapability(t, store, func(c *domain.Capability) { c.Enabled = false }) _, err := eng.Execute(&domain.ExecuteRequest{ CapabilityID: cap.ID, TargetEntityID: "ent_x", }) if !errors.Is(err, domain.ErrCapabilityDisabled) { t.Fatalf("expected ErrCapabilityDisabled, got %v", err) } events, _ := store.EventsAfter(0, 100) for _, evt := range events { if evt.Type == domain.EventExecutionDenied { t.Errorf("execution.denied event emitted but expected to not be in use") } } }