internal/control/orgscope_test.go
176 lines · 6054 bytes
1package control
2
3import (
4 "bytes"
5 "strings"
6 "testing"
7
8 "gitbay.org/gitbay/internal/config"
9 "gitbay.org/gitbay/internal/protocol"
10 "gitbay.org/gitbay/internal/store"
11)
12
13// orgFixture: alice admins org acme with acme/core (public) and acme/priv
14// (private); bob is a plain member; carol is outside. alice also owns
15// alice/app.
16type orgFixture struct {
17 st *store.Store
18 alice, bob, carol int64
19 org int64
20 core, priv, app store.Repo
21}
22
23func newOrgFixture(t *testing.T) orgFixture {
24 t.Helper()
25 st, err := store.Open(":memory:")
26 if err != nil {
27 t.Fatal(err)
28 }
29 t.Cleanup(func() { st.Close() })
30 if err := st.MigrateUp(); err != nil {
31 t.Fatal(err)
32 }
33 var f orgFixture
34 f.st = st
35 user := func(name string) int64 {
36 id, err := st.CreateUser(name, false)
37 if err != nil {
38 t.Fatal(err)
39 }
40 return id
41 }
42 f.alice, f.bob, f.carol = user("alice"), user("bob"), user("carol")
43 if f.org, err = st.CreateOrg("acme", f.alice); err != nil {
44 t.Fatal(err)
45 }
46 if err := st.SetOrgMember(f.org, f.bob, "member"); err != nil {
47 t.Fatal(err)
48 }
49 repo := func(kind string, owner int64, name, vis string) store.Repo {
50 id, err := st.CreateRepo(kind, owner, name, vis)
51 if err != nil {
52 t.Fatal(err)
53 }
54 r, _ := st.RepoByID(id)
55 return r
56 }
57 f.core = repo("org", f.org, "core", "public")
58 f.priv = repo("org", f.org, "priv", "private")
59 f.app = repo("user", f.alice, "app", "public")
60 return f
61}
62
63func (f orgFixture) ctx(uid int64) (*Ctx, *bytes.Buffer) {
64 var out bytes.Buffer
65 name := map[int64]string{f.alice: "alice", f.bob: "bob", f.carol: "carol"}[uid]
66 return &Ctx{
67 User: store.User{ID: uid, Username: name},
68 Scope: "full",
69 Source: "SHA256:session",
70 Store: f.st,
71 Cfg: config.Config{Server: config.Server{SiteURL: "https://x.test"}},
72 Stdin: strings.NewReader(""),
73 Stdout: &out,
74 Stderr: &out,
75 JSON: true,
76 }, &out
77}
78
79func TestReadableOrgRepoIDs(t *testing.T) {
80 f := newOrgFixture(t)
81 ids, err := ReadableOrgRepoIDs(f.st, store.User{ID: f.bob, Username: "bob"}, f.org)
82 if err != nil || len(ids) != 2 {
83 t.Fatalf("member reads %v, %v; want both", ids, err)
84 }
85 ids, _ = ReadableOrgRepoIDs(f.st, store.User{ID: f.carol, Username: "carol"}, f.org)
86 if len(ids) != 1 || ids[0] != f.core.ID {
87 t.Fatalf("outsider reads %v; want core only", ids)
88 }
89 ids, _ = ReadableOrgRepoIDs(f.st, store.User{}, f.org)
90 if len(ids) != 1 || ids[0] != f.core.ID {
91 t.Fatalf("anonymous reads %v; want core only", ids)
92 }
93 ids, _ = ReadableScope(f.st, store.User{ID: f.alice, Username: "alice"}, f.app)
94 if len(ids) != 1 || ids[0] != f.app.ID {
95 t.Fatalf("user repo scope %v; want itself", ids)
96 }
97}
98
99func TestRepoLabelCommandsRefuseOrgNames(t *testing.T) {
100 f := newOrgFixture(t)
101 if _, err := f.st.SetOrgLabel(f.org, "bug", ""); err != nil {
102 t.Fatal(err)
103 }
104 c, out := f.ctx(f.alice)
105 if code := runLabelSet(c, []string{"acme/core", "bug", "--color", "ff0000"}); code != protocol.ExitFailure ||
106 !strings.Contains(out.String(), "org label set acme bug") {
107 t.Fatalf("label set over org name: exit %d %s", code, out.String())
108 }
109 out.Reset()
110 if code := runLabelRemove(c, []string{"acme/core", "bug"}); code != protocol.ExitFailure ||
111 !strings.Contains(out.String(), "org label remove acme bug") {
112 t.Fatalf("label remove of org row: exit %d %s", code, out.String())
113 }
114 out.Reset()
115 // issue label --add resolves to the org row, and label list marks it.
116 iid, _ := f.st.CreateIssue(f.core.ID, f.alice, "c1", "", "md")
117 _ = iid
118 if code := runIssueLabel(c, []string{"acme/core", "1", "--add", "bug"}); code != protocol.ExitOK {
119 t.Fatalf("issue label: exit %d %s", code, out.String())
120 }
121 out.Reset()
122 if code := runLabelList(c, []string{"acme/core"}); code != protocol.ExitOK ||
123 !strings.Contains(out.String(), `"org":true`) || !strings.Contains(out.String(), `"issues":1`) {
124 t.Fatalf("label list: exit %d %s", code, out.String())
125 }
126}
127
128func TestRepoMilestoneCommandsRefuseOrgTitles(t *testing.T) {
129 f := newOrgFixture(t)
130 if _, _, err := f.st.CreateOrgMilestone(f.org, "v1", "", ""); err != nil {
131 t.Fatal(err)
132 }
133 c, out := f.ctx(f.alice)
134 if code := runMilestoneCreate(c, []string{"acme/core", "v1"}); code != protocol.ExitFailure ||
135 !strings.Contains(out.String(), "org milestone create acme v1") {
136 t.Fatalf("milestone create over org title: exit %d %s", code, out.String())
137 }
138 out.Reset()
139 if code := runMilestoneClose(c, []string{"acme/core", "v1"}); code != protocol.ExitFailure ||
140 !strings.Contains(out.String(), "org milestone close acme v1") {
141 t.Fatalf("milestone close of org row: exit %d %s", code, out.String())
142 }
143 out.Reset()
144 // Attaching by title from a repo resolves the org milestone.
145 f.st.CreateIssue(f.core.ID, f.alice, "c1", "", "md")
146 if code := runIssueMilestone(c, []string{"acme/core", "1", "v1"}); code != protocol.ExitOK {
147 t.Fatalf("issue milestone: exit %d %s", code, out.String())
148 }
149 out.Reset()
150 if code := runMilestoneList(c, []string{"acme/core"}); code != protocol.ExitOK ||
151 !strings.Contains(out.String(), `"org":true`) || !strings.Contains(out.String(), `"open":1`) {
152 t.Fatalf("milestone list: exit %d %s", code, out.String())
153 }
154}
155
156// A duplicate title fails; it is not a usage error, and the repo-level
157// and org-level commands answer with the same code.
158func TestDuplicateMilestoneTitleFails(t *testing.T) {
159 f := newOrgFixture(t)
160 c, out := f.ctx(f.alice)
161 if code := runMilestoneCreate(c, []string{"alice/app", "v1"}); code != protocol.ExitOK {
162 t.Fatalf("first create: exit %d %s", code, out.String())
163 }
164 out.Reset()
165 if code := runMilestoneCreate(c, []string{"alice/app", "v1"}); code != protocol.ExitFailure {
166 t.Fatalf("duplicate create: exit %d %s", code, out.String())
167 }
168 out.Reset()
169 if code := runOrgMilestoneCreate(c, []string{"acme", "v1"}); code != protocol.ExitOK {
170 t.Fatalf("first org create: exit %d %s", code, out.String())
171 }
172 out.Reset()
173 if code := runOrgMilestoneCreate(c, []string{"acme", "v1"}); code != protocol.ExitFailure {
174 t.Fatalf("duplicate org create: exit %d %s", code, out.String())
175 }
176}