carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package main
2
3import (
4 "context"
5 "errors"
6 "io"
7 "net/http"
8 "net/http/httptest"
9 "strings"
10 "testing"
11)
12
13// recordNotFound turns the record door's membership 404 into what actually
14// went wrong, using the app's route list as the witness (#264): a host the
15// list carries is a box route with no record, a host it does not carry is a
16// miss, and a list that will not answer either leaves the membership riddle
17// standing, since then the account or app really may be wrong.
18func TestRecordNotFoundNarrowsTheMembership404(t *testing.T) {
19 routes := `{"routes":[{"host":"shop.example.com","kind":"instance","environment":"staging"},` +
20 `{"host":"canary-ab12.shop.acme.oncarlos.test","kind":"instance","channel":"canary/ab12","platform_canary":true}]}`
21 boxRoute := func(h string) string { return "BOX " + h }
22
23 cases := []struct {
24 name string
25 host string
26 routesOK bool
27 in error
28 want string // substring of the error; "" means the input error, unchanged
29 }{
30 {"box route with no record", "shop.example.com", true, notFoundErr(), "BOX shop.example.com"},
31 {"platform canary is not sent to the box", "canary-ab12.shop.acme.oncarlos.test", true, notFoundErr(), "is a canary route the platform mints for shop"},
32 {"no such host", "shop.exmaple.com", true, notFoundErr(), "shop has no instance named shop.exmaple.com"},
33 {"route list refused too", "shop.example.com", false, notFoundErr(), ""},
34 {"another error passes through", "shop.example.com", true, errors.New("could not read the instance"), ""},
35 }
36 for _, c := range cases {
37 t.Run(c.name, func(t *testing.T) {
38 listed := false
39 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
40 if !strings.HasSuffix(r.URL.Path, "/routes") {
41 t.Errorf("unexpected request %s", r.URL.Path)
42 }
43 listed = true
44 if !c.routesOK {
45 http.NotFound(w, r)
46 return
47 }
48 io.WriteString(w, routes)
49 }))
50 defer ts.Close()
51
52 err := recordNotFound(context.Background(), newTestSession(ts.URL),
53 apiAcct{Seg: "acme"}, "shop", c.host, c.in, boxRoute)
54 if c.want == "" {
55 if err != c.in {
56 t.Fatalf("err = %v, want the input error %v unchanged", err, c.in)
57 }
58 } else if err == nil || !strings.Contains(err.Error(), c.want) {
59 t.Fatalf("err = %v, want it to contain %q", err, c.want)
60 }
61 if errors.Is(c.in, errMembershipNotFound) != listed {
62 t.Fatalf("route list consulted = %v, want it only for the membership 404", listed)
63 }
64 })
65 }
66}
67
68// The membership message must survive as itself everywhere else: the
69// sentinel only changes how recordNotFound recognises it, not what it says.
70func TestNotFoundErrIsTheSentinel(t *testing.T) {
71 if !errors.Is(apiErrorFromBody(http.StatusNotFound, []byte("404 page not found\n")), errMembershipNotFound) {
72 t.Fatal("a text/plain 404 must come back as errMembershipNotFound")
73 }
74 if !strings.Contains(notFoundErr().Error(), "check --account and --app") {
75 t.Fatalf("notFoundErr() = %q, want the membership message", notFoundErr())
76 }
77}
78