Commit b46c9682ce
Verified · cmc
Layout: unified · split
internal/control/build.go +10 −1
| @@ -564,12 +564,21 @@ func runSecretList(c *Ctx, args []string) int { | ||
| 564 | 564 | if err != nil { |
| 565 | 565 | return c.fail(protocol.ExitFailure, "%v", err) |
| 566 | 566 | } |
| 567 | return c.emit(names, func(w io.Writer) { | |
| 567 | return c.emitView(names, func(w io.Writer) { | |
| 568 | 568 | tb := c.table(w, "NAME") |
| 569 | 569 | for _, n := range names { |
| 570 | 570 | tb.row(cRef(n)) |
| 571 | 571 | } |
| 572 | 572 | tb.flush() |
| 573 | }, func() screen { | |
| 574 | rows := make([]row, len(names)) | |
| 575 | for i, n := range names { | |
| 576 | rows[i] = rowOf(cRef(n)) | |
| 577 | } | |
| 578 | return listScreen("Build secrets", rows, | |
| 579 | action{"Secrets", []string{"repo", "secret", "set", repo.Path(), "<NAME>"}}, | |
| 580 | action{"Secrets", []string{"repo", "secret", "remove", repo.Path(), "<NAME>"}}, | |
| 581 | ) | |
| 573 | 582 | }) |
| 574 | 583 | } |
| 575 | 584 | |
internal/control/deploykey.go +11 −1
| @@ -119,13 +119,23 @@ func runDeployKeyList(c *Ctx, args []string) int { | ||
| 119 | 119 | ds = append(ds, out{k.Fingerprint, k.Algo, mode, k.Label, k.LastUsedAt, k.ExpiresAt}) |
| 120 | 120 | } |
| 121 | 121 | now := time.Now() |
| 122 | return c.emit(ds, func(w io.Writer) { | |
| 122 | return c.emitView(ds, func(w io.Writer) { | |
| 123 | 123 | tb := c.table(w, "FINGERPRINT", "ALGO", "MODE", "LABEL", "USED", "EXPIRES") |
| 124 | 124 | for _, d := range ds { |
| 125 | 125 | tb.row(cFlex(d.Fingerprint), cText(d.Algo), cState(d.Mode), cText(d.Label), |
| 126 | 126 | cText(c.usedText(d.LastUsedAt)), cText(c.expiresText(d.ExpiresAt, now))) |
| 127 | 127 | } |
| 128 | 128 | tb.flush() |
| 129 | }, func() screen { | |
| 130 | rows := make([]row, len(ds)) | |
| 131 | for i, d := range ds { | |
| 132 | rows[i] = rowOf(cRef(d.Fingerprint), cState(d.Mode), cText(d.Label), | |
| 133 | cMeta(d.Algo, "used "+c.usedText(d.LastUsedAt), c.expiresText(d.ExpiresAt, now))) | |
| 134 | } | |
| 135 | return listScreen("Deploy keys", rows, | |
| 136 | action{"Keys", []string{"repo", "deploy-key", "add", repo.Path()}}, | |
| 137 | action{"Keys", []string{"repo", "deploy-key", "remove", repo.Path(), "<fingerprint>"}}, | |
| 138 | ) | |
| 129 | 139 | }) |
| 130 | 140 | } |
| 131 | 141 | |
internal/control/mirrorcmd.go +21 −1
| @@ -122,7 +122,7 @@ func runMirrorList(c *Ctx, args []string) int { | ||
| 122 | 122 | // The token never leaves the server, in any encoding. |
| 123 | 123 | ds = append(ds, out{m.ID, m.Direction, m.URL, m.Username, m.Dirty, m.LastSync, m.LastError}) |
| 124 | 124 | } |
| 125 | return c.emit(ds, func(w io.Writer) { | |
| 125 | return c.emitView(ds, func(w io.Writer) { | |
| 126 | 126 | tb := c.table(w, "ID", "DIRECTION", "URL", "LAST", "STATUS") |
| 127 | 127 | for _, d := range ds { |
| 128 | 128 | status := "ok" |
| @@ -135,6 +135,26 @@ func runMirrorList(c *Ctx, args []string) int { | ||
| 135 | 135 | tb.row(cRef(fmt.Sprintf("%d", d.ID)), cText(d.Direction), cText(d.URL), cText("last "+orDash(d.LastSync)), cState(status)) |
| 136 | 136 | } |
| 137 | 137 | tb.flush() |
| 138 | }, func() screen { | |
| 139 | rows := make([]row, len(ds)) | |
| 140 | for i, d := range ds { | |
| 141 | state := "ok" | |
| 142 | if d.Pending { | |
| 143 | state = "pending" | |
| 144 | } | |
| 145 | if d.LastError != "" { | |
| 146 | state = "error" | |
| 147 | } | |
| 148 | synced := "never synced" | |
| 149 | if d.LastSync != "" { | |
| 150 | synced = "synced " + relAge(d.LastSync, termNow()) | |
| 151 | } | |
| 152 | rows[i] = rowOf(cRef(strconv.FormatInt(d.ID, 10)), cGlyph(state), cFlex(d.URL), cMeta(d.Direction, synced), cMark(d.LastError, sgrRed)) | |
| 153 | } | |
| 154 | return listScreen("Mirrors", rows, | |
| 155 | action{"Mirrors", []string{"repo", "mirror", "sync", repo.Path()}}, | |
| 156 | action{"Mirrors", []string{"repo", "mirror", "remove", repo.Path(), "<id>"}}, | |
| 157 | ) | |
| 138 | 158 | }) |
| 139 | 159 | } |
| 140 | 160 | |
internal/control/pagescmd.go +14 −1
| @@ -220,11 +220,24 @@ func runDomainList(c *Ctx, args []string) int { | ||
| 220 | 220 | } |
| 221 | 221 | list = append(list, out{d.Domain, state, d.VerifiedAt}) |
| 222 | 222 | } |
| 223 | return c.emit(list, func(w io.Writer) { | |
| 223 | return c.emitView(list, func(w io.Writer) { | |
| 224 | 224 | tb := c.table(w, "DOMAIN", "STATE") |
| 225 | 225 | for _, d := range list { |
| 226 | 226 | tb.row(cRef(d.Domain), cState(d.State)) |
| 227 | 227 | } |
| 228 | 228 | tb.flush() |
| 229 | }, func() screen { | |
| 230 | rows := make([]row, len(list)) | |
| 231 | for i, d := range list { | |
| 232 | state := d.State | |
| 233 | if state == "expired" { | |
| 234 | state = "failed" | |
| 235 | } | |
| 236 | rows[i] = rowOf(cGlyph(state), cRef(d.Domain), cState(d.State)) | |
| 237 | } | |
| 238 | return listScreen("Pages domains", rows, | |
| 239 | action{"Domains", []string{"repo", "domain", "add", repo.Path(), "<domain>"}}, | |
| 240 | action{"Domains", []string{"repo", "domain", "verify", repo.Path(), "<domain>"}}, | |
| 241 | ) | |
| 229 | 242 | }) |
| 230 | 243 | } |
internal/control/repo.go +70 −6
| @@ -360,7 +360,7 @@ func runRepoList(c *Ctx, args []string) int { | ||
| 360 | 360 | desc := gitutil.ReadDescription(RepoDir(c.Cfg.Server.Root, r.OwnerName, r.Name)) |
| 361 | 361 | ds = append(ds, out{r.Path(), r.Visibility, desc, r.Settings.Archived}) |
| 362 | 362 | } |
| 363 | return c.emitPage(p, ds, next, func(w io.Writer) { | |
| 363 | return c.emitPageView(p, ds, next, func(w io.Writer) { | |
| 364 | 364 | tb := c.table(w, "PATH", "VISIBILITY", "DESCRIPTION") |
| 365 | 365 | for _, d := range ds { |
| 366 | 366 | cells := []cell{cLink(d.Path, c.siteURL(d.Path)), cState(d.Visibility), cFlex(d.Description)} |
| @@ -370,6 +370,20 @@ func runRepoList(c *Ctx, args []string) int { | ||
| 370 | 370 | tb.row(cells...) |
| 371 | 371 | } |
| 372 | 372 | tb.flush() |
| 373 | }, func() screen { | |
| 374 | rows := make([]row, len(ds)) | |
| 375 | for i, d := range ds { | |
| 376 | state := d.Visibility | |
| 377 | if d.Archived { | |
| 378 | state += ", archived" | |
| 379 | } | |
| 380 | rows[i] = rowOf(cLink(d.Path, c.siteURL(d.Path)), cState(state), cFlex(d.Description)) | |
| 381 | } | |
| 382 | s := listScreen("Repositories", rows) | |
| 383 | if len(ds) > 0 { | |
| 384 | s.actions = []action{{"Read", []string{"repo", "show", ds[0].Path}}} | |
| 385 | } | |
| 386 | return s | |
| 373 | 387 | }) |
| 374 | 388 | } |
| 375 | 389 | |
| @@ -771,12 +785,21 @@ func runAccessList(c *Ctx, args []string) int { | ||
| 771 | 785 | for _, e := range entries { |
| 772 | 786 | ds = append(ds, out{e.Username, e.Role, e.Source}) |
| 773 | 787 | } |
| 774 | return c.emit(ds, func(w io.Writer) { | |
| 788 | return c.emitView(ds, func(w io.Writer) { | |
| 775 | 789 | tb := c.table(w, "USER", "ROLE", "SOURCE") |
| 776 | 790 | for _, d := range ds { |
| 777 | 791 | tb.row(cRef(d.User), cState(d.Role), cText("via "+d.Source)) |
| 778 | 792 | } |
| 779 | 793 | tb.flush() |
| 794 | }, func() screen { | |
| 795 | rows := make([]row, len(ds)) | |
| 796 | for i, d := range ds { | |
| 797 | rows[i] = rowOf(cRef(d.User), cState(d.Role), cMeta("via "+d.Source)) | |
| 798 | } | |
| 799 | return listScreen("Access", rows, | |
| 800 | action{"Access", []string{"repo", "access", "grant", repo.Path(), "<user>", "write"}}, | |
| 801 | action{"Access", []string{"repo", "access", "revoke", repo.Path(), "<user>"}}, | |
| 802 | ) | |
| 780 | 803 | }) |
| 781 | 804 | } |
| 782 | 805 | |
| @@ -960,6 +983,19 @@ func archiveRepo(c *Ctx, repo store.Repo, archived bool) int { | ||
| 960 | 983 | return c.emit(s, func(w io.Writer) { fmt.Fprintf(w, "%sd %s\n", verb, repo.Path()) }) |
| 961 | 984 | } |
| 962 | 985 | |
| 986 | // topicsScreen is a repository's topics at a terminal, after a read or | |
| 987 | // an edit. | |
| 988 | func topicsScreen(repo store.Repo, topics []string) screen { | |
| 989 | rows := make([]row, len(topics)) | |
| 990 | for i, t := range topics { | |
| 991 | rows[i] = rowOf(cRef(t)) | |
| 992 | } | |
| 993 | return listScreen("Topics", rows, | |
| 994 | action{"Edit", []string{"repo", "topics", "add", repo.Path(), "<topic>"}}, | |
| 995 | action{"Edit", []string{"repo", "topics", "remove", repo.Path(), "<topic>"}}, | |
| 996 | ) | |
| 997 | } | |
| 998 | ||
| 963 | 999 | func runTopicsList(c *Ctx, args []string) int { |
| 964 | 1000 | if len(args) != 1 { |
| 965 | 1001 | return c.usage() |
| @@ -972,12 +1008,14 @@ func runTopicsList(c *Ctx, args []string) int { | ||
| 972 | 1008 | if err != nil { |
| 973 | 1009 | return c.fail(protocol.ExitFailure, "%v", err) |
| 974 | 1010 | } |
| 975 | return c.emit(topics, func(w io.Writer) { | |
| 1011 | return c.emitView(topics, func(w io.Writer) { | |
| 976 | 1012 | tb := c.table(w, "TOPIC") |
| 977 | 1013 | for _, t := range topics { |
| 978 | 1014 | tb.row(cRef(t)) |
| 979 | 1015 | } |
| 980 | 1016 | tb.flush() |
| 1017 | }, func() screen { | |
| 1018 | return topicsScreen(repo, topics) | |
| 981 | 1019 | }) |
| 982 | 1020 | } |
| 983 | 1021 | |
| @@ -1031,12 +1069,14 @@ func editTopics(c *Ctx, args []string, add bool) int { | ||
| 1031 | 1069 | if err != nil { |
| 1032 | 1070 | return c.fail(protocol.ExitFailure, "%v", err) |
| 1033 | 1071 | } |
| 1034 | return c.emit(now, func(w io.Writer) { | |
| 1072 | return c.emitView(now, func(w io.Writer) { | |
| 1035 | 1073 | tb := c.table(w, "TOPIC") |
| 1036 | 1074 | for _, t := range now { |
| 1037 | 1075 | tb.row(cRef(t)) |
| 1038 | 1076 | } |
| 1039 | 1077 | tb.flush() |
| 1078 | }, func() screen { | |
| 1079 | return topicsScreen(repo, now) | |
| 1040 | 1080 | }) |
| 1041 | 1081 | } |
| 1042 | 1082 | |
| @@ -1079,12 +1119,22 @@ func runRepoSearch(c *Ctx, args []string) int { | ||
| 1079 | 1119 | } |
| 1080 | 1120 | ds = append(ds, out{r.Path(), r.Visibility, desc, topics}) |
| 1081 | 1121 | } |
| 1082 | return c.emit(ds, func(w io.Writer) { | |
| 1122 | return c.emitView(ds, func(w io.Writer) { | |
| 1083 | 1123 | tb := c.table(w, "PATH", "VISIBILITY", "DESCRIPTION") |
| 1084 | 1124 | for _, d := range ds { |
| 1085 | 1125 | tb.row(cLink(d.Path, c.siteURL(d.Path)), cState(d.Visibility), cFlex(d.Description)) |
| 1086 | 1126 | } |
| 1087 | 1127 | tb.flush() |
| 1128 | }, func() screen { | |
| 1129 | rows := make([]row, len(ds)) | |
| 1130 | for i, d := range ds { | |
| 1131 | rows[i] = rowOf(cLink(d.Path, c.siteURL(d.Path)), cState(d.Visibility), cFlex(d.Description), cMeta(strings.Join(d.Topics, ", "))) | |
| 1132 | } | |
| 1133 | s := listScreen(fmt.Sprintf("Repositories matching %q", args[0]), rows) | |
| 1134 | if len(ds) > 0 { | |
| 1135 | s.actions = []action{{"Read", []string{"repo", "show", ds[0].Path}}} | |
| 1136 | } | |
| 1137 | return s | |
| 1088 | 1138 | }) |
| 1089 | 1139 | } |
| 1090 | 1140 | |
| @@ -1249,12 +1299,26 @@ func runRepoBookmarks(c *Ctx, args []string) int { | ||
| 1249 | 1299 | Bookmarks: c.Store.BookmarkCount(r.ID), |
| 1250 | 1300 | }) |
| 1251 | 1301 | } |
| 1252 | return c.emit(out, func(w io.Writer) { | |
| 1302 | return c.emitView(out, func(w io.Writer) { | |
| 1253 | 1303 | tb := c.table(w, "PATH", "COUNT", "DESCRIPTION") |
| 1254 | 1304 | for _, b := range out { |
| 1255 | 1305 | tb.row(cRef(b.Path), cNum(int64(b.Bookmarks)), cFlex(b.Description)) |
| 1256 | 1306 | } |
| 1257 | 1307 | tb.flush() |
| 1308 | }, func() screen { | |
| 1309 | rows := make([]row, len(out)) | |
| 1310 | for i, b := range out { | |
| 1311 | n := fmt.Sprintf("%d bookmarks", b.Bookmarks) | |
| 1312 | if b.Bookmarks == 1 { | |
| 1313 | n = "1 bookmark" | |
| 1314 | } | |
| 1315 | rows[i] = rowOf(cLink(b.Path, c.siteURL(b.Path)), cState(b.Visibility), cFlex(b.Description), cMeta(n)) | |
| 1316 | } | |
| 1317 | s := listScreen("Bookmarks", rows) | |
| 1318 | if len(out) > 0 { | |
| 1319 | s.actions = []action{{"Read", []string{"repo", "show", out[0].Path}}} | |
| 1320 | } | |
| 1321 | return s | |
| 1258 | 1322 | }) |
| 1259 | 1323 | } |
| 1260 | 1324 | |
internal/control/runnerrepo.go +17 −1
| @@ -114,7 +114,7 @@ func runRepoRunnerList(c *Ctx, args []string) int { | ||
| 114 | 114 | if runners == nil { |
| 115 | 115 | runners = []store.RepoRunner{} |
| 116 | 116 | } |
| 117 | return c.emit(runners, func(w io.Writer) { | |
| 117 | return c.emitView(runners, func(w io.Writer) { | |
| 118 | 118 | tb := c.table(w, "FINGERPRINT", "ALGO", "USER", "SEEN", "HELD") |
| 119 | 119 | for _, r := range runners { |
| 120 | 120 | seen := r.LastSeen |
| @@ -128,6 +128,22 @@ func runRepoRunnerList(c *Ctx, args []string) int { | ||
| 128 | 128 | tb.row(cRef(r.Fingerprint), cText(r.Algo), cText(r.Username), cAge(seen), cText(held)) |
| 129 | 129 | } |
| 130 | 130 | tb.flush() |
| 131 | }, func() screen { | |
| 132 | rows := make([]row, len(runners)) | |
| 133 | for i, r := range runners { | |
| 134 | lead, held := cGlyph(""), "" | |
| 135 | if r.BuildNumber != 0 { | |
| 136 | lead, held = cGlyph("running"), fmt.Sprintf("building %s #%d %s", r.BuildRepo, r.BuildNumber, r.BuildJob) | |
| 137 | } | |
| 138 | seen := "never seen" | |
| 139 | if r.LastSeen != "" { | |
| 140 | seen = "seen " + relAge(r.LastSeen, termNow()) | |
| 141 | } | |
| 142 | rows[i] = rowOf(cRef(r.Fingerprint), lead, cFlex(r.Username), cMeta(r.Algo, seen, held)) | |
| 143 | } | |
| 144 | return listScreen("Runners", rows, | |
| 145 | action{"Runners", []string{"repo", "runner", "remove", repo.Path(), "<fingerprint>"}}, | |
| 146 | ) | |
| 131 | 147 | }) |
| 132 | 148 | } |
| 133 | 149 | |
internal/control/screen.go +6
| @@ -57,6 +57,12 @@ type action struct { | ||
| 57 | 57 | argv []string |
| 58 | 58 | } |
| 59 | 59 | |
| 60 | // listScreen is a list command's terminal screen: one section of rows | |
| 61 | // and the commands that apply. | |
| 62 | func listScreen(title string, rows []row, actions ...action) screen { | |
| 63 | return screen{sections: []section{{title: title, n: len(rows), rows: rows}}, actions: actions} | |
| 64 | } | |
| 65 | ||
| 60 | 66 | // render draws s: the parts in order, one blank line between parts and |
| 61 | 67 | // between sections. |
| 62 | 68 | func (c *Ctx) render(w io.Writer, s screen) { |
internal/control/stage3repo_test.go added +149
| @@ -0,0 +1,149 @@ | ||
| 1 | package control | |
| 2 | ||
| 3 | import ( | |
| 4 | "bytes" | |
| 5 | "crypto/ed25519" | |
| 6 | "strings" | |
| 7 | "testing" | |
| 8 | ||
| 9 | "gitbay.org/gitbay/internal/protocol" | |
| 10 | "gitbay.org/gitbay/internal/store" | |
| 11 | "golang.org/x/crypto/ssh" | |
| 12 | ) | |
| 13 | ||
| 14 | // dispatchAs runs argv as u with stdin, piped, and returns stdout; a | |
| 15 | // non-zero exit fails t. | |
| 16 | func dispatchAs(t *testing.T, st *store.Store, u store.User, stdin string, argv ...string) string { | |
| 17 | t.Helper() | |
| 18 | out, errOut := &bytes.Buffer{}, &bytes.Buffer{} | |
| 19 | c := &Ctx{User: u, Scope: "full", Store: st, Stdout: out, Stderr: errOut, Stdin: strings.NewReader(stdin)} | |
| 20 | if code := Dispatch(c, argv); code != protocol.ExitOK { | |
| 21 | t.Fatalf("%v: exit %d: %s", argv, code, errOut) | |
| 22 | } | |
| 23 | return out.String() | |
| 24 | } | |
| 25 | ||
| 26 | // atTerminal runs argv as u at a 100-column terminal without colour | |
| 27 | // and returns stdout; a non-zero exit fails t. | |
| 28 | func atTerminal(t *testing.T, st *store.Store, u store.User, argv ...string) string { | |
| 29 | t.Helper() | |
| 30 | out, errOut := &bytes.Buffer{}, &bytes.Buffer{} | |
| 31 | c := &Ctx{User: u, Scope: "full", Store: st, Stdout: out, Stderr: errOut, Stdin: strings.NewReader(""), Term: Term{Cols: 100}} | |
| 32 | if code := Dispatch(c, argv); code != protocol.ExitOK { | |
| 33 | t.Fatalf("%v: exit %d: %s", argv, code, errOut) | |
| 34 | } | |
| 35 | return out.String() | |
| 36 | } | |
| 37 | ||
| 38 | // checkLegend fails t for any suggested command in a rendered screen | |
| 39 | // (a line starting "gitbay ", or after "+n more ") that the registry | |
| 40 | // would not dispatch with the flags it names. | |
| 41 | func checkLegend(t *testing.T, out string) { | |
| 42 | t.Helper() | |
| 43 | var s screen | |
| 44 | for _, line := range strings.Split(out, "\n") { | |
| 45 | if i := strings.Index(line, "more gitbay "); i >= 0 { | |
| 46 | line = line[i+len("more "):] | |
| 47 | } | |
| 48 | for _, cmd := range strings.Split(line, " ") { | |
| 49 | cmd = strings.TrimSpace(cmd) | |
| 50 | if !strings.HasPrefix(cmd, "gitbay ") { | |
| 51 | continue | |
| 52 | } | |
| 53 | argv, err := protocol.Tokenize(strings.TrimPrefix(cmd, "gitbay ")) | |
| 54 | if err != nil { | |
| 55 | t.Errorf("%q: %v", cmd, err) | |
| 56 | continue | |
| 57 | } | |
| 58 | s.actions = append(s.actions, action{"", argv}) | |
| 59 | } | |
| 60 | } | |
| 61 | if len(s.actions) == 0 { | |
| 62 | t.Errorf("no suggested commands in:\n%s", out) | |
| 63 | } | |
| 64 | checkActions(t, s) | |
| 65 | } | |
| 66 | ||
| 67 | // newPubKey is an ed25519 public key in authorized_keys form, the same | |
| 68 | // on every run so its fingerprint can be pinned. | |
| 69 | func newPubKey(t *testing.T) string { | |
| 70 | t.Helper() | |
| 71 | pub := ed25519.NewKeyFromSeed(bytes.Repeat([]byte{7}, ed25519.SeedSize)).Public().(ed25519.PublicKey) | |
| 72 | sp, err := ssh.NewPublicKey(pub) | |
| 73 | if err != nil { | |
| 74 | t.Fatal(err) | |
| 75 | } | |
| 76 | return string(ssh.MarshalAuthorizedKey(sp)) | |
| 77 | } | |
| 78 | ||
| 79 | // repoListsFixture is a repository with something in every list task 1 | |
| 80 | // migrates. | |
| 81 | func repoListsFixture(t *testing.T) (*store.Store, store.Repo, store.User) { | |
| 82 | t.Helper() | |
| 83 | st, repo, uid := newQueueTestRepo(t) | |
| 84 | owner := store.User{ID: uid, Username: "alice", IsAdmin: true} | |
| 85 | p := repo.Path() | |
| 86 | dispatchAs(t, st, owner, "", "repo", "topics", "add", p, "cli", "forge") | |
| 87 | dispatchAs(t, st, owner, "s3cret\n", "repo", "secret", "set", p, "DEPLOY_TOKEN") | |
| 88 | dispatchAs(t, st, owner, testRunnerPub, "repo", "deploy-key", "add", p) | |
| 89 | dispatchAs(t, st, owner, "", "repo", "domain", "add", p, "docs.example.test") | |
| 90 | dispatchAs(t, st, owner, "", "repo", "bookmark", p) | |
| 91 | // mirror add resolves the host, so the fixture goes to the store. | |
| 92 | if _, err := st.AddMirror(repo.ID, "push", "https://mirror.example.test/app.git", "", "tok"); err != nil { | |
| 93 | t.Fatal(err) | |
| 94 | } | |
| 95 | dispatchAs(t, st, owner, newPubKey(t), "repo", "runner", "add", p) | |
| 96 | return st, repo, owner | |
| 97 | } | |
| 98 | ||
| 99 | func TestRepoListsPlainPinned(t *testing.T) { | |
| 100 | st, repo, owner := repoListsFixture(t) | |
| 101 | p := repo.Path() | |
| 102 | for name, argv := range map[string][]string{ | |
| 103 | "repo-topics": {"repo", "topics", p}, | |
| 104 | "repo-secret-list": {"repo", "secret", "list", p}, | |
| 105 | "repo-deploy-key-list": {"repo", "deploy-key", "list", p}, | |
| 106 | "repo-domain-list": {"repo", "domain", "list", p}, | |
| 107 | "repo-access-list": {"repo", "access", "list", p}, | |
| 108 | "repo-bookmarks": {"repo", "bookmarks"}, | |
| 109 | "repo-search": {"repo", "search", "app"}, | |
| 110 | "repo-list": {"repo", "list"}, | |
| 111 | "repo-mirror-list": {"repo", "mirror", "list", p}, | |
| 112 | "repo-runner-list": {"repo", "runner", "list", p}, | |
| 113 | } { | |
| 114 | pinPlain(t, name, dispatchAs(t, st, owner, "", argv...)) | |
| 115 | } | |
| 116 | pinPlain(t, "repo-topics-add", dispatchAs(t, st, owner, "", "repo", "topics", "add", p, "git")) | |
| 117 | } | |
| 118 | ||
| 119 | func TestRepoListScreens(t *testing.T) { | |
| 120 | st, repo, owner := repoListsFixture(t) | |
| 121 | p := repo.Path() | |
| 122 | for _, tc := range []struct { | |
| 123 | argv []string | |
| 124 | want []string | |
| 125 | }{ | |
| 126 | {[]string{"repo", "topics", p}, []string{"Topics (2)\ncli\nforge\n"}}, | |
| 127 | {[]string{"repo", "topics", "add", p, "git"}, []string{"Topics (3)\n"}}, | |
| 128 | {[]string{"repo", "secret", "list", p}, []string{"Build secrets (1)\nDEPLOY_TOKEN\n"}}, | |
| 129 | {[]string{"repo", "deploy-key", "list", p}, []string{"Deploy keys (1)\n", " ro runner@test ssh-ed25519 · used never"}}, | |
| 130 | {[]string{"repo", "domain", "list", p}, []string{"Pages domains (1)\n", "◐ docs.example.test pending"}}, | |
| 131 | {[]string{"repo", "access", "list", p}, []string{"Access (1)\nalice admin via owner\n"}}, | |
| 132 | {[]string{"repo", "mirror", "list", p}, []string{"Mirrors (1)\n1 ◐ https://mirror.example.test/app.git push · never synced"}}, | |
| 133 | {[]string{"repo", "runner", "list", p}, []string{"Runners (1)\n", " alice ssh-ed25519 · never seen"}}, | |
| 134 | {[]string{"repo", "bookmarks"}, []string{"Bookmarks (1)\nalice/app public 1 bookmark\n"}}, | |
| 135 | {[]string{"repo", "search", "app"}, []string{"Repositories matching \"app\" (1)\nalice/app public cli, forge"}}, | |
| 136 | {[]string{"repo", "list"}, []string{"Repositories (1)\nalice/app public\n"}}, | |
| 137 | } { | |
| 138 | out := atTerminal(t, st, owner, tc.argv...) | |
| 139 | for _, w := range tc.want { | |
| 140 | if !strings.Contains(out, w) { | |
| 141 | t.Errorf("%v: missing %q in:\n%s", tc.argv, w, out) | |
| 142 | } | |
| 143 | } | |
| 144 | if strings.Contains(out, "s3cret") || strings.Contains(out, "tok\n") { | |
| 145 | t.Errorf("%v: secret on screen:\n%s", tc.argv, out) | |
| 146 | } | |
| 147 | checkLegend(t, out) | |
| 148 | } | |
| 149 | } | |
internal/control/testdata/plain/repo-access-list.txt added +1
| @@ -0,0 +1 @@ | ||
| 1 | alice admin via owner | |
internal/control/testdata/plain/repo-bookmarks.txt added +1
| @@ -0,0 +1 @@ | ||
| 1 | alice/app 1 | |
internal/control/testdata/plain/repo-deploy-key-list.txt added +1
| @@ -0,0 +1 @@ | ||
| 1 | SHA256:re738AN1/nwapcQTqXzvkuuJNj+U6StCED66ZUJNCdE ssh-ed25519 ro runner@test never never | |
internal/control/testdata/plain/repo-domain-list.txt added +1
| @@ -0,0 +1 @@ | ||
| 1 | docs.example.test pending | |
internal/control/testdata/plain/repo-list.txt added +1
| @@ -0,0 +1 @@ | ||
| 1 | alice/app public | |
internal/control/testdata/plain/repo-mirror-list.txt added +1
| @@ -0,0 +1 @@ | ||
| 1 | 1 push https://mirror.example.test/app.git last - pending | |
internal/control/testdata/plain/repo-runner-list.txt added +1
| @@ -0,0 +1 @@ | ||
| 1 | SHA256:z/fSv0Z0RZS+Lccbc6ZoOWwt/fbj1VFJGSBKQbo4icE ssh-ed25519 alice never idle | |
internal/control/testdata/plain/repo-search.txt added +1
| @@ -0,0 +1 @@ | ||
| 1 | alice/app public | |
internal/control/testdata/plain/repo-secret-list.txt added +1
| @@ -0,0 +1 @@ | ||
| 1 | DEPLOY_TOKEN | |
internal/control/testdata/plain/repo-topics-add.txt added +3
| @@ -0,0 +1,3 @@ | ||
| 1 | cli | |
| 2 | forge | |
| 3 | git | |
internal/control/testdata/plain/repo-topics.txt added +2
| @@ -0,0 +1,2 @@ | ||
| 1 | cli | |
| 2 | forge | |