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 "errors"
5 "reflect"
6 "testing"
7 "time"
8
9 "github.com/carlosframework/platform/internal/svcrole"
10)
11
12func TestCanonicalRungs(t *testing.T) {
13 for _, tc := range []struct {
14 in []string
15 want string
16 }{
17 {nil, ""},
18 {[]string{" ", ""}, ""},
19 {[]string{"edge"}, "edge"},
20 {[]string{"edge", " canary ", "edge", ""}, "canary,edge"},
21 {[]string{"beta", "canary", "edge"}, "canary,edge,beta"},
22 } {
23 got, err := canonicalRungs(tc.in)
24 if err != nil || got != tc.want {
25 t.Errorf("canonicalRungs(%q) = %q, %v; want %q", tc.in, got, err, tc.want)
26 }
27 }
28 if _, err := canonicalRungs([]string{"edge", "stable"}); !errors.Is(err, ErrStableNotDelegable) {
29 t.Errorf("stable: err = %v, want ErrStableNotDelegable", err)
30 }
31 for _, bad := range []string{"Edge", "canary/x", "prod", "edge;beta"} {
32 var ue *UnknownRungError
33 if _, err := canonicalRungs([]string{bad}); !errors.As(err, &ue) || ue.Rung != bad {
34 t.Errorf("canonicalRungs(%q) err = %v, want UnknownRungError naming it", bad, err)
35 }
36 }
37}
38
39func TestParseStoredRungs(t *testing.T) {
40 if got, ok := parseStoredRungs("canary,edge"); !ok || !reflect.DeepEqual(got, []string{"canary", "edge"}) {
41 t.Fatalf("parseStoredRungs(canary,edge) = %v, %v", got, ok)
42 }
43 // Not canonical, empty, or naming stable: never a usable scope.
44 for _, bad := range []string{"", "edge,canary", "edge,edge", "edge,stable", "edge,", " edge"} {
45 if got, ok := parseStoredRungs(bad); ok {
46 t.Errorf("parseStoredRungs(%q) = %v, true; want false", bad, got)
47 }
48 }
49}
50
51func TestMintDeployRules(t *testing.T) {
52 st := newDeployStore(t)
53 now := time.Now()
54 mint := func(app string, role svcrole.Role, rungs []string) error {
55 _, err := st.NewServicePrincipalScoped(1, "ci", app, role, rungs, nil, "paul@example.test", now)
56 return err
57 }
58 if err := mint("", svcrole.Deploy, []string{"edge"}); !errors.Is(err, ErrAccountScopedDeploy) {
59 t.Errorf("deploy with no app: %v", err)
60 }
61 if err := mint("hello", svcrole.Deploy, nil); !errors.Is(err, ErrDeployNeedsRungs) {
62 t.Errorf("deploy with no rungs: %v", err)
63 }
64 if err := mint("hello", svcrole.Deploy, []string{"stable"}); !errors.Is(err, ErrStableNotDelegable) {
65 t.Errorf("deploy with stable: %v", err)
66 }
67 if err := mint("hello", svcrole.Publish, []string{"edge"}); !errors.Is(err, ErrRungsNeedDeploy) {
68 t.Errorf("publish with rungs: %v", err)
69 }
70 // The old entry point cannot mint a deploy credential: it has no rungs.
71 if _, err := st.NewServicePrincipal(1, "ci", "hello", svcrole.Deploy, "paul@example.test", now); !errors.Is(err, ErrDeployNeedsRungs) {
72 t.Errorf("NewServicePrincipal(deploy): %v", err)
73 }
74}
75
76func TestDeployPrincipalReadsBackItsRungs(t *testing.T) {
77 st := newDeployStore(t)
78 now := time.Now()
79 raw, err := st.NewServicePrincipalScoped(1, "ci", "hello", svcrole.Deploy, []string{"edge", "canary"}, nil, "paul@example.test", now)
80 if err != nil {
81 t.Fatal(err)
82 }
83 p, ok := st.CLIPrincipal(raw, now)
84 if !ok || p.Role != svcrole.Deploy || p.App != "hello" || !reflect.DeepEqual(p.Rungs, []string{"canary", "edge"}) {
85 t.Fatalf("CLIPrincipal = %+v, %v", p, ok)
86 }
87 // Review Focus 3: rotation keeps the rungs, and the old secret dies.
88 fresh, err := st.RotateServicePrincipal(1, "ci", now)
89 if err != nil {
90 t.Fatal(err)
91 }
92 if _, ok := st.CLIPrincipal(raw, now); ok {
93 t.Fatal("the old secret still resolves after rotate")
94 }
95 p, ok = st.CLIPrincipal(fresh, now)
96 if !ok || !reflect.DeepEqual(p.Rungs, []string{"canary", "edge"}) {
97 t.Fatalf("after rotate: %+v, %v", p, ok)
98 }
99 rows, err := st.ServicePrincipals(1)
100 if err != nil || len(rows) != 1 || rows[0].Rungs != "canary,edge" || rows[0].RungList() != "canary, edge" {
101 t.Fatalf("ServicePrincipals = %+v, %v", rows, err)
102 }
103}
104
105func TestDeployRowWithBadRungsDoesNotResolve(t *testing.T) {
106 st := newDeployStore(t)
107 now := time.Now()
108 for i, rungs := range []string{"", "edge,canary", "stable", "edge,beta,"} {
109 raw, err := st.NewServicePrincipalScoped(1, "ci"+string(rune('a'+i)), "hello", svcrole.Deploy, []string{"edge"}, nil, "paul@example.test", now)
110 if err != nil {
111 t.Fatal(err)
112 }
113 if _, err := st.db.Exec(`UPDATE cli_tokens SET rungs = ? WHERE token_hash = ?`, rungs, hashToken(raw)); err != nil {
114 t.Fatal(err)
115 }
116 if p, ok := st.CLIPrincipal(raw, now); ok {
117 t.Errorf("rungs %q: resolved to %+v; want refused", rungs, p)
118 }
119 }
120 // A deploy row whose app was emptied (rungs still valid) is refused too:
121 // a deploy credential is never account-wide.
122 raw, err := st.NewServicePrincipalScoped(1, "noapp", "hello", svcrole.Deploy, []string{"edge"}, nil, "paul@example.test", now)
123 if err != nil {
124 t.Fatal(err)
125 }
126 if _, err := st.db.Exec(`UPDATE cli_tokens SET app = '' WHERE token_hash = ?`, hashToken(raw)); err != nil {
127 t.Fatal(err)
128 }
129 if p, ok := st.CLIPrincipal(raw, now); ok {
130 t.Errorf("a deploy row with app '' resolved to %+v; want refused", p)
131 }
132 // A non-deploy row that somehow carries rungs is refused too.
133 raw, err = st.NewServicePrincipalScoped(1, "pub", "hello", svcrole.Publish, nil, nil, "paul@example.test", now)
134 if err != nil {
135 t.Fatal(err)
136 }
137 if _, err := st.db.Exec(`UPDATE cli_tokens SET rungs = 'edge' WHERE token_hash = ?`, hashToken(raw)); err != nil {
138 t.Fatal(err)
139 }
140 if _, ok := st.CLIPrincipal(raw, now); ok {
141 t.Error("a publish row carrying rungs resolved")
142 }
143}
144
145// newDeployStore is a bare console store, enough for the store-level tests.
146func newDeployStore(t *testing.T) *Store {
147 t.Helper()
148 st, err := Open(t.TempDir() + "/console.db")
149 if err != nil {
150 t.Fatal(err)
151 }
152 t.Cleanup(func() { st.Close() })
153 return st
154}
155