| index 9a7bf0062..8218e3e58 100644 |
| --- a/apps_policy_test.go |
| +++ b/apps_policy_test.go |
| @@ -27,8 +27,11 @@ func policyChangeFor(t *testing.T, args ...string) (*policyChange, error) { |
| } |
| |
| // policyConsole is a fake console's policy door. getBody is the GET's |
| -// answer (getStatus when set); putStatus/putBody the PUT's. |
| +// answer (getStatus when set); putStatus/putBody the PUT's. accounts is the |
| +// memberships listing (GET /api/cli/apps); "" answers it as an older |
| +// console does, with a bare 404. calls leaves the listing out. |
| type policyConsole struct { |
| + accounts string |
| getStatus int |
| getBody string |
| putStatus int |
| @@ -45,6 +48,15 @@ func (c *policyConsole) session(t *testing.T) *apiSession { |
| t.Helper() |
| ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| b, _ := io.ReadAll(r.Body) |
| + if r.Method == http.MethodGet && r.URL.Path == "/api/cli/apps" { |
| + if c.accounts == "" { |
| + http.NotFound(w, r) |
| + return |
| + } |
| + w.Header().Set("Content-Type", "application/json") |
| + io.WriteString(w, c.accounts) |
| + return |
| + } |
| c.mu.Lock() |
| c.calls = append(c.calls, r.Method+" "+r.URL.Path) |
| c.mu.Unlock() |
| @@ -320,3 +332,48 @@ func TestAppsSetRunPolicyChangePrintsWhatTheConsoleStored(t *testing.T) { |
| t.Fatalf("err = %v", err) |
| } |
| } |
| + |
| +// The policy door takes the account's sqid and resolves no names, so a name |
| +// given with --account (or as the configured default) is resolved to the |
| +// sqid among the caller's memberships before the request; one no membership |
| +// names, such as another tenant's sqid, travels as it is. |
| +func TestAppPolicyResolvesAnAccountNameToItsSqid(t *testing.T) { |
| + ctx := context.Background() |
| + accounts := `{"accounts":[{"name":"shop-team","sqid":"bab","role":"owner","apps":["shop"]},{"name":"other","sqid":"zzz","role":"member","apps":[]}]}` |
| + captureStdout(t) |
| + |
| + c := &policyConsole{accounts: accounts, getBody: `{"policy":{"v":1},"etag":""}`} |
| + if err := appsShowRun(ctx, c.session(t), apiAcct{Seg: "Shop-Team"}, "shop"); err != nil { |
| + t.Fatal(err) |
| + } |
| + if want := []string{"GET /api/cli/apps/bab/shop/policy"}; !reflect.DeepEqual(c.calls, want) { |
| + t.Fatalf("apps show: requests = %v, want %v", c.calls, want) |
| + } |
| + |
| + c = &policyConsole{accounts: accounts, getBody: `{"policy":{"v":1},"etag":""}`, putBody: `{"policy":{"v":1,"default_size":"large"},"etag":"e1"}`} |
| + large := "large" |
| + if err := appsSetRun(ctx, c.session(t), apiAcct{Seg: "shop-team"}, "shop", nil, nil, &policyChange{DefaultSize: &large}); err != nil { |
| + t.Fatal(err) |
| + } |
| + if want := []string{"GET /api/cli/apps/bab/shop/policy", "PUT /api/cli/apps/bab/shop/policy"}; !reflect.DeepEqual(c.calls, want) { |
| + t.Fatalf("apps set: requests = %v, want %v", c.calls, want) |
| + } |
| + |
| + c = &policyConsole{accounts: accounts, getBody: `{"policy":{"v":1},"etag":""}`, putBody: `{"policy":{"v":1},"etag":"e1"}`} |
| + def := "default" |
| + if err := instancesSetRun(ctx, c.session(t), apiAcct{Seg: "shop-team"}, "shop", "shop-a.example.com", nil, nil, &def); err != nil { |
| + t.Fatal(err) |
| + } |
| + if want := []string{"GET /api/cli/apps/bab/shop/policy", "PUT /api/cli/apps/bab/shop/policy"}; !reflect.DeepEqual(c.calls, want) { |
| + t.Fatalf("instances set: requests = %v, want %v", c.calls, want) |
| + } |
| + |
| + // A sqid no membership names (an operator acting on another account). |
| + c = &policyConsole{accounts: accounts, getBody: `{"policy":{"v":1},"etag":""}`} |
| + if err := appsShowRun(ctx, c.session(t), apiAcct{Seg: "bcd"}, "shop"); err != nil { |
| + t.Fatal(err) |
| + } |
| + if want := []string{"GET /api/cli/apps/bcd/shop/policy"}; !reflect.DeepEqual(c.calls, want) { |
| + t.Fatalf("foreign sqid: requests = %v, want %v", c.calls, want) |
| + } |
| +} |