package hostops import ( "context" "errors" "testing" "github.com/carlosframework/platform/internal/release" ) func TestMutationBoundaryRefusesEveryWriteWithoutAuthority(t *testing.T) { ctx := t.Context() raw := release.NewDirStore(t.TempDir()) key := "apps/bab/demo/deleted.json" tag, err := raw.Put(ctx, key, []byte("old"), "") if err != nil { t.Fatal(err) } calls := 0 guarded, err := NewMutationBoundary(true, raw, func(context.Context, MutationTarget) error { calls++; return ErrPending }) if err != nil { t.Fatal(err) } if _, err = guarded.Put(ctx, key, []byte("new"), tag); !errors.Is(err, ErrPending) { t.Fatal(err) } if err = guarded.Delete(ctx, key); !errors.Is(err, ErrPending) { t.Fatal(err) } if err = release.DeleteIfMatch(ctx, guarded, key, tag); !errors.Is(err, ErrPending) { t.Fatal(err) } if err = guarded.Delete(ctx, "apps/bab/demo/absent.json"); !errors.Is(err, ErrPending) { t.Fatal(err) } if calls != 4 { t.Fatalf("only %d writes reached the boundary", calls) } body, after, err := raw.Get(ctx, key) if err != nil || string(body) != "old" || after != tag { t.Fatalf("refused write changed storage: %q %q %v", body, after, err) } } func TestMutationBoundaryDeleteCannotEraseSuccessor(t *testing.T) { ctx := t.Context() raw := release.NewDirStore(t.TempDir()) key := "domains/one.example.json" tag, err := raw.Put(ctx, key, []byte("old"), "") if err != nil { t.Fatal(err) } guarded, err := NewMutationBoundary(true, raw, func(_ context.Context, m MutationTarget) error { if string(m.Before) != "old" || m.ETag != tag { t.Fatal("not the inspected incarnation") } _, e := raw.Put(ctx, key, []byte("successor"), tag) return e }) if err != nil { t.Fatal(err) } if err = guarded.Delete(ctx, key); !errors.Is(err, release.ErrCASConflict) { t.Fatalf("successor delete: %v", err) } body, _, err := raw.Get(ctx, key) if err != nil || string(body) != "successor" { t.Fatalf("successor: %q %v", body, err) } } func TestMutationBoundaryDisabledIsOriginalHandle(t *testing.T) { raw := release.NewDirStore(t.TempDir()) s, err := NewMutationBoundary(false, raw, func(context.Context, MutationTarget) error { t.Fatal("flag-off check"); return nil }) if err != nil || s != raw { t.Fatal("flag-off was wrapped") } if _, err = NewMutationBoundary(true, raw, nil); !errors.Is(err, ErrContract) { t.Fatal("enabled without production authority") } } func TestBoundaryForwardsReadExtensions(t *testing.T) { ctx := t.Context() raw := release.NewDirStore(t.TempDir()) const key = "apps/bab/demo/a.json" if _, err := raw.Put(ctx, key, []byte("a"), ""); err != nil { t.Fatal(err) } // The production chain: boundary over the record store over the bucket. records, err := NewConditionalRecordStore(raw, make([]byte, 32)) if err != nil { t.Fatal(err) } guarded, err := NewMutationBoundary(true, records, func(context.Context, MutationTarget) error { return nil }) if err != nil { t.Fatal(err) } cg, ok := guarded.(release.ConditionalGetter) if !ok { t.Fatal("boundary hides ConditionalGetter") } el, ok := guarded.(release.ETagLister) if !ok { t.Fatal("boundary hides ETagLister") } body, e, changed, err := cg.GetIfChanged(ctx, key, "") if err != nil || !changed || string(body) != "a" { t.Fatalf("GetIfChanged: %q %v %v", body, changed, err) } if _, _, changed, err = cg.GetIfChanged(ctx, key, e); err != nil || changed { t.Fatalf("unchanged: %v %v", changed, err) } got, err := el.ListETags(ctx, "apps/") if err != nil || len(got) != 1 || got[0].Key != key || got[0].ETag != e { t.Fatalf("ListETags: %v %v", got, err) } } // bareStore has none of the optional extensions. type bareStore struct{ release.Store } func TestBoundaryWithoutReadExtensionsIsContractError(t *testing.T) { ctx := t.Context() guarded, err := NewMutationBoundary(true, bareStore{release.NewDirStore(t.TempDir())}, func(context.Context, MutationTarget) error { return nil }) if err != nil { t.Fatal(err) } if _, _, _, err = guarded.(release.ConditionalGetter).GetIfChanged(ctx, "apps/bab/demo/a.json", ""); !errors.Is(err, ErrContract) { t.Fatalf("GetIfChanged: %v", err) } if _, err = guarded.(release.ETagLister).ListETags(ctx, "apps/"); !errors.Is(err, ErrContract) { t.Fatalf("ListETags: %v", err) } } func TestRecordStoreWithoutReadExtensionsIsContractError(t *testing.T) { ctx := t.Context() records, err := NewConditionalRecordStore(bareStore{release.NewDirStore(t.TempDir())}, make([]byte, 32)) if err != nil { t.Fatal(err) } if _, _, _, err = records.(release.ConditionalGetter).GetIfChanged(ctx, "apps/bab/demo/a.json", ""); !errors.Is(err, ErrContract) { t.Fatalf("GetIfChanged: %v", err) } if _, err = records.(release.ETagLister).ListETags(ctx, "apps/"); !errors.Is(err, ErrContract) { t.Fatalf("ListETags: %v", err) } }