package main import ( "context" "encoding/json" "io" "net/http" "net/http/httptest" "strings" "testing" ) // instanceCreate carries the config binding when one is asked for, and omits // the key entirely when it is not — so a create from a caller that never // heard of the flag is byte-identical to what it always sent. func TestInstanceCreateSendsEnvironmentOnlyWhenGiven(t *testing.T) { var gotBody string ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { b, _ := io.ReadAll(r.Body) gotBody = string(b) w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(map[string]any{ "host": "canary.example.com", "channel": "canary/x", "environment": "jam-canary", }) })) defer ts.Close() s := newTestSession(ts.URL) rec, err := s.instanceCreate(context.Background(), apiAcct{Seg: "bah"}, "app", "canary.example.com", "", "canary/x", "", "jam-canary", nil, nil, nil) if err != nil { t.Fatalf("instanceCreate: %v", err) } if !strings.Contains(gotBody, `"environment":"jam-canary"`) { t.Errorf("request body = %s, want it to carry the environment", gotBody) } // The decode matters as much as the send: `canary ls` prints this column, // and the whole point of the column is that a wrong binding is otherwise // invisible — the host serves GETs perfectly either way. if rec.Environment != "jam-canary" { t.Errorf("decoded environment = %q, want jam-canary", rec.Environment) } gotBody = "" if _, err := s.instanceCreate(context.Background(), apiAcct{Seg: "bah"}, "app", "plain.example.com", "", "", "", "", nil, nil, nil); err != nil { t.Fatalf("instanceCreate (no environment): %v", err) } if strings.Contains(gotBody, "environment") { t.Errorf("request body = %s, want no environment key when none was given", gotBody) } } // instanceSetEnvironment sends the field ALWAYS, empty string included: // clearing a binding — returning a route to the app's default bundle — is a // real request, and an omitempty here would silently turn it into a no-op // PATCH the door then refuses as "nothing to update". func TestInstanceSetEnvironmentSendsAnEmptyValue(t *testing.T) { var gotBody, gotMethod string ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotMethod = r.Method b, _ := io.ReadAll(r.Body) gotBody = string(b) json.NewEncoder(w).Encode(map[string]any{"host": "canary.example.com", "environment": ""}) })) defer ts.Close() s := newTestSession(ts.URL) rec, err := s.instanceSetEnvironment(context.Background(), apiAcct{Seg: "bah"}, "app", "canary.example.com", "") if err != nil { t.Fatalf("instanceSetEnvironment: %v", err) } if gotMethod != http.MethodPatch { t.Errorf("method = %s, want PATCH — this rides the same door as set-upstreams", gotMethod) } if !strings.Contains(gotBody, `"environment":""`) { t.Errorf("request body = %s, want an explicit empty environment", gotBody) } if rec.Environment != "" { t.Errorf("decoded environment = %q, want empty", rec.Environment) } } // environmentSuffix renders nothing for the default bundle. The empty case is // the common one, and printing "environment default" on every create would // train people to skim the parenthetical in the case that matters. func TestEnvironmentSuffix(t *testing.T) { for _, tc := range []struct{ env, want string }{ {"", ""}, {" ", ""}, {"jam-canary", ", environment jam-canary"}, } { if got := environmentSuffix(tc.env); got != tc.want { t.Errorf("environmentSuffix(%q) = %q, want %q", tc.env, got, tc.want) } } }