package seeds import ( "context" "errors" "os" "strings" "testing" "github.com/carlosframework/platform/internal/release" ) func putOwners(t *testing.T, s release.Store, body, ifMatch string) string { t.Helper() etag, err := s.Put(context.Background(), LegacyOwnersKey, []byte(body), ifMatch) if err != nil { t.Fatal(err) } return etag } func TestLegacyOwners(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) o, etag, err := ReadLegacyOwners(ctx, s) if err != nil || etag != "" || o == nil || len(o.Names) != 0 { t.Fatalf("absent = %+v %q %v", o, etag, err) } if _, ok := o.Lookup("shop"); ok { t.Fatal("a name resolved from an absent file") } etag = putOwners(t, s, `{"v":1,"names":{"shop":{"account":"abc","app":"shop"}}}`, "") o, got, err := ReadLegacyOwners(ctx, s) if err != nil || got == "" || got != etag { t.Fatalf("read = %q (put %q) %v", got, etag, err) } if got, ok := o.Lookup("shop"); !ok || got != (LegacyOwner{Account: "abc", App: "shop"}) { t.Fatalf("lookup = %+v %v", got, ok) } if _, ok := o.Lookup("other"); ok { t.Fatal("an unassigned name resolved") } if _, _, err := ReadLegacyOwners(ctx, s); err != nil { t.Fatal(err) } putOwners(t, s, `{garbage`, etag) if _, _, err := ReadLegacyOwners(ctx, s); err == nil { t.Fatal("garbage read as no assignments") } } func TestLegacyOwnersEmptySet(t *testing.T) { s := release.NewDirStore(t.TempDir()) putOwners(t, s, `{"v":1}`, "") o, etag, err := ReadLegacyOwners(context.Background(), s) if err != nil || etag == "" || o == nil || o.Names == nil || len(o.Names) != 0 { t.Fatalf("empty v1 file = %+v %q %v", o, etag, err) } if _, ok := o.Lookup("shop"); ok { t.Fatal("a name resolved from an empty set") } } // Every present-but-unusable file is an error and no owners, never an empty // set: the swap treats "no assignment" as a refusal it can explain, and a // read it cannot trust as a failure of its own. func TestLegacyOwnersFailClosed(t *testing.T) { good := `"names":{"shop":{"account":"abc","app":"shop"}}` cases := map[string]string{ "garbage": `{garbage`, "empty body": ``, "not an object": `[]`, "null": `null`, "version missing": `{` + good + `}`, "version zero": `{"v":0,` + good + `}`, "version unknown": `{"v":2,` + good + `}`, "version negative": `{"v":-1,` + good + `}`, "version a string": `{"v":"1",` + good + `}`, "names a list": `{"v":1,"names":[]}`, "entry a string": `{"v":1,"names":{"shop":"abc/shop"}}`, "entry no account": `{"v":1,"names":{"shop":{"app":"shop"}}}`, "entry no app": `{"v":1,"names":{"shop":{"account":"abc"}}}`, "entry null": `{"v":1,"names":{"shop":null}}`, "empty name": `{"v":1,"names":{"":{"account":"abc","app":"shop"}}}`, "unknown field": `{"v":1,"extra":true,` + good + `}`, "unknown entry key": `{"v":1,"names":{"shop":{"account":"abc","app":"shop","x":1}}}`, "trailing object": `{"v":1,` + good + `}{"v":1}`, "trailing garbage": `{"v":1,` + good + `}x`, } for name, body := range cases { t.Run(name, func(t *testing.T) { s := release.NewDirStore(t.TempDir()) putOwners(t, s, body, "") o, etag, err := ReadLegacyOwners(context.Background(), s) if err == nil { t.Fatalf("read %q = %+v, want an error", body, o) } if o != nil || etag != "" { t.Fatalf("an error came with owners %+v / etag %q", o, etag) } if errors.Is(err, os.ErrNotExist) { t.Fatalf("a bad file reads as absent: %v", err) } if !strings.Contains(err.Error(), "legacy route owners") { t.Fatalf("error does not say what failed: %v", err) } }) } } // A store error other than not-found is an error too. func TestLegacyOwnersReadError(t *testing.T) { boom := errors.New("bucket unreachable") o, etag, err := ReadLegacyOwners(context.Background(), failingStore{Store: release.NewDirStore(t.TempDir()), err: boom}) if !errors.Is(err, boom) || o != nil || etag != "" { t.Fatalf("read error = %+v %q %v", o, etag, err) } var nilOwners *LegacyOwners if _, ok := nilOwners.Lookup("shop"); ok { t.Fatal("a nil set resolved a name") } } type failingStore struct { release.Store err error } func (f failingStore) Get(context.Context, string) ([]byte, string, error) { return nil, "", f.err }