98 lines
4.9 KiB
Go
98 lines
4.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 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)
|
|
}
|
|
}
|
|
}
|