package spindle import ( "errors" "fmt" "testing" "github.com/bluesky-social/indigo/atproto/syntax" "tangled.org/core/spindle/models" "tangled.org/core/spindle/xrpc" ) func TestWorkflowDefinitionsAreCachedAndRateLimitedPerRepo(t *testing.T) { var c workflowDefCache loads := 0 knotRead := func(repo syntax.DID) func() ([]*models.WorkflowDefinition, error) { return func() ([]*models.WorkflowDefinition, error) { loads++ return []*models.WorkflowDefinition{{ID: "ci"}}, c.admit(repo) } } for range 20 { if _, err := c.list("did:plc:repo", "HEAD", knotRead("did:plc:repo")); err != nil { t.Fatal(err) } } if loads != 1 { t.Fatalf("%d loads for 20 reads of the same ref, want 1", loads) } // distinct refs skip the cache and each one is a knot read var busy error for i := range 3 * workflowDefLoadBurst { if _, err := c.list("did:plc:repo", fmt.Sprintf("refs/heads/b%d", i), knotRead("did:plc:repo")); err != nil { busy = err break } } if !errors.Is(busy, xrpc.ErrDefinitionsBusy) { t.Fatalf("%d uncached reads of one repo in a row ended with %v, want ErrDefinitionsBusy", 3*workflowDefLoadBurst, busy) } if _, err := c.list("did:plc:otherrepo", "HEAD", knotRead("did:plc:otherrepo")); err != nil { t.Fatalf("another repo was refused after the first one got busy: %v", err) } } func TestWorkflowDefinitionRateLimitOnlyCountsKnotReads(t *testing.T) { var c workflowDefCache local := func() ([]*models.WorkflowDefinition, error) { return nil, nil } for i := range 10 * workflowDefLoadBurst { if _, err := c.list("did:plc:repo", fmt.Sprintf("%040d", i), local); err != nil { t.Fatalf("local read %d was limited: %v", i, err) } } if err := c.admit("did:plc:repo"); err != nil { t.Fatalf("local reads used up the knot budget: %v", err) } }