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 "errors"
6 "net/http"
7 "net/url"
8 "strings"
9 "testing"
10 "time"
11
12 "github.com/carlosframework/platform/internal/registry"
13 "github.com/carlosframework/platform/internal/release"
14)
15
16func TestAccountReviewExcludesLegitimateSameNamedApp(t *testing.T) {
17 fx := newConsoleFixture(t)
18 const otherID = 4242
19 if err := fx.st.CreateAccount(otherID, "cdef", "Other", "paul@keymail.dev", time.Now()); err != nil {
20 t.Fatal(err)
21 }
22 if err := fx.st.AddMember(otherID, "paul@keymail.dev", RoleOwner, time.Now()); err != nil {
23 t.Fatal(err)
24 }
25 if _, err := fx.st.CreateAppIn(otherID, "kass", "paul@keymail.dev", time.Now()); err != nil {
26 t.Fatal(err)
27 }
28 fx.addRoute(t, registry.Route{Host: "other.example", App: "kass", Account: "cdef", Kind: "instance"})
29 viewed, err := fx.st.AccountBySqid("bab")
30 if err != nil || viewed == nil {
31 t.Fatal(err)
32 }
33 review, all, err := fx.srv.appAccountReview("paul@keymail.dev", viewed, "kass")
34 if err != nil {
35 t.Fatal(err)
36 }
37 if review.Count != 0 || all {
38 t.Fatalf("legitimate sibling became a repair candidate: %+v all=%v", review, all)
39 }
40}
41
42func TestAccountReviewSiblingPreventsUnrelatedMove(t *testing.T) {
43 fx := newConsoleFixture(t)
44 now := time.Now()
45 for _, tc := range []struct {
46 id int64
47 sqid string
48 name string
49 app bool
50 }{{4242, "cdef", "Sibling", true}, {4343, "cghi", "Candidate", false}} {
51 if err := fx.st.CreateAccount(tc.id, tc.sqid, tc.name, "paul@keymail.dev", now); err != nil {
52 t.Fatal(err)
53 }
54 if err := fx.st.AddMember(tc.id, "paul@keymail.dev", RoleOwner, now); err != nil {
55 t.Fatal(err)
56 }
57 if tc.app {
58 if _, err := fx.st.CreateAppIn(tc.id, "kass", "paul@keymail.dev", now); err != nil {
59 t.Fatal(err)
60 }
61 }
62 }
63 fx.addRoute(t, registry.Route{Host: "sibling.example", App: "kass", Account: "cdef", Kind: "instance"})
64 fx.addRoute(t, registry.Route{Host: "candidate.example", App: "kass", Account: "cghi", Kind: "instance"})
65 viewed, _ := fx.st.AccountBySqid("bab")
66 review, all, err := fx.srv.appAccountReview("paul@keymail.dev", viewed, "kass")
67 if err != nil {
68 t.Fatal(err)
69 }
70 if review.Count != 1 || all {
71 t.Fatalf("review = %+v all=%v; sibling must prevent move authority", review, all)
72 }
73 if _, agreed, err := fx.srv.routeAccountFor("paul@keymail.dev", viewed, "kass"); !errors.Is(err, errMoveTargetUnavailable) || agreed {
74 t.Fatalf("routeAccountFor agreed=%v err=%v; same-named sibling must hide the move target", agreed, err)
75 }
76}
77
78func TestAccountReviewHidesInaccessibleSource(t *testing.T) {
79 fx := newConsoleFixture(t)
80 fx.addRoute(t, registry.Route{Host: "secret.example", App: "kass", Account: "unknown", Kind: "instance"})
81 viewed, _ := fx.st.AccountBySqid("bab")
82 review, all, err := fx.srv.appAccountReview("paul@keymail.dev", viewed, "kass")
83 if err != nil {
84 t.Fatal(err)
85 }
86 if review.Count != 0 || all {
87 t.Fatalf("inaccessible source disclosed: %+v all=%v", review, all)
88 }
89 if _, agreed, err := fx.srv.routeAccountFor("paul@keymail.dev", viewed, "kass"); !errors.Is(err, errMoveTargetUnavailable) || agreed {
90 t.Fatalf("routeAccountFor agreed=%v err=%v; an inaccessible source must hide the move target", agreed, err)
91 }
92}
93
94func TestAccountReviewTreatsTrashedSameNamedAppAsSibling(t *testing.T) {
95 fx := newConsoleFixture(t)
96 const otherID = 4242
97 if err := fx.st.CreateAccount(otherID, "cdef", "Other", "paul@keymail.dev", time.Now()); err != nil {
98 t.Fatal(err)
99 }
100 if _, err := fx.st.CreateAppIn(otherID, "kass", "paul@keymail.dev", time.Now()); err != nil {
101 t.Fatal(err)
102 }
103 if err := fx.st.TrashAppRow(otherID, "kass", time.Now()); err != nil {
104 t.Fatal(err)
105 }
106 fx.addRoute(t, registry.Route{Host: "trashed-sibling.example", App: "kass", Account: "cdef", Kind: "instance"})
107 viewed, _ := fx.st.AccountBySqid("bab")
108 review, all, err := fx.srv.appAccountReview("paul@keymail.dev", viewed, "kass")
109 if err != nil {
110 t.Fatal(err)
111 }
112 if review.Count != 0 || all {
113 t.Fatalf("trashed sibling became a move candidate: %+v all=%v", review, all)
114 }
115 if _, agreed, err := fx.srv.routeAccountFor("paul@keymail.dev", viewed, "kass"); !errors.Is(err, errMoveTargetUnavailable) || agreed {
116 t.Fatalf("routeAccountFor agreed=%v err=%v; trashed sibling must hide the move target", agreed, err)
117 }
118}
119
120func TestAccountReviewPageShowsOwnedCandidatesAndHidesFromNonMembers(t *testing.T) {
121 fx := newConsoleFixture(t)
122 const otherID = 4242
123 if err := fx.st.CreateAccount(otherID, "cdef", "Other", "paul@keymail.dev", time.Now()); err != nil {
124 t.Fatal(err)
125 }
126 if err := fx.st.AddMember(otherID, "paul@keymail.dev", RoleOwner, time.Now()); err != nil {
127 t.Fatal(err)
128 }
129 fx.addRoute(t, registry.Route{Host: "candidate.example", App: "kass", Account: "cdef", Kind: "instance"})
130
131 c := signInPaul(t, fx)
132 status, _, body := get(t, c, fx.u("/apps/kass/account-review"))
133 if status != http.StatusOK || !strings.Contains(body, "candidate.example") || !strings.Contains(body, "Other") ||
134 !strings.Contains(body, approvedMismatchCopy.Partial) {
135 t.Fatalf("account review = %d, missing candidate/account/partial copy:\n%s", status, body)
136 }
137 reg, err := registry.Open(fx.regPath)
138 if err != nil {
139 t.Fatal(err)
140 }
141 routes, err := reg.List()
142 if err != nil {
143 t.Fatal(err)
144 }
145 for _, route := range routes {
146 if route.App == "kass" && !route.Platform {
147 if err := reg.SetAccount(route.Host, "cdef"); err != nil {
148 t.Fatal(err)
149 }
150 }
151 }
152 reg.Close()
153 status, _, body = get(t, c, fx.u("/apps/kass/account-review"))
154 if status != http.StatusOK || !strings.Contains(body, approvedMismatchCopy.Repairable) ||
155 strings.Contains(body, approvedMismatchCopy.Partial) {
156 t.Fatalf("fully mismatched review = %d, missing repairable branch:\n%s", status, body)
157 }
158
159 fx.setUser("eve@keymail.dev")
160 stranger := newBrowser(t)
161 signUp(t, fx, stranger, "code-account-review-stranger")
162 strangerCSRF := sessionToken(t, fx, stranger)
163 strangerStatus, _, strangerBody := get(t, stranger, fx.u("/apps/kass/account-review"))
164 ghostStatus, _, ghostBody := get(t, stranger, fx.ts.URL+"/definitely-not-a-page")
165 if strangerStatus != http.StatusNotFound || strangerStatus != ghostStatus || strangerBody != ghostBody {
166 t.Fatalf("non-member review = %d %q; unrouted = %d %q", strangerStatus, strangerBody, ghostStatus, ghostBody)
167 }
168 moveStatus, _, moveBody := postForm(t, stranger, fx.u("/apps/kass/move"), url.Values{"csrf": {strangerCSRF}})
169 if moveStatus != http.StatusNotFound || moveBody != ghostBody {
170 t.Fatalf("non-member move = %d %q; unrouted = %d %q", moveStatus, moveBody, ghostStatus, ghostBody)
171 }
172}
173
174func TestMoveRefusesHiddenOrSiblingSources(t *testing.T) {
175 for _, tc := range []struct {
176 name, account string
177 sibling bool
178 }{
179 {name: "hidden", account: "unknown"},
180 {name: "sibling", account: "cdef", sibling: true},
181 } {
182 t.Run(tc.name, func(t *testing.T) {
183 fx := newConsoleFixture(t)
184 if tc.sibling {
185 const otherID = 4242
186 if err := fx.st.CreateAccount(otherID, tc.account, "Other", "paul@keymail.dev", time.Now()); err != nil {
187 t.Fatal(err)
188 }
189 if _, err := fx.st.CreateAppIn(otherID, "kass", "paul@keymail.dev", time.Now()); err != nil {
190 t.Fatal(err)
191 }
192 }
193 fx.addRoute(t, registry.Route{Host: tc.name + ".example", App: "kass", Account: tc.account, Kind: "instance"})
194 c := signInPaul(t, fx)
195 csrf := sessionToken(t, fx, c)
196 status, _, body := postForm(t, c, fx.u("/apps/kass/move"), url.Values{"csrf": {csrf}})
197 ghostStatus, _, ghostBody := get(t, c, fx.ts.URL+"/definitely-not-a-page")
198 if status != http.StatusNotFound || status != ghostStatus || body != ghostBody {
199 t.Fatalf("unavailable move = %d %q; unrouted = %d %q", status, body, ghostStatus, ghostBody)
200 }
201 accounts, err := fx.st.AppAccounts("kass")
202 if err != nil || len(accounts) == 0 || accounts[0] != OpsAccountID {
203 t.Fatalf("AppAccounts after refusal = %v, %v", accounts, err)
204 }
205 })
206 }
207}
208
209func TestPublishedAppWithoutOwnedInstancesWarnsFromOwnedStateOnly(t *testing.T) {
210 fx := newConsoleFixture(t)
211 if _, err := fx.st.CreateAppIn(OpsAccountID, "empty", "paul@keymail.dev", time.Now()); err != nil {
212 t.Fatal(err)
213 }
214 c := signInPaul(t, fx)
215
216 _, _, empty := get(t, c, fx.u("/apps/empty"))
217 if strings.Contains(empty, `<p class="banner">`+approvedMismatchCopy.NoInstances) {
218 t.Fatalf("an unshipped empty app shows the published-app warning:\n%s", empty)
219 }
220
221 manifest := release.Manifest{
222 Schema: 1, App: "empty", Version: "v1", Kind: "binary", BuiltAt: time.Now().UTC().Format(time.RFC3339),
223 Artifacts: map[string]release.Artifact{"linux-amd64": {File: "empty"}},
224 }
225 if err := release.Ship(context.Background(), fx.rel, "bab", manifest,
226 map[string][]byte{"linux-amd64": []byte("binary")}); err != nil {
227 t.Fatal(err)
228 }
229 seedChannel(t, fx, "empty", "stable", "v1")
230 _, _, absentForeign := get(t, c, fx.u("/apps/empty"))
231 if !strings.Contains(absentForeign, `<p class="banner">`+approvedMismatchCopy.NoInstances) ||
232 !strings.Contains(absentForeign, `/apps/empty/instances">Instances</a>`) {
233 t.Fatalf("published app with no owned instances lacks the warning and Instances link:\n%s", absentForeign)
234 }
235 _, _, settings := get(t, c, fx.u("/apps/empty/settings"))
236 if !strings.Contains(settings, approvedMismatchCopy.NoInstances) || strings.Contains(settings, "This app deploys from") {
237 t.Fatalf("settings reassures a published app with no owned instances:\n%s", settings)
238 }
239
240 fx.addRoute(t, registry.Route{Host: "hidden.example", App: "empty", Account: "unknown", Kind: "instance"})
241 _, _, hiddenForeign := get(t, c, fx.u("/apps/empty"))
242 if hiddenForeign != absentForeign {
243 t.Fatal("a hidden foreign route changed the owned app warning render")
244 }
245
246 fx.addRoute(t, registry.Route{Host: "static.example", App: "empty", Account: "bab", Kind: "static"})
247 _, _, ownedStatic := get(t, c, fx.u("/apps/empty"))
248 if strings.Contains(ownedStatic, approvedMismatchCopy.NoInstances) {
249 t.Fatalf("an owned static route still shows the no-instances warning:\n%s", ownedStatic)
250 }
251}
252
253func TestAccountReviewDoesNotClaimOwnedInstancesWhenABlockerLeavesNone(t *testing.T) {
254 fx := newConsoleFixture(t)
255 const candidateID = 4242
256 if err := fx.st.CreateAccount(candidateID, "cdef", "Candidate", "paul@keymail.dev", time.Now()); err != nil {
257 t.Fatal(err)
258 }
259 if err := fx.st.AddMember(candidateID, "paul@keymail.dev", RoleOwner, time.Now()); err != nil {
260 t.Fatal(err)
261 }
262 const siblingID = 4343
263 if err := fx.st.CreateAccount(siblingID, "cghi", "Sibling", "paul@keymail.dev", time.Now()); err != nil {
264 t.Fatal(err)
265 }
266 if _, err := fx.st.CreateAppIn(siblingID, "empty", "paul@keymail.dev", time.Now()); err != nil {
267 t.Fatal(err)
268 }
269 if _, err := fx.st.CreateAppIn(OpsAccountID, "empty", "paul@keymail.dev", time.Now()); err != nil {
270 t.Fatal(err)
271 }
272 fx.addRoute(t, registry.Route{Host: "candidate.example", App: "empty", Account: "cdef", Kind: "instance"})
273 fx.addRoute(t, registry.Route{Host: "sibling.example", App: "empty", Account: "cghi", Kind: "instance"})
274
275 c := signInPaul(t, fx)
276 status, _, body := get(t, c, fx.u("/apps/empty/account-review"))
277 if status != http.StatusOK || !strings.Contains(body, "candidate.example") {
278 t.Fatalf("blocked review = %d, missing safe candidate:\n%s", status, body)
279 }
280 if strings.Contains(body, approvedMismatchCopy.Partial) || strings.Contains(body, "sibling.example") {
281 t.Fatalf("blocked review claims owned instances or exposes sibling:\n%s", body)
282 }
283}
284