| 1 | package console |
| 2 | |
| 3 | import ( |
| 4 | "net/http" |
| 5 | "net/url" |
| 6 | "testing" |
| 7 | |
| 8 | "github.com/carlosframework/platform/internal/registry" |
| 9 | "github.com/carlosframework/platform/internal/release" |
| 10 | ) |
| 11 | |
| 12 | func TestBrowserAppRestartUsesSharedGeneration(t *testing.T) { |
| 13 | fx := newConsoleFixture(t) |
| 14 | c := signInPaul(t, fx) |
| 15 | csrf := sessionToken(t, fx, c) |
| 16 | status, _, _ := postForm(t, c, fx.u("/apps/kass/restart"), url.Values{"csrf": {csrf}}) |
| 17 | if status != http.StatusOK { |
| 18 | t.Fatalf("confirm = %d", status) |
| 19 | } |
| 20 | if restartGenEtag(t, fx, "kass") != "" || len(scopeLog(t, fx, "kass").Requests) != 0 { |
| 21 | t.Fatal("confirmation page wrote restart state") |
| 22 | } |
| 23 | status, loc, _ := postGated(t, fx, c, fx.u("/apps/kass/restart"), url.Values{"csrf": {csrf}, "confirmed": {"1"}}) |
| 24 | if status != http.StatusSeeOther || loc != fx.path("/apps/kass/instances") { |
| 25 | t.Fatalf("restart = %d -> %q", status, loc) |
| 26 | } |
| 27 | if restartGenEtag(t, fx, "kass") == "" { |
| 28 | t.Fatal("browser restart did not touch app generation") |
| 29 | } |
| 30 | if got := serial(t, fx); got != 1 { |
| 31 | t.Fatalf("serial = %d", got) |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | func TestBrowserInstanceRestartIsExactAndNeverWidens(t *testing.T) { |
| 36 | fx := newConsoleFixture(t) |
| 37 | fx.addRoute(t, registry.Route{Host: "sleep.kass.test", App: "kass", Account: "bab", Kind: "instance", Status: "ready", Hibernate: true}) |
| 38 | c := signInPaul(t, fx) |
| 39 | csrf := sessionToken(t, fx, c) |
| 40 | endpoint := fx.u("/apps/kass/instances/sleep.kass.test/restart") |
| 41 | status, _, _ := postGated(t, fx, c, endpoint, url.Values{"csrf": {csrf}, "confirmed": {"1"}}) |
| 42 | if status != http.StatusSeeOther { |
| 43 | t.Fatalf("restart = %d", status) |
| 44 | } |
| 45 | if restartGenEtag(t, fx, "kass") != "" { |
| 46 | t.Fatal("instance restart widened into app generation") |
| 47 | } |
| 48 | log := scopeLog(t, fx, "kass") |
| 49 | if len(log.Requests) != 1 || log.Requests[0].Scope.Kind != release.RestartScopeHost || log.Requests[0].Scope.Value != "sleep.kass.test" { |
| 50 | t.Fatalf("scope log = %+v", log) |
| 51 | } |
| 52 | reg, _ := registry.Open(fx.regPath) |
| 53 | routes, _ := reg.List() |
| 54 | for _, rt := range routes { |
| 55 | if rt.Host == "sleep.kass.test" && !rt.Hibernate { |
| 56 | t.Fatal("restart request woke sleeping route") |
| 57 | } |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | func TestBrowserInstanceRestartRefusesUnaddressableHosts(t *testing.T) { |
| 62 | fx := newConsoleFixture(t) |
| 63 | fx.addRoute(t, registry.Route{Host: "foreign.test", App: "kass", Account: "elsewhere", Kind: "instance", Status: "ready"}) |
| 64 | c := signInPaul(t, fx) |
| 65 | csrf := sessionToken(t, fx, c) |
| 66 | for _, host := range []string{"missing.test", "foreign.test", "docs.kass.carlosapp.dev", "%20", "%09", "%20%20"} { |
| 67 | status, _, body := postGated(t, fx, c, fx.u("/apps/kass/instances/"+host+"/restart"), url.Values{"csrf": {csrf}, "confirmed": {"1"}}) |
| 68 | if status != http.StatusNotFound || body != "404 page not found\n" { |
| 69 | t.Errorf("%s = %d %q", host, status, body) |
| 70 | } |
| 71 | } |
| 72 | if restartGenEtag(t, fx, "kass") != "" || len(scopeLog(t, fx, "kass").Requests) != 0 { |
| 73 | t.Fatal("refusal wrote restart state") |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | func TestBrowserRestartRequiresCSRF(t *testing.T) { |
| 78 | fx := newConsoleFixture(t) |
| 79 | c := signInPaul(t, fx) |
| 80 | status, _, _ := postForm(t, c, fx.u("/apps/kass/restart"), url.Values{"csrf": {"wrong"}}) |
| 81 | if status != http.StatusForbidden { |
| 82 | t.Fatalf("csrf = %d", status) |
| 83 | } |
| 84 | status, _, _ = postForm(t, c, fx.u("/apps/no-such-app/restart"), url.Values{"csrf": {"wrong"}}) |
| 85 | if status != http.StatusForbidden { |
| 86 | t.Fatalf("csrf before app membership = %d", status) |
| 87 | } |
| 88 | } |
| 89 | |