Once chain_moved records the kind, a plan is a key of the rollouts bucket under its id and the open batch is the same key in state assembling. A save refuses a copy another save moved since (the plan's own Revision, kept in its value, counts saves) and keeps the merges the record holds, so a copy read before a merge moved cannot lay it back. The merges a batch or walk answers are the record's held field, with the columns the table had; a merge no plan answers stays, flagged, in the record that held it last, or in the record kept for those. A merge moved to a plan is written to the receiving record first and then taken from the giving one; one found in both is read from the one it entered last and repaired at the next cut. Plans and merges are read from a view kept by one watch of the bucket. A bus that does not answer, a lease lost and a key moved are now not-now like the store being away, so a merge whose plan could not be written is held and handed over again, never acknowledged. The start moves release_plan and batched_merge into the bucket, one kind, counted against both tables.
48 lines
1.9 KiB
Go
48 lines
1.9 KiB
Go
//go:build integration
|
|
|
|
package inventory
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/chain"
|
|
)
|
|
|
|
// **A plan's id is a value, never a query** (novox/hq issue 561): the id reaches PlanByID from a caller of the
|
|
// controller's plans verb, so an id holding a quote is read as a plain id — no error from the store, and no
|
|
// other plan answered for it. On the bus an id is a key, and one the bus takes no spelling of is refused to be kept.
|
|
func TestAPlanIdHoldingAQuoteIsReadAsAPlainId(t *testing.T) {
|
|
inv := ForTest(t)
|
|
ctx := t.Context()
|
|
p := Plan{ID: "plan-561", Repository: "novox/mesh-controller", Commit: "abc", Created: time.Now().UTC(),
|
|
State: PlanBuilding, Tiers: [][]string{{"a"}}, Modules: map[string]*PlanModule{"a": {}}}
|
|
if err := inv.SavePlan(ctx, &p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, id := range []string{
|
|
"x' or '1'='1", // every plan, when pasted into the query
|
|
"plan-561' or id like '%", // the plan above, by a pattern
|
|
"it's", // a quote alone: a syntax error, when pasted
|
|
} {
|
|
got, err := inv.PlanByID(ctx, id)
|
|
if err == nil {
|
|
t.Fatalf("PlanByID(%q) answered %s, a plan of another id", id, got.ID)
|
|
}
|
|
if want := "no plan " + id; err.Error() != want {
|
|
t.Fatalf("PlanByID(%q) failed with %q, not as a plain id no plan has (%q)", id, err, want)
|
|
}
|
|
}
|
|
// A plan whose id holds a quote cannot be a key of the bus: refused by name, never rewritten into another id.
|
|
quoted := p
|
|
quoted.ID, quoted.Revision = "plan-'561'", 0
|
|
if err := inv.SavePlan(ctx, "ed); !errors.Is(err, chain.ErrBadKey) {
|
|
t.Fatalf("a plan with a quote in its id was kept, or refused for another reason: %v", err)
|
|
}
|
|
if recent, err := inv.RecentPlans(ctx, 1); err != nil || len(recent) != 1 || !strings.HasPrefix(recent[0].ID, "plan-") {
|
|
t.Fatalf("RecentPlans with a parameter: %v %+v", err, recent)
|
|
}
|
|
}
|