package hostclaim import ( "context" "encoding/json" "errors" "io" "reflect" "strings" "testing" "github.com/carlosframework/platform/internal/hostwrite" "github.com/carlosframework/platform/internal/release" ) func TestPlan15ActivationDoesNotTrustRetainedAdmissions(t *testing.T) { t.Setenv("CARLOS_HOST_CLAIM", "") t.Setenv("CARLOS_HOST_CLAIM_ORIGINS", "") s := release.NewDirStore(t.TempDir()) o := servingClaim(t, t.Context(), s) o.Claim.Holder.Admissions = append(o.Claim.Holder.Admissions, AdmissionRecord{Region: "ie", Generation: "injected", Mode: "cold"}) key, _ := Key(o.Host) body, _ := json.Marshal(o.Claim) var err error o.ETag, err = s.Put(t.Context(), key, body, o.ETag) if err != nil { t.Fatal(err) } plan15Enable(t) o = updateGenerationTest(t, t.Context(), s, o, plan15Admit(o, "after-boundary", "ie")) if o.Claim.Origins.Count != 1 { t.Fatalf("migration seeded %d origins", o.Claim.Origins.Count) } plan15Proof(t, s, o, "injected", "ie", false) plan15Proof(t, s, o, "after-boundary", "ie", true) } func TestPlan15UpdateAuthenticatesObservedHead(t *testing.T) { for _, erase := range []bool{false, true} { t.Run(map[bool]string{false: "forged", true: "erased"}[erase], func(t *testing.T) { plan15Enable(t) s := release.NewDirStore(t.TempDir()) o := servingClaim(t, t.Context(), s) o = updateGenerationTest(t, t.Context(), s, o, plan15Admit(o, "real", "ie")) stored := *o.Claim.Origins if erase { o.Claim.Origins = nil } else { o.Claim.Origins = &OriginHead{strings.Repeat("f", 64), 1} t.Setenv("CARLOS_HOST_CLAIM_ORIGINS", "") } if _, err := Update(t.Context(), s, o, func(*Claim) error { return nil }); !errors.Is(err, ErrConflict) { t.Fatalf("edited head: %v", err) } fresh, err := Read(t.Context(), s, o.Host) if err != nil || !reflect.DeepEqual(fresh.Claim.Origins, &stored) { t.Fatalf("stored head changed: %+v %v", fresh, err) } }) } } func TestPlan15SwitchParsingMatchesClaim(t *testing.T) { for _, claim := range []string{"", " ", "1", "true", " 1", "false", "0"} { for _, origins := range []string{"", " ", "1", "true", " 1", "false", "0"} { t.Setenv("CARLOS_HOST_CLAIM", claim) t.Setenv("CARLOS_HOST_CLAIM_ORIGINS", origins) enabled := func(v string) bool { return hostwrite.Enabled(func(string) string { return v }) } if got, want := originEnabled(), enabled(claim) && enabled(origins); got != want { t.Errorf("%q/%q = %v want %v", claim, origins, got, want) } } } } func TestPlan15ProofEarlyExit(t *testing.T) { plan15Enable(t) s := &plan15BoundedStore{DirStore: release.NewDirStore(t.TempDir())} o := servingClaim(t, t.Context(), s) for _, g := range []string{"first", "middle", "last"} { o = updateGenerationTest(t, t.Context(), s, o, plan15Admit(o, g, "ie")) } h := o.Claim.Holder last := OriginTuple{h.Incarnation, h.Location, "ie", "last"} s.opens = 0 if got, err := AdmittedOrigins(t.Context(), s, o, []OriginTuple{last, last}); err != nil || !got[last] || s.opens != 1 { t.Fatalf("head proof=%v err=%v opens=%d", got, err, s.opens) } s.opens = 0 if _, err := AdmittedOrigins(t.Context(), s, o, nil); err != nil || s.opens != 0 { t.Fatalf("empty proof err=%v opens=%d", err, s.opens) } missing := last missing.Generation = "never" s.opens = 0 if got, err := AdmittedOrigins(t.Context(), s, o, []OriginTuple{last, missing}); err != nil || !got[last] || got[missing] || s.opens != 3 { t.Fatalf("negative proof=%v err=%v opens=%d", got, err, s.opens) } } type plan15GetOnly struct{ release.Store } func (s plan15GetOnly) Get(ctx context.Context, key string) ([]byte, string, error) { if strings.Contains(key, "control/host-origins/") { panic("unbounded node Get through wrapper") } return s.Store.Get(ctx, key) } func TestPlan15PrefixCannotBufferOrigin(t *testing.T) { plan15Enable(t) raw := release.NewDirStore(t.TempDir()) prefixed := release.NewPrefixStore(raw, "deployment") o := servingClaim(t, t.Context(), prefixed) o = updateGenerationTest(t, t.Context(), prefixed, o, plan15Admit(o, "real", "ie")) s := release.NewPrefixStore(plan15GetOnly{raw}, "deployment") h := o.Claim.Holder if ok, err := AdmittedOrigin(t.Context(), s, o, h.Incarnation, h.Location, "ie", "real"); ok || err == nil { t.Fatalf("buffered origin %v %v", ok, err) } } // A comparable outer type can still hold a noncomparable dynamic value. type plan15Noncomparable struct { *plan15BoundedStore slice []byte } type plan15InterfaceWrapper struct{ release.Store } func (s plan15InterfaceWrapper) Open(ctx context.Context, key string) (io.ReadCloser, string, error) { return release.OpenObject(ctx, s.Store, key) } func TestPlan15CacheBoundAtConstruction(t *testing.T) { plan15Enable(t) raw := &plan15BoundedStore{DirStore: release.NewDirStore(t.TempDir())} s := plan15InterfaceWrapper{plan15Noncomparable{raw, []byte{1}}} o := servingClaim(t, t.Context(), s) o = updateGenerationTest(t, t.Context(), s, o, plan15Admit(o, "real", "ie")) h := o.Claim.Holder tuple := OriginTuple{h.Incarnation, h.Location, "ie", "real"} cache := NewOriginProofCache(s) raw.opens, raw.gets = 0, 0 for i := 0; i < 2; i++ { got, err := cache.Proven(t.Context(), o, []OriginTuple{tuple}) if err != nil || !got[tuple] { t.Fatalf("proof %v %v", got, err) } got[tuple] = false } if raw.opens != 1 || raw.gets != 2 { t.Fatalf("opens=%d gets=%d", raw.opens, raw.gets) } // The method has no store argument: changing the constructor's variable // cannot redirect this cache, and a second cache cannot inherit its result. other := release.NewDirStore(t.TempDir()) key, _ := Key(o.Host) body, _ := json.Marshal(o.Claim) if _, err := other.Put(t.Context(), key, body, ""); err != nil { t.Fatal(err) } s = plan15InterfaceWrapper{other} if got, err := NewOriginProofCache(s).Proven(t.Context(), o, []OriginTuple{tuple}); got != nil || err == nil { t.Fatalf("foreign store reused proof %v %v", got, err) } if got, err := cache.Proven(t.Context(), o, []OriginTuple{tuple}); err != nil || !got[tuple] { t.Fatalf("bound store changed %v %v", got, err) } if got, err := new(OriginProofCache).Proven(t.Context(), o, []OriginTuple{tuple}); got != nil || !errors.Is(err, ErrMalformed) { t.Fatalf("unbound cache %v %v", got, err) } }