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 "encoding/json"
5 "net/http"
6 "testing"
7 "time"
8)
9
10// TestAPIBuildProbeSetsAndClearsOnAnExistingApp is the whole point of the
11// door. Until it existed, SetAppBuildProbe had exactly one caller — the claim
12// door — so an app created without the flag could never acquire a probe, and
13// the fix for "deploy reported live over the old binary" was unreachable by
14// every app that already had the problem (discussion #13).
15func TestAPIBuildProbeSetsAndClearsOnAnExistingApp(t *testing.T) {
16 fx := newConsoleFixture(t)
17 token := cliToken(t, fx, "paul@keymail.dev", "code-buildprobe")
18 if _, err := fx.st.CreateAppIn(OpsAccountID, "docs", "paul@keymail.dev", time.Now()); err != nil {
19 t.Fatalf("create app: %v", err)
20 }
21
22 probeOf := func() string {
23 t.Helper()
24 _, probe, err := fx.st.AppFlags(OpsAccountID, "docs")
25 if err != nil {
26 t.Fatalf("AppFlags: %v", err)
27 }
28 return probe
29 }
30 if got := probeOf(); got != "" {
31 t.Fatalf("a fresh app already carries a probe: %q", got)
32 }
33
34 u := fx.ts.URL + "/api/cli/apps/" + fx.sqid + "/docs/build-probe"
35 status, _, body := rawReq(t, http.MethodPost, u, token, `{"build_probe":"/api/version"}`)
36 if status != http.StatusOK {
37 t.Fatalf("set = %d %q", status, body)
38 }
39 var out struct {
40 BuildProbe string `json:"build_probe"`
41 }
42 if err := json.Unmarshal([]byte(body), &out); err != nil {
43 t.Fatalf("unreadable: %v: %s", err, body)
44 }
45 // The echo is the contract: this is the field the deploy watch trusts
46 // instead of the header, so a write that silently failed has to be a loud
47 // error rather than a quiet lie. Response and store both.
48 if out.BuildProbe != "/api/version" {
49 t.Errorf("echo = %q, want /api/version", out.BuildProbe)
50 }
51 if got := probeOf(); got != "/api/version" {
52 t.Errorf("stored = %q, want /api/version", got)
53 }
54
55 // "" clears back to header-watching — a legal value of this field, not a
56 // missing one.
57 status, _, body = rawReq(t, http.MethodPost, u, token, `{"build_probe":""}`)
58 if status != http.StatusOK {
59 t.Fatalf("clear = %d %q", status, body)
60 }
61 if got := probeOf(); got != "" {
62 t.Errorf("after clear, stored = %q, want empty", got)
63 }
64}
65
66// TestAPIBuildProbeRefusesABadShape: the watch builds "https://" + host +
67// probe, so a probe not starting with "/" turns the poll into a request to a
68// machine of somebody else's choosing, which would then report ITS build as
69// this deploy's. Refused at the write door, not only where it is read.
70func TestAPIBuildProbeRefusesABadShape(t *testing.T) {
71 fx := newConsoleFixture(t)
72 token := cliToken(t, fx, "paul@keymail.dev", "code-buildprobebad")
73 if _, err := fx.st.CreateAppIn(OpsAccountID, "docs", "paul@keymail.dev", time.Now()); err != nil {
74 t.Fatalf("create app: %v", err)
75 }
76
77 u := fx.ts.URL + "/api/cli/apps/" + fx.sqid + "/docs/build-probe"
78 for _, bad := range []string{"@evil.example/x", "api/version", "/x?y=1", "https://evil.example/x"} {
79 payload, _ := json.Marshal(map[string]string{"build_probe": bad})
80 status, _, body := rawReq(t, http.MethodPost, u, token, string(payload))
81 if status != http.StatusBadRequest {
82 t.Errorf("probe %q = %d %q, want 400", bad, status, body)
83 }
84 }
85 if _, probe, _ := fx.st.AppFlags(OpsAccountID, "docs"); probe != "" {
86 t.Errorf("a refused probe still landed: %q", probe)
87 }
88}
89