package console import ( "context" "encoding/json" "net/http" "strings" "testing" "github.com/carlosframework/platform/internal/objstore" ) // The store doors take the environment as ?environment=, the same spelling // the config plane uses — a store delivers INTO a bundle, so the two naming // it differently would be a seam with no meaning. These pin the doors that // need no backend: declare and status. func storeEnvURL(fx *consoleFixture, app, env string) string { return storeURL(fx, app) + "?environment=" + env } func decodeReply(t *testing.T, body string) map[string]any { t.Helper() var out map[string]any if err := json.Unmarshal([]byte(body), &out); err != nil { t.Fatalf("decode reply %q: %v", body, err) } return out } // Declaring into an environment writes THAT environment's record, stamped // with the environment it lives under, and leaves the app's own store absent. func TestAPIStoreCreateInEnvironment(t *testing.T) { fx, token := storeFixture(t, "code-store-env-create") status, body := postRaw(t, storeEnvURL(fx, "titogo", "beta"), map[string]any{"env_prefix": "BLOBS"}, token) if status != http.StatusOK { t.Fatalf("create in an environment = %d %s, want 200", status, body) } if got := decodeReply(t, body)["environment"]; got != "beta" { t.Errorf("reply environment = %v, want %q", got, "beta") } ctx := context.Background() rec, _, err := objstore.GetIn(ctx, fx.rel, "bab", "titogo", "beta") if err != nil || rec == nil { t.Fatalf("environment record = %+v, %v", rec, err) } if rec.Environment != "beta" { t.Errorf("stored environment = %q, want %q", rec.Environment, "beta") } if rec.Declared == nil || rec.Declared.EnvPrefix != "BLOBS" { t.Errorf("declared = %+v, want the BLOBS prefix", rec.Declared) } // The app's own store is a different store, and nobody declared it. appRec, _, err := objstore.Get(ctx, fx.rel, "bab", "titogo") if err != nil { t.Fatal(err) } if appRec != nil { t.Errorf("declaring in an environment also wrote the app's own store: %+v", appRec) } } // An environment name the config plane would refuse is refused here, before // anything is written — including "default", which names the app's own // bundle rather than a named overlay. func TestAPIStoreEnvironmentNameRefused(t *testing.T) { fx, token := storeFixture(t, "code-store-env-bad") for _, env := range []string{"Beta", "default", "a_b", "this-name-is-far-too-long-to-be-an-environment"} { status, body := postRaw(t, storeEnvURL(fx, "titogo", env), map[string]any{}, token) if status != http.StatusBadRequest { t.Errorf("create with environment %q = %d %s, want 400", env, status, body) } } } // Status answers about the store it was asked about, and an app-store status // additionally names every environment this app has a store for — an // environment store nobody remembers is a bucket nobody remembers. func TestAPIStoreStatusListsEnvironments(t *testing.T) { fx, token := storeFixture(t, "code-store-env-status") for _, env := range []string{"beta", "alpha"} { if status, body := postRaw(t, storeEnvURL(fx, "titogo", env), map[string]any{}, token); status != http.StatusOK { t.Fatalf("declare %s = %d %s", env, status, body) } } // The app's own store, so the app-store status has something to answer // about at all. if status, body := postRaw(t, storeURL(fx, "titogo"), map[string]any{}, token); status != http.StatusOK { t.Fatalf("declare the app store = %d %s", status, body) } status, body := getRaw(t, storeURL(fx, "titogo"), token) if status != http.StatusOK { t.Fatalf("app store status = %d %s", status, body) } reply := decodeReply(t, body) if got := reply["environment"]; got != "" { t.Errorf("app store status environment = %v, want \"\"", got) } envs, _ := reply["environments"].([]any) if len(envs) != 2 || envs[0] != "alpha" || envs[1] != "beta" { t.Errorf("environments = %v, want [alpha beta] sorted", envs) } // Asked about one environment, it answers about that one and lists none. status, body = getRaw(t, storeEnvURL(fx, "titogo", "beta"), token) if status != http.StatusOK { t.Fatalf("environment status = %d %s", status, body) } reply = decodeReply(t, body) if got := reply["environment"]; got != "beta" { t.Errorf("environment status environment = %v, want %q", got, "beta") } if _, ok := reply["environments"]; ok { t.Error("a status aimed at one environment listed the app's environments") } } // An environment with no store 404s with a sentence naming the verb that // would create it — the app-store door's own convention, carried over. func TestAPIStoreStatusUndeclaredEnvironment(t *testing.T) { fx, token := storeFixture(t, "code-store-env-404") status, body := getRaw(t, storeEnvURL(fx, "titogo", "ghost"), token) if status != http.StatusNotFound { t.Fatalf("status for an undeclared environment = %d %s, want 404", status, body) } if !jsonErrorContains(t, body, "--environment ghost") { t.Errorf("404 body = %q, want it to name the environment and the verb", body) } } func jsonErrorContains(t *testing.T, body, want string) bool { t.Helper() var out struct { Error string `json:"error"` } if err := json.Unmarshal([]byte(body), &out); err != nil { return false } return out.Error != "" && strings.Contains(out.Error, want) }