Files
Agent-System-Backend/internal/app/project_source_recheck_test.go
T
agent-exec-transport 4c2db4c89b
governance / governance (pull_request) Successful in 17s
ci / vet-and-test (pull_request) Successful in 1m27s
agent-exec transport baad876f1320016310be2522c373a71b/9d3ed5593bfb
2026-10-22 03:03:31 +00:00

137 lines
6.9 KiB
Go

package app
import (
"context"
"errors"
"testing"
"time"
"agent-system-backend/pkg/extension"
)
type scriptedRecheckProvider struct {
testProjectContextProvider
recheck func(context.Context, extension.ProjectContextSourceRecheckRequest) (extension.ProjectContextSourceRecheckResult, error)
}
func (p scriptedRecheckProvider) Recheck(ctx context.Context, req extension.ProjectContextSourceRecheckRequest) (extension.ProjectContextSourceRecheckResult, error) {
return p.recheck(ctx, req)
}
func registeredRecheckSlot(t *testing.T, recheck func(context.Context, extension.ProjectContextSourceRecheckRequest) (extension.ProjectContextSourceRecheckResult, error)) *projectContextSlot {
t.Helper()
slot := &projectContextSlot{}
if err := (pluginRegistry{pluginID: "rdm", caps: hostcaps{contexts: slot}}).RegisterProjectContextProvider(scriptedRecheckProvider{recheck: recheck}); err != nil {
t.Fatal(err)
}
return slot
}
func TestProjectSourceRecheckScopeErrorsAreDistinguishable(t *testing.T) {
var nilSlot *projectContextSlot
slot := registeredRecheckSlot(t, func(context.Context, extension.ProjectContextSourceRecheckRequest) (extension.ProjectContextSourceRecheckResult, error) {
t.Error("unusable recheck scope reached the provider")
return extension.ProjectContextSourceRecheckResult{}, nil
})
cases := map[string]func() (extension.ProjectContextSourceRecheckResult, error){
"slot missing": func() (extension.ProjectContextSourceRecheckResult, error) {
return nilSlot.recheck(context.Background(), extension.ProjectContextSourceRecheckRequest{Scope: extension.ProjectContextRequest{Actor: "alice", ProjectID: "p1"}})
},
"no actor": func() (extension.ProjectContextSourceRecheckResult, error) {
return slot.recheck(context.Background(), extension.ProjectContextSourceRecheckRequest{Scope: extension.ProjectContextRequest{ProjectID: "p1"}})
},
"no project": func() (extension.ProjectContextSourceRecheckResult, error) {
return slot.recheck(context.Background(), extension.ProjectContextSourceRecheckRequest{Scope: extension.ProjectContextRequest{Actor: "alice"}})
},
"carries goal": func() (extension.ProjectContextSourceRecheckResult, error) {
return slot.recheck(context.Background(), extension.ProjectContextSourceRecheckRequest{Scope: extension.ProjectContextRequest{Actor: "alice", ProjectID: "p1", Goal: "secret"}})
},
}
seen := map[string]string{}
for name, call := range cases {
result, err := call()
if err == nil {
t.Fatalf("%s scope accepted", name)
}
if result.Bundle.Status != "" || result.Bundle.ProjectID != "" {
t.Fatalf("%s scope returned a non-empty bundle: %+v", name, result.Bundle)
}
if other, dup := seen[err.Error()]; dup {
t.Fatalf("%s and %s share one error message: %q", other, name, err)
}
seen[err.Error()] = name
}
if len(seen) != len(cases) {
t.Fatalf("expected %d distinct errors, got %d: %v", len(cases), len(seen), seen)
}
}
func TestProjectSourceRecheckFailsClosedAtThreeSecondCeiling(t *testing.T) {
var remaining time.Duration
slot := registeredRecheckSlot(t, func(ctx context.Context, req extension.ProjectContextSourceRecheckRequest) (extension.ProjectContextSourceRecheckResult, error) {
deadline, ok := ctx.Deadline()
if !ok {
return extension.ProjectContextSourceRecheckResult{}, errors.New("unbounded recheck")
}
remaining = time.Until(deadline)
select {
case <-ctx.Done():
case <-time.After(10 * time.Second):
}
// A late success must not authorize old machine context.
return extension.ProjectContextSourceRecheckResult{Bundle: extension.ProjectContextBundle{Status: extension.ProjectContextReady, ProjectID: req.Scope.ProjectID}}, nil
})
started := time.Now()
_, err := slot.recheck(context.Background(), extension.ProjectContextSourceRecheckRequest{Scope: extension.ProjectContextRequest{Actor: "alice", ProjectID: "p1"}})
if err == nil {
t.Fatal("source recheck past its deadline returned authorized context")
}
if remaining <= 0 || remaining > 3*time.Second {
t.Fatalf("source recheck deadline is not bounded by 3s: %v", remaining)
}
if elapsed := time.Since(started); elapsed > 5*time.Second {
t.Fatalf("source recheck outlived its 3s ceiling: %v", elapsed)
}
}
func TestProjectSourceRecheckRejectsProviderRefusalAndForeignBundles(t *testing.T) {
scope := extension.ProjectContextRequest{Actor: "alice", ProjectID: "p1"}
answers := map[string]func(extension.ProjectContextSourceRecheckRequest) (extension.ProjectContextSourceRecheckResult, error){
"refused": func(extension.ProjectContextSourceRecheckRequest) (extension.ProjectContextSourceRecheckResult, error) {
return extension.ProjectContextSourceRecheckResult{}, errors.New("provider refused")
},
"denied": func(req extension.ProjectContextSourceRecheckRequest) (extension.ProjectContextSourceRecheckResult, error) {
return extension.ProjectContextSourceRecheckResult{Bundle: extension.ProjectContextBundle{Status: extension.ProjectContextDenied, ProjectID: req.Scope.ProjectID}}, nil
},
"incomplete": func(req extension.ProjectContextSourceRecheckRequest) (extension.ProjectContextSourceRecheckResult, error) {
return extension.ProjectContextSourceRecheckResult{Bundle: extension.ProjectContextBundle{Status: extension.ProjectContextIncomplete, ProjectID: req.Scope.ProjectID}}, nil
},
"other project": func(extension.ProjectContextSourceRecheckRequest) (extension.ProjectContextSourceRecheckResult, error) {
return extension.ProjectContextSourceRecheckResult{Bundle: extension.ProjectContextBundle{Status: extension.ProjectContextReady, ProjectID: "p2"}}, nil
},
}
for name, answer := range answers {
slot := registeredRecheckSlot(t, func(_ context.Context, req extension.ProjectContextSourceRecheckRequest) (extension.ProjectContextSourceRecheckResult, error) {
return answer(req)
})
if _, err := slot.recheck(context.Background(), extension.ProjectContextSourceRecheckRequest{Scope: scope}); err == nil {
t.Fatalf("%s source recheck accepted", name)
}
}
var sawPlugin string
slot := registeredRecheckSlot(t, func(_ context.Context, req extension.ProjectContextSourceRecheckRequest) (extension.ProjectContextSourceRecheckResult, error) {
sawPlugin = req.Scope.PluginID
return extension.ProjectContextSourceRecheckResult{Bundle: extension.ProjectContextBundle{Status: extension.ProjectContextReady, ProjectID: req.Scope.ProjectID}}, nil
})
forged := scope
forged.PluginID = "forged-plugin"
if _, err := slot.recheck(context.Background(), extension.ProjectContextSourceRecheckRequest{Scope: forged}); err != nil || sawPlugin != "rdm" {
t.Fatalf("recheck scope plugin was not the registered holder: plugin=%q err=%v", sawPlugin, err)
}
for _, missing := range []extension.ProjectContextRequest{{ProjectID: "p1"}, {Actor: "alice"}} {
if _, err := slot.recheck(context.Background(), extension.ProjectContextSourceRecheckRequest{Scope: missing}); err == nil {
t.Fatalf("recheck without trusted actor/project accepted: %+v", missing)
}
}
}