history_test.go
4980 bytes
1package pages
2
3import (
4 "bytes"
5 "os"
6 "os/exec"
7 "path/filepath"
8 "strings"
9 "testing"
10
11 "github.com/charmbracelet/soft-serve/git"
12)
13
14func TestHistoryPaginationUsesThirtyItemsAndBounds(t *testing.T) {
15 repo := newTestRepository(t, map[string]string{"log.txt": "0\n"})
16 for i := 1; i <= commitsPerPage; i++ {
17 appendCommit(t, repo.Path, "log.txt", "next "+strings.Repeat("x", i))
18 }
19 ref, err := ResolveRef(repo, "")
20 if err != nil {
21 t.Fatal(err)
22 }
23 commits, err := repo.CommitsByPage(ref, 1, commitsPerPage+1)
24 if err != nil {
25 t.Fatal(err)
26 }
27 if len(commits) != commitsPerPage+1 {
28 t.Fatalf("extra commit request returned %d commits, want %d", len(commits), commitsPerPage+1)
29 }
30 if got := len(commitEntries("project", commits[:commitsPerPage])); got != commitsPerPage {
31 t.Fatalf("display entries = %d, want %d", got, commitsPerPage)
32 }
33 if _, err := historyPageNumber("0"); err == nil {
34 t.Fatal("zero page was accepted")
35 }
36 if got, err := historyPageNumber("1001"); err != nil || got != maxCommitPage {
37 t.Fatalf("out-of-range page = %d, %v; want %d, nil", got, err, maxCommitPage)
38 }
39}
40
41func TestRefsSupportSlashBranchesAndAnnotatedTags(t *testing.T) {
42 repo := newTestRepository(t, map[string]string{"README.md": "hello"})
43 runGit(t, repo.Path, "branch", "feature/with-slash")
44 runGit(t, repo.Path, "tag", "-a", "release/v1", "-m", "release")
45 references, err := repo.References()
46 if err != nil {
47 t.Fatal(err)
48 }
49 var branch, tag *git.Reference
50 for _, ref := range references {
51 switch ref.Name().String() {
52 case "refs/heads/feature/with-slash":
53 branch = ref
54 case "refs/tags/release/v1":
55 tag = ref
56 }
57 }
58 if branch == nil || tag == nil {
59 t.Fatalf("missing branch or tag in %#v", references)
60 }
61 branchEntry := refEntry(repo, "team/project", branch)
62 tagEntry := refEntry(repo, "team/project", tag)
63 for _, entry := range []refPageEntry{branchEntry, tagEntry} {
64 if entry.CommitURL == "" || !strings.Contains(entry.CommitURL, "/@/commit?hash=") {
65 t.Fatalf("commit link missing: %#v", entry)
66 }
67 }
68 if !strings.Contains(branchEntry.TreeURL, "ref=refs%2Fheads%2Ffeature%2Fwith-slash") {
69 t.Fatalf("branch tree link does not encode full ref: %q", branchEntry.TreeURL)
70 }
71 if !strings.Contains(tagEntry.TreeURL, "ref=refs%2Ftags%2Frelease%2Fv1") {
72 t.Fatalf("tag tree link does not encode full ref: %q", tagEntry.TreeURL)
73 }
74}
75
76func TestCommitLookupValidationAndRepositoryBoundary(t *testing.T) {
77 first := newTestRepository(t, map[string]string{"first.txt": "first"})
78 second := newTestRepository(t, map[string]string{"second.txt": "second"})
79 if _, err := LookupCommit(first, "not-a-hash"); err == nil {
80 t.Fatal("invalid hash was accepted")
81 }
82 treeID := strings.TrimSpace(runGitOutput(t, first.Path, "rev-parse", "HEAD^{tree}"))
83 if _, err := LookupCommit(first, treeID); err == nil {
84 t.Fatal("non-commit object was accepted")
85 }
86 secondID := strings.TrimSpace(runGitOutput(t, second.Path, "rev-parse", "HEAD"))
87 if _, err := LookupCommit(first, secondID); err == nil {
88 t.Fatal("commit from another repository was disclosed")
89 }
90}
91
92func TestCommitMessageEscapingAndBoundedDiff(t *testing.T) {
93 repo := newTestRepository(t, map[string]string{"changed.txt": "start\n"})
94 appendCommit(t, repo.Path, "changed.txt", strings.Repeat("line\n", 120))
95 hash := strings.TrimSpace(runGitOutput(t, repo.Path, "rev-parse", "HEAD"))
96 commit, err := LookupCommit(repo, hash)
97 if err != nil {
98 t.Fatal(err)
99 }
100 diff, err := repo.DiffWithLimits(commit, git.DiffLimits{MaxFiles: maxDiffFiles, MaxFileLines: maxDiffFileLines, MaxLineChars: maxDiffLineChars})
101 if err != nil {
102 t.Fatal(err)
103 }
104 patch, truncated, err := boundedDiffPatch(diff)
105 if err != nil {
106 t.Fatal(err)
107 }
108 if !truncated || strings.Count(patch, "\n") > maxDiffFileLines+5 {
109 t.Fatalf("diff was not bounded: truncated=%t lines=%d", truncated, strings.Count(patch, "\n"))
110 }
111 var rendered bytes.Buffer
112 if err := pageTemplates.ExecuteTemplate(&rendered, "commit", commitPage{Message: "<script>alert(1)</script>"}); err != nil {
113 t.Fatal(err)
114 }
115 if strings.Contains(rendered.String(), "<script>alert(1)</script>") || !strings.Contains(rendered.String(), "<script>") {
116 t.Fatalf("commit message was not escaped: %s", rendered.String())
117 }
118}
119
120func appendCommit(t *testing.T, repoPath, filename, content string) {
121 t.Helper()
122 appendCommitMessage(t, repoPath, filename, content, "change")
123}
124
125func appendCommitMessage(t *testing.T, repoPath, filename, content, message string) {
126 t.Helper()
127 if err := os.WriteFile(filepath.Join(repoPath, filename), []byte(content), 0o600); err != nil {
128 t.Fatal(err)
129 }
130 runGit(t, repoPath, "add", filename)
131 runGit(t, repoPath, "commit", "-qm", message)
132}
133
134func runGitOutput(t *testing.T, dir string, args ...string) string {
135 t.Helper()
136 cmd := exec.Command("git", args...)
137 cmd.Dir = dir
138 output, err := cmd.CombinedOutput()
139 if err != nil {
140 t.Fatalf("git %s: %v: %s", strings.Join(args, " "), err, output)
141 }
142 return string(output)
143}