carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3import (
4 "context"
5 "encoding/json"
6 "net/http"
7 "strings"
8 "testing"
9
10 "github.com/carlosframework/platform/internal/objstore"
11)
12
13// The store doors take the environment as ?environment=, the same spelling
14// the config plane uses — a store delivers INTO a bundle, so the two naming
15// it differently would be a seam with no meaning. These pin the doors that
16// need no backend: declare and status.
17
18func storeEnvURL(fx *consoleFixture, app, env string) string {
19 return storeURL(fx, app) + "?environment=" + env
20}
21
22func decodeReply(t *testing.T, body string) map[string]any {
23 t.Helper()
24 var out map[string]any
25 if err := json.Unmarshal([]byte(body), &out); err != nil {
26 t.Fatalf("decode reply %q: %v", body, err)
27 }
28 return out
29}
30
31// Declaring into an environment writes THAT environment's record, stamped
32// with the environment it lives under, and leaves the app's own store absent.
33func TestAPIStoreCreateInEnvironment(t *testing.T) {
34 fx, token := storeFixture(t, "code-store-env-create")
35
36 status, body := postRaw(t, storeEnvURL(fx, "titogo", "beta"), map[string]any{"env_prefix": "BLOBS"}, token)
37 if status != http.StatusOK {
38 t.Fatalf("create in an environment = %d %s, want 200", status, body)
39 }
40 if got := decodeReply(t, body)["environment"]; got != "beta" {
41 t.Errorf("reply environment = %v, want %q", got, "beta")
42 }
43
44 ctx := context.Background()
45 rec, _, err := objstore.GetIn(ctx, fx.rel, "bab", "titogo", "beta")
46 if err != nil || rec == nil {
47 t.Fatalf("environment record = %+v, %v", rec, err)
48 }
49 if rec.Environment != "beta" {
50 t.Errorf("stored environment = %q, want %q", rec.Environment, "beta")
51 }
52 if rec.Declared == nil || rec.Declared.EnvPrefix != "BLOBS" {
53 t.Errorf("declared = %+v, want the BLOBS prefix", rec.Declared)
54 }
55 // The app's own store is a different store, and nobody declared it.
56 appRec, _, err := objstore.Get(ctx, fx.rel, "bab", "titogo")
57 if err != nil {
58 t.Fatal(err)
59 }
60 if appRec != nil {
61 t.Errorf("declaring in an environment also wrote the app's own store: %+v", appRec)
62 }
63}
64
65// An environment name the config plane would refuse is refused here, before
66// anything is written — including "default", which names the app's own
67// bundle rather than a named overlay.
68func TestAPIStoreEnvironmentNameRefused(t *testing.T) {
69 fx, token := storeFixture(t, "code-store-env-bad")
70
71 for _, env := range []string{"Beta", "default", "a_b", "this-name-is-far-too-long-to-be-an-environment"} {
72 status, body := postRaw(t, storeEnvURL(fx, "titogo", env), map[string]any{}, token)
73 if status != http.StatusBadRequest {
74 t.Errorf("create with environment %q = %d %s, want 400", env, status, body)
75 }
76 }
77}
78
79// Status answers about the store it was asked about, and an app-store status
80// additionally names every environment this app has a store for — an
81// environment store nobody remembers is a bucket nobody remembers.
82func TestAPIStoreStatusListsEnvironments(t *testing.T) {
83 fx, token := storeFixture(t, "code-store-env-status")
84
85 for _, env := range []string{"beta", "alpha"} {
86 if status, body := postRaw(t, storeEnvURL(fx, "titogo", env), map[string]any{}, token); status != http.StatusOK {
87 t.Fatalf("declare %s = %d %s", env, status, body)
88 }
89 }
90 // The app's own store, so the app-store status has something to answer
91 // about at all.
92 if status, body := postRaw(t, storeURL(fx, "titogo"), map[string]any{}, token); status != http.StatusOK {
93 t.Fatalf("declare the app store = %d %s", status, body)
94 }
95
96 status, body := getRaw(t, storeURL(fx, "titogo"), token)
97 if status != http.StatusOK {
98 t.Fatalf("app store status = %d %s", status, body)
99 }
100 reply := decodeReply(t, body)
101 if got := reply["environment"]; got != "" {
102 t.Errorf("app store status environment = %v, want \"\"", got)
103 }
104 envs, _ := reply["environments"].([]any)
105 if len(envs) != 2 || envs[0] != "alpha" || envs[1] != "beta" {
106 t.Errorf("environments = %v, want [alpha beta] sorted", envs)
107 }
108
109 // Asked about one environment, it answers about that one and lists none.
110 status, body = getRaw(t, storeEnvURL(fx, "titogo", "beta"), token)
111 if status != http.StatusOK {
112 t.Fatalf("environment status = %d %s", status, body)
113 }
114 reply = decodeReply(t, body)
115 if got := reply["environment"]; got != "beta" {
116 t.Errorf("environment status environment = %v, want %q", got, "beta")
117 }
118 if _, ok := reply["environments"]; ok {
119 t.Error("a status aimed at one environment listed the app's environments")
120 }
121}
122
123// An environment with no store 404s with a sentence naming the verb that
124// would create it — the app-store door's own convention, carried over.
125func TestAPIStoreStatusUndeclaredEnvironment(t *testing.T) {
126 fx, token := storeFixture(t, "code-store-env-404")
127
128 status, body := getRaw(t, storeEnvURL(fx, "titogo", "ghost"), token)
129 if status != http.StatusNotFound {
130 t.Fatalf("status for an undeclared environment = %d %s, want 404", status, body)
131 }
132 if !jsonErrorContains(t, body, "--environment ghost") {
133 t.Errorf("404 body = %q, want it to name the environment and the verb", body)
134 }
135}
136
137func jsonErrorContains(t *testing.T, body, want string) bool {
138 t.Helper()
139 var out struct {
140 Error string `json:"error"`
141 }
142 if err := json.Unmarshal([]byte(body), &out); err != nil {
143 return false
144 }
145 return out.Error != "" && strings.Contains(out.Error, want)
146}
147