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 "bytes"
5 "context"
6 "encoding/json"
7 "errors"
8 "flag"
9 "io"
10 "net/http"
11 "net/http/httptest"
12 "os"
13 "path/filepath"
14 "reflect"
15 "strings"
16 "testing"
17
18 "github.com/carlosframework/platform/internal/plans"
19)
20
21func TestAccountsPlanParsesPacks(t *testing.T) {
22 var p packFlags
23 for _, v := range []string{"apps=2", "email=1"} {
24 if err := p.Set(v); err != nil {
25 t.Fatalf("Set(%q): %v", v, err)
26 }
27 }
28 if !reflect.DeepEqual(map[string]int(p), map[string]int{"apps": 2, "email": 1}) {
29 t.Fatalf("packs %v", p)
30 }
31 for _, bad := range []string{"apps", "apps=x", "=2", "apps=2=3"} {
32 var q packFlags
33 if err := q.Set(bad); err == nil {
34 t.Errorf("Set(%q) accepted", bad)
35 }
36 }
37}
38
39// planServer answers the plan door with reply and records each request.
40func planServer(t *testing.T, reply accountPlanReply) (*httptest.Server, *[]string, *[]setAccountPlanRequest) {
41 t.Helper()
42 var paths []string
43 var bodies []setAccountPlanRequest
44 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
45 paths = append(paths, r.Method+" "+r.URL.RequestURI())
46 if r.Method == http.MethodPost {
47 var req setAccountPlanRequest
48 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
49 t.Errorf("decode request: %v", err)
50 }
51 bodies = append(bodies, req)
52 }
53 json.NewEncoder(w).Encode(reply)
54 }))
55 t.Cleanup(ts.Close)
56 return ts, &paths, &bodies
57}
58
59func TestAccountsPlanShowAndSet(t *testing.T) {
60 reply := accountPlanReply{
61 Account: "bac", Plan: "personal", Packs: map[string]int{"apps": 2}, Source: "operator",
62 Reason: "upgraded", UpdatedBy: "ops@example.com", Revision: 2, PublishedRevision: 2,
63 Limits: &plans.Limits{RunningThings: 35, IdleSeconds: 900, MemoryTier: "default", DiskBytes: 10 << 30,
64 EmailPerDay: 1000, CustomDomains: true, HistoryDays: 7},
65 }
66 ts, paths, bodies := planServer(t, reply)
67 s := newTestSession(ts.URL)
68
69 var out bytes.Buffer
70 if err := runAccountsPlanWithSession(context.Background(), s, []string{"--account", "bac"}, &out); err != nil {
71 t.Fatal(err)
72 }
73 text := out.String()
74 for _, want := range []string{"bac", "personal", "apps 2", "35", "15 minutes", "10 GB", "1000", "7 days", "ops@example.com", "upgraded"} {
75 if !strings.Contains(text, want) {
76 t.Errorf("show output lacks %q:\n%s", want, text)
77 }
78 }
79
80 sponsored := filepath.Join(t.TempDir(), "limits.json")
81 if err := os.WriteFile(sponsored, []byte(`{"running_things": 40}`), 0o600); err != nil {
82 t.Fatal(err)
83 }
84 args := []string{"--account", "bac", "--set", "personal", "--pack", "apps=2", "--sponsored", sponsored, "--reason", "existing"}
85 if err := runAccountsPlanWithSession(context.Background(), s, args, io.Discard); err != nil {
86 t.Fatal(err)
87 }
88 want := []string{"GET /api/cli/accounts/bac/plan", "POST /api/cli/accounts/bac/plan"}
89 if !reflect.DeepEqual(*paths, want) {
90 t.Fatalf("paths %v, want %v", *paths, want)
91 }
92 got := (*bodies)[0]
93 if got.Plan != "personal" || got.Reason != "existing" || got.Packs["apps"] != 2 ||
94 got.Sponsored == nil || got.Sponsored.RunningThings != 40 {
95 t.Fatalf("request %+v", got)
96 }
97}
98
99func TestAccountsPlanValidationStopsBeforeNetwork(t *testing.T) {
100 hits := 0
101 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { hits++ }))
102 defer ts.Close()
103 s := newTestSession(ts.URL)
104 missing := filepath.Join(t.TempDir(), "missing.json")
105 for _, args := range [][]string{
106 {}, // no account
107 {"--account", "bac", "--set", "free"}, // no reason
108 {"--account", "bac", "--pack", "apps=1"}, // pack without set
109 {"--account", "bac", "--reason", "r"}, // reason without set
110 {"--account", "bac", "--set", "free", "--pack", "apps", "--reason", "r"},
111 {"--account", "bac", "--set", "free", "--sponsored", missing, "--reason", "r"},
112 {"--account", "bac", "extra"},
113 } {
114 if err := runAccountsPlanWithSession(context.Background(), s, args, io.Discard); err == nil {
115 t.Errorf("%v: accepted", args)
116 }
117 }
118 var help bytes.Buffer
119 if err := runAccountsPlanWithSession(context.Background(), s, []string{"--help"}, &help); !errors.Is(err, flag.ErrHelp) {
120 t.Fatalf("--help: %v", err)
121 }
122 if hits != 0 {
123 t.Fatalf("%d requests reached the console", hits)
124 }
125}
126
127func TestAccountsPlanRefusesAMisspeltSponsoredFile(t *testing.T) {
128 hits := 0
129 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { hits++ }))
130 defer ts.Close()
131 f := filepath.Join(t.TempDir(), "limits.json")
132 if err := os.WriteFile(f, []byte(`{"running_thing": 40}`), 0o600); err != nil {
133 t.Fatal(err)
134 }
135 args := []string{"--account", "bac", "--set", "free", "--sponsored", f, "--reason", "r"}
136 if err := runAccountsPlanWithSession(context.Background(), newTestSession(ts.URL), args, io.Discard); err == nil {
137 t.Fatal("accepted a sponsored file with an unknown field")
138 }
139 if hits != 0 {
140 t.Fatalf("%d requests reached the console", hits)
141 }
142}
143