e2e/snippet_test.go

e2a32d5f8d59e4213571c602bd9009b6c8fa86ed
gitbay/e2e/snippet_test.go history · blame · raw

143 lines · 6012 bytes

  1package e2e
  2
  3import (
  4	"encoding/json"
  5	"regexp"
  6	"strings"
  7	"testing"
  8)
  9
 10// Snippets over SSH: create from stdin, read back, list by visibility,
 11// edit files and metadata, and the not-found rule for private ones.
 12func TestSnippets(t *testing.T) {
 13	t.Parallel()
 14	inst := startInstanceWith(t, "[limits]\nmax_snippets_per_user = 3\n")
 15	aliceKey := inst.newKey(t, "alice")
 16	bobKey := inst.newKey(t, "bob")
 17	inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub", "--email", "alice@example.test", "--verified")
 18	inst.admin(t, "admin", "user", "create", "bob", "--key", bobKey+".pub", "--email", "bob@example.test", "--verified")
 19	must := func(key, stdin string, args ...string) string {
 20		t.Helper()
 21		out, errOut, code := inst.ssh(t, key, stdin, args...)
 22		if code != 0 {
 23			t.Fatalf("%v: exit %d %s", args, code, errOut)
 24		}
 25		return out
 26	}
 27	fails := func(key, stdin string, want int, args ...string) string {
 28		t.Helper()
 29		_, errOut, code := inst.ssh(t, key, stdin, args...)
 30		if code != want {
 31			t.Fatalf("%v: exit %d, want %d: %s", args, code, want, errOut)
 32		}
 33		return errOut
 34	}
 35	idOf := func(out string) string {
 36		t.Helper()
 37		var env struct {
 38			Data struct {
 39				ID  string `json:"id"`
 40				URL string `json:"url"`
 41			} `json:"data"`
 42		}
 43		if err := json.Unmarshal([]byte(out), &env); err != nil || !regexp.MustCompile(`^[0-9a-f]{12}$`).MatchString(env.Data.ID) {
 44			t.Fatalf("create output: %s", out)
 45		}
 46		if !strings.HasSuffix(env.Data.URL, "/alice/-/snippets/"+env.Data.ID) {
 47			t.Fatalf("url: %s", env.Data.URL)
 48		}
 49		return env.Data.ID
 50	}
 51
 52	// Create with the default visibility, read back byte for byte.
 53	body := "line one\nline two\n"
 54	unlisted := idOf(must(aliceKey, body, "snippet", "create", "build.log", "--description", "'a log'", "--json"))
 55	if got := must(aliceKey, "", "snippet", "file", "get", unlisted, "build.log"); got != body {
 56		t.Fatalf("file get: %q", got)
 57	}
 58	out := must(aliceKey, "", "snippet", "show", unlisted, "--json")
 59	if !strings.Contains(out, `"visibility":"unlisted"`) || !strings.Contains(out, `"content":"line one\nline two\n"`) {
 60		t.Fatalf("show: %s", out)
 61	}
 62	public := idOf(must(aliceKey, "pub\n", "snippet", "create", "a.txt", "--visibility", "public", "--json"))
 63	private := idOf(must(aliceKey, "sec\n", "snippet", "create", "b.txt", "--visibility", "private", "--json"))
 64
 65	// The per-account cap: three snippets exist, a fourth is refused.
 66	if msg := fails(aliceKey, "x\n", 2, "snippet", "create", "c.txt"); !strings.Contains(msg, "snippet limit reached") {
 67		t.Fatalf("cap refusal: %s", msg)
 68	}
 69
 70	// Refusals on create: empty, not text, over the limit, bad name.
 71	fails(aliceKey, "", 2, "snippet", "create", "x.txt")
 72	fails(aliceKey, "\xff\xfe\n", 2, "snippet", "create", "x.bin")
 73	fails(aliceKey, strings.Repeat("x", 1<<20+1), 2, "snippet", "create", "big.txt")
 74	fails(aliceKey, "x\n", 2, "snippet", "create", "../x")
 75	fails(aliceKey, "x\n", 2, "snippet", "create", "x.txt", "--visibility", "secret")
 76
 77	// Visibility from the other side. Private is not-found, never denied.
 78	fails(bobKey, "", 3, "snippet", "show", private)
 79	fails(bobKey, "", 3, "snippet", "file", "get", private, "b.txt")
 80	must(bobKey, "", "snippet", "show", unlisted)
 81	out = must(bobKey, "", "snippet", "list", "alice", "--json")
 82	if !strings.Contains(out, public) || strings.Contains(out, unlisted) || strings.Contains(out, private) {
 83		t.Fatalf("bob's view of alice's list: %s", out)
 84	}
 85	out = must(aliceKey, "", "snippet", "list", "--json")
 86	for _, id := range []string{public, unlisted, private} {
 87		if !strings.Contains(out, id) {
 88			t.Fatalf("alice's own list lacks %s: %s", id, out)
 89		}
 90	}
 91	fails(bobKey, "", 3, "snippet", "list", "nobody")
 92
 93	// Paging: two pages of one, the second reached by cursor.
 94	out = must(aliceKey, "", "snippet", "list", "--limit", "1", "--json")
 95	var page struct {
 96		Data struct {
 97			Items []struct {
 98				ID string `json:"id"`
 99			} `json:"items"`
100			Next string `json:"next"`
101		} `json:"data"`
102	}
103	json.Unmarshal([]byte(out), &page)
104	if len(page.Data.Items) != 1 || page.Data.Items[0].ID != private || page.Data.Next == "" {
105		t.Fatalf("first page: %s", out)
106	}
107	out = must(aliceKey, "", "snippet", "list", "--limit", "1", "--cursor", page.Data.Next, "--json")
108	if !strings.Contains(out, public) {
109		t.Fatalf("second page: %s", out)
110	}
111
112	// Files: set adds, set replaces, remove drops, the last one stays.
113	must(aliceKey, "notes\n", "snippet", "file", "set", unlisted, "notes.txt")
114	must(aliceKey, "changed\n", "snippet", "file", "set", unlisted, "build.log")
115	if got := must(aliceKey, "", "snippet", "file", "get", unlisted, "build.log"); got != "changed\n" {
116		t.Fatalf("after replace: %q", got)
117	}
118	must(aliceKey, "", "snippet", "file", "remove", unlisted, "notes.txt")
119	fails(aliceKey, "", 3, "snippet", "file", "remove", unlisted, "notes.txt")
120	if msg := fails(aliceKey, "", 2, "snippet", "file", "remove", unlisted, "build.log"); !strings.Contains(msg, "at least one file") {
121		t.Fatalf("last file removal: %s", msg)
122	}
123
124	// Only the owner writes: denied on a readable one, not-found on a private one.
125	fails(bobKey, "x\n", 4, "snippet", "file", "set", unlisted, "x.txt")
126	fails(bobKey, "", 4, "snippet", "edit", unlisted, "--description", "mine")
127	fails(bobKey, "", 4, "snippet", "delete", unlisted)
128	fails(bobKey, "", 3, "snippet", "delete", private)
129
130	// Edit moves visibility and the listing follows.
131	fails(aliceKey, "", 2, "snippet", "edit", unlisted)
132	must(aliceKey, "", "snippet", "edit", unlisted, "--visibility", "public", "--description", "shared")
133	out = must(bobKey, "", "snippet", "list", "alice", "--json")
134	if !strings.Contains(out, unlisted) || !strings.Contains(out, `"description":"shared"`) {
135		t.Fatalf("list after edit: %s", out)
136	}
137
138	// Delete, then gone; deleting the user takes the rest.
139	must(aliceKey, "", "snippet", "delete", unlisted)
140	fails(aliceKey, "", 3, "snippet", "show", unlisted)
141	inst.admin(t, "admin", "user", "delete", "alice", "--yes")
142	fails(bobKey, "", 3, "snippet", "show", public)
143}