~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
243 lines · 9.1 KB Raw History
package bidder

import (
	"context"
	"strings"
	"testing"
	"time"

	"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/atproto/market"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/market/participant"
	bundledactions "github.com/johnandersen777/socialweb-computer-kcp/internal/policyengine/bundled-actions"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/policyengine/policytest"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/policyengine/runs"
)

func specBidder(h *policytest.Harness, own string) *Bidder {
	records := market.NewRepoStore(market.RepoStoreConfig{DID: "did:plc:policytestbidderlocal", Foreign: h.World})
	return &Bidder{
		cfg:     Config{DID: policytest.Bidder, ScopePolicy: own},
		records: records,
		policy:  &policyEvaluator{runs: h.Client(PerspectiveBidder), scope: own, records: records},
	}
}

func (b *Bidder) specRFPToBid(ctx context.Context, requester string, rfp policytest.Ref, attached []market.StrongRef) participant.PolicyDecision {
	if scope := b.policy.Scope(ctx, participant.PolicyInput{SelfDid: b.cfg.DID, CounterpartyDid: requester,
		RFP: &participant.PolicyRef{URI: rfp.URI, CID: rfp.CID}}); !scope.Allow {
		return scope
	}
	return b.policy.EvaluatePolicies(ctx, attached, b.fullInput(requester, rfp.URI, rfp.CID))
}

type bidderMode struct {
	own      string
	workflow string
	want     func(policytest.TrustCase) bool
}

var bidderModes = []bidderMode{
	{own: "", workflow: v1alpha1.BidderDefaultPolicy, want: func(c policytest.TrustCase) bool { return c.Vouch }},
	{own: "bidder-tangled-vouch", workflow: "bidder-tangled-vouch", want: func(c policytest.TrustCase) bool { return c.Vouch }},
	{own: "bidder-only-me", workflow: "bidder-only-me", want: func(c policytest.TrustCase) bool { return c.OnlyMe }},
	{own: "open", workflow: "open", want: func(policytest.TrustCase) bool { return true }},
}

func modeName(m bidderMode) string {
	if m.own == "" {
		return "default"
	}
	return m.own
}

type ranRun struct {
	mode     string
	workflow string
	allow    bool
	finished bool
}

func runsOf(h *policytest.Harness, mark int) []ranRun {
	out := []ranRun{}
	for _, run := range h.Runner.Since(mark) {
		allow := false
		if raw, ok := run.Status.Outputs["evaluate"]; ok {
			allow = strings.Contains(string(raw), `"allow":"true"`)
		}
		out = append(out, ranRun{mode: run.Input("mode"), workflow: run.Workflow, allow: allow, finished: run.Status.CompletionTime != nil})
	}
	return out
}

func requireRan(t *testing.T, got []ranRun, mode, workflow string) ranRun {
	t.Helper()
	for _, run := range got {
		if run.mode == mode && run.workflow == workflow {
			if !run.finished {
				t.Fatalf("the %s %s run did not finish", mode, workflow)
			}
			return run
		}
	}
	t.Fatalf("no %s run of %s for this decision; runs %+v", mode, workflow, got)
	return ranRun{}
}

func attachedRecord(h *policytest.Harness, name string) market.StrongRef {
	text, _ := bundledactions.Workflow(name)
	ref := h.World.PolicyRecord(policytest.Requester, "policy-"+name, name, string(text))
	return market.StrongRef{URI: ref.URI, CID: ref.CID}
}

func TestBidderDecisionSites(t *testing.T) {
	t.Parallel()
	for _, site := range []string{"rfp-to-bid", "accept"} {
		for _, mode := range bidderModes {
			for _, attach := range []string{"", "requester-tangled-vouch"} {
				for _, tc := range policytest.TrustCases {
					label := "none"
					if attach != "" {
						label = attach
					}
					t.Run("always-run/bidder-"+site+"/"+modeName(mode)+"/attached-"+label+"/"+tc.Name, func(t *testing.T) {
						t.Parallel()
						h := policytest.New(t)
						p := policytest.PartiesFor("bidder")
						tc.Build(h.World, p)
						b := specBidder(h, mode.own)
						var refs []market.StrongRef
						if attach != "" {
							refs = append(refs, attachedRecord(h, attach))
						}
						rfp := h.World.RFP(policytest.Requester, "rfp-"+site)
						mark := h.Runner.Mark()
						ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
						defer cancel()
						verdict := b.specRFPToBid(ctx, p.CP, rfp, refs)
						got := runsOf(h, mark)
						ownScope := requireRan(t, got, runs.ModeScope, mode.workflow)
						want := mode.want(tc)
						if ownScope.allow != want {
							t.Fatalf("the scope run of %s answered allow=%t, want %t", mode.workflow, ownScope.allow, want)
						}
						if !ownScope.allow {
							if verdict.Allow {
								t.Fatalf("the decision allowed after the scope run denied")
							}
							for _, run := range got {
								if run.mode == runs.ModeFull {
									t.Fatalf("a full run went ahead after the scope run denied")
								}
							}
							return
						}
						ownFull := requireRan(t, got, runs.ModeFull, mode.workflow)
						if ownFull.allow != want {
							t.Fatalf("the full run of %s answered allow=%t, want %t", mode.workflow, ownFull.allow, want)
						}
						if attach != "" && ownFull.allow {
							attachedRun := requireRan(t, got, runs.ModeFull, refs[0].URI)
							want = want && tc.Vouch
							if attachedRun.allow != tc.Vouch {
								t.Fatalf("the attached %s run answered allow=%t, want %t", attach, attachedRun.allow, tc.Vouch)
							}
						}
						if verdict.Allow != want {
							t.Fatalf("the %s decision allow=%t, want %t (%s)", site, verdict.Allow, want, verdict.Reason)
						}
					})
				}
			}
		}
	}
}

func TestBidderAttachedPoliciesFailClosed(t *testing.T) {
	t.Parallel()
	for _, tc := range []struct {
		name   string
		attach func(h *policytest.Harness) market.StrongRef
		reason string
	}{
		{"attached-deny-workflow", func(h *policytest.Harness) market.StrongRef {
			ref := h.World.PolicyRecord(policytest.Requester, "split", "split-verdict", policytest.WorkflowFixture("split-verdict"))
			return market.StrongRef{URI: ref.URI, CID: ref.CID}
		}, "the second job denies"},
		{"attached-typescript-record", func(h *policytest.Harness) market.StrongRef {
			ref := h.World.RecordOfType(policytest.Requester, "computer.socialweb.temp.policy.typescript", "ts", policytest.RecordFixture("typescript"))
			return market.StrongRef{URI: ref.URI, CID: ref.CID}
		}, "not gha-lite"},
		{"attached-record-missing", func(h *policytest.Harness) market.StrongRef {
			return market.StrongRef{URI: policytest.URI(policytest.Requester, policytest.PolicyGhaLiteNSID, "gone"), CID: "bafyreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy"}
		}, "failed to resolve policy ref"},
		{"attached-record-cid-changed", func(h *policytest.Harness) market.StrongRef {
			ref := attachedRecord(h, "requester-tangled-vouch")
			return market.StrongRef{URI: ref.URI, CID: "bafyreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy"}
		}, "failed to resolve policy ref"},
	} {
		t.Run("always-run/bidder-rfp-to-bid/default/"+tc.name, func(t *testing.T) {
			t.Parallel()
			h := policytest.New(t)
			h.World.Vouch(policytest.Bidder, policytest.Requester)
			b := specBidder(h, "")
			rfp := h.World.RFP(policytest.Requester, "rfp")
			mark := h.Runner.Mark()
			verdict := b.specRFPToBid(context.Background(), policytest.Requester, rfp, []market.StrongRef{tc.attach(h)})
			got := runsOf(h, mark)
			requireRan(t, got, runs.ModeScope, v1alpha1.BidderDefaultPolicy)
			requireRan(t, got, runs.ModeFull, v1alpha1.BidderDefaultPolicy)
			if verdict.Allow {
				t.Fatalf("an attached policy that cannot allow let the bid through")
			}
			if !strings.Contains(verdict.Reason, tc.reason) {
				t.Fatalf("want a deny with %q, got %q", tc.reason, verdict.Reason)
			}
		})
	}
}

func TestBidderFailClosedWithoutEngine(t *testing.T) {
	t.Parallel()
	for _, tc := range []struct {
		name  string
		setup func(h *policytest.Harness, b *Bidder)
	}{
		{"no-run-client", func(h *policytest.Harness, b *Bidder) { b.policy = &policyEvaluator{} }},
		{"sandbox-unfenced", func(h *policytest.Harness, b *Bidder) { h.Sandbox.SetMode(policytest.SandboxUnfenced) }},
		{"no-engine-output", func(h *policytest.Harness, b *Bidder) {}},
	} {
		t.Run("fail-closed/bidder-rfp-to-bid/"+tc.name, func(t *testing.T) {
			t.Parallel()
			h := policytest.New(t)
			h.World.Vouch(policytest.Bidder, policytest.Requester)
			b := specBidder(h, "open")
			tc.setup(h, b)
			rfp := h.World.RFP(policytest.Requester, "rfp")
			var attached []market.StrongRef
			if tc.name == "no-engine-output" {
				ref := h.World.PolicyRecord(policytest.Requester, "silent", "silent", policytest.WorkflowFixture("no-allow-output"))
				attached = []market.StrongRef{{URI: ref.URI, CID: ref.CID}}
			}
			verdict := b.specRFPToBid(context.Background(), policytest.Requester, rfp, attached)
			if verdict.Allow {
				t.Fatalf("the bid decision allowed with %s", tc.name)
			}
			if strings.TrimSpace(verdict.Reason) == "" {
				t.Fatalf("the deny carries no reason")
			}
		})
	}
}

func TestBidderDefaultPolicyIsTangledVouch(t *testing.T) {
	t.Parallel()
	if v1alpha1.BidderDefaultPolicy != "bidder-tangled-vouch" {
		t.Fatalf("the bidder default policy is %q, want bidder-tangled-vouch", v1alpha1.BidderDefaultPolicy)
	}
	for _, own := range []string{"", "  "} {
		if got := (&policyEvaluator{scope: own}).workflow(); got != "bidder-tangled-vouch" {
			t.Fatalf("spec.policy %q runs %q, want bidder-tangled-vouch", own, got)
		}
	}
}