package reconcile import ( "context" "errors" "path/filepath" "testing" "gitea.dooplex.hu/admin/felhom-agent/internal/authz" ) // newSignedEngine builds an engine whose gate has a real verifier pinning one // operational key — for exercising the signed-job consuming layer end to end. func newSignedEngine(t *testing.T, api GuestAPI) (*Engine, *Journal, testSigner) { t.Helper() j, err := OpenJournal(filepath.Join(t.TempDir(), "journal.log")) if err != nil { t.Fatalf("OpenJournal: %v", err) } t.Cleanup(func() { j.Close() }) q := NewQueue() t.Cleanup(q.Close) op := newTestSigner(t) v, _ := realVerifierAt(t, filepath.Join(t.TempDir(), "n.log"), testHost, op.allowed(t, "op1", authz.RoleOperational)) g := NewGate(v, testHost, nil, nil) e := NewEngine(EngineOptions{API: api, Queue: q, Journal: j, Gate: g, HostID: testHost}) return e, j, op } func TestRunSignedJob_ValidExecutesAndMarksApplied(t *testing.T) { e, j, op := newSignedEngine(t, &fakeAPI{}) issued, expires := freshWindow() n := nonce() signed := op.mint("guest_destroy", testHost, "9001", "op1", n, `{"purge":true}`, issued, expires) calls := 0 exec := func(context.Context, Intent, *authz.VerifiedOp) (string, error) { calls++; return "", nil } // synchronous res := e.RunSignedJob(context.Background(), destroyIntent(SourceOneShotJob), signed, exec) if !res.Executed || res.Err != nil { t.Fatalf("valid job should execute, got %+v", res) } if calls != 1 { t.Errorf("executor should run once, ran %d", calls) } if !j.AlreadyApplied(n) { t.Error("successful job must mark its idempotency key (nonce) applied") } } func TestRunSignedJob_RedeliveryDedupedByIdempotencyKey(t *testing.T) { // After success, a redelivered identical job must NOT re-run — the journal's // idempotency key short-circuits BEFORE the verifier (so it reports already-applied, // not a confusing replay rejection). e, _, op := newSignedEngine(t, &fakeAPI{}) issued, expires := freshWindow() n := nonce() signed := op.mint("guest_destroy", testHost, "9001", "op1", n, `{"purge":true}`, issued, expires) calls := 0 exec := func(context.Context, Intent, *authz.VerifiedOp) (string, error) { calls++; return "", nil } first := e.RunSignedJob(context.Background(), destroyIntent(SourceOneShotJob), signed, exec) if !first.Executed { t.Fatalf("first delivery should execute: %+v", first) } second := e.RunSignedJob(context.Background(), destroyIntent(SourceOneShotJob), signed, exec) if !second.AlreadyApplied || second.Executed { t.Fatalf("redelivery should be deduped (already applied), got %+v", second) } if calls != 1 { t.Errorf("executor must run exactly once across redelivery, ran %d", calls) } } func TestRunSignedJob_RefusedDoesNotExecute(t *testing.T) { e, j, _ := newSignedEngine(t, &fakeAPI{}) attacker := newTestSigner(t) // not pinned issued, expires := freshWindow() n := nonce() signed := attacker.mint("guest_destroy", testHost, "9001", "op1", n, `{"purge":true}`, issued, expires) calls := 0 exec := func(context.Context, Intent, *authz.VerifiedOp) (string, error) { calls++; return "", nil } res := e.RunSignedJob(context.Background(), destroyIntent(SourceOneShotJob), signed, exec) if res.Executed || res.Decision.Allowed || !errors.Is(res.Decision.Err, authz.ErrUnknownSigner) { t.Fatalf("forged job must be refused unexecuted, got %+v", res) } if calls != 0 { t.Errorf("executor must not run for a refused job, ran %d", calls) } if j.AlreadyApplied(n) { t.Error("a refused job must not mark its key applied") } } func TestRunSignedJob_NoExecutorInert(t *testing.T) { // Slice-4 inert state: a VALID authorization with no destructive executor wired // returns an error and does NOT mark the key applied (so it is retryable once the // executor lands in a later slice). e, j, op := newSignedEngine(t, &fakeAPI{}) issued, expires := freshWindow() n := nonce() signed := op.mint("guest_destroy", testHost, "9001", "op1", n, `{"purge":true}`, issued, expires) res := e.RunSignedJob(context.Background(), destroyIntent(SourceOneShotJob), signed, nil) if !res.Decision.Allowed { t.Fatalf("op should authorize even with no executor: %+v", res.Decision) } if res.Executed || res.Err == nil { t.Fatalf("no-executor job should not execute and should error, got %+v", res) } if j.AlreadyApplied(n) { t.Error("an unexecuted (no-executor) job must not mark its key applied") } } func TestRunSignedJob_ExecutorErrorJournaledFailed(t *testing.T) { e, j, op := newSignedEngine(t, &fakeAPI{}) issued, expires := freshWindow() n := nonce() signed := op.mint("guest_destroy", testHost, "9001", "op1", n, `{"purge":true}`, issued, expires) exec := func(context.Context, Intent, *authz.VerifiedOp) (string, error) { return "", errors.New("destroy failed") } res := e.RunSignedJob(context.Background(), destroyIntent(SourceOneShotJob), signed, exec) if res.Executed || res.Err == nil { t.Fatalf("executor error should propagate, got %+v", res) } if j.AlreadyApplied(n) { t.Error("a failed execution must not mark its key applied") } }