1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package usecase_test
4
5import (
6 "fmt"
7 "strings"
8 "testing"
9
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12
13 "bnema.dev/ermite/internal/domain"
14 "bnema.dev/ermite/internal/ports"
15 "bnema.dev/ermite/internal/usecase"
16)
17
18func TestWindowLimits(t *testing.T) {
19 def := domain.DefaultDiffLimits()
20 win := def
21 win.MaxFiles, win.From, win.To, win.SkipGenerated = 0, 2, 5, true
22 open := win
23 open.From, open.To = 7, 0
24
25 tests := []struct {
26 name string
27 w *ports.FileWindow
28 want domain.DiffLimits
29 bad bool
30 }{
31 {name: "nil is the default", want: def},
32 {name: "a window", w: &ports.FileWindow{From: 2, To: 5}, want: win},
33 {name: "an open window", w: &ports.FileWindow{From: 7}, want: open},
34 {name: "full", w: &ports.FileWindow{From: 3, To: 4, Full: true}, want: domain.OneFileDiffLimits(3)},
35 {name: "negative from", w: &ports.FileWindow{From: -1}, bad: true},
36 {name: "negative to", w: &ports.FileWindow{To: -1}, bad: true},
37 {name: "to before from", w: &ports.FileWindow{From: 5, To: 3}, bad: true},
38 {name: "full without to", w: &ports.FileWindow{From: 3, Full: true}, bad: true},
39 {name: "full of two files", w: &ports.FileWindow{From: 3, To: 5, Full: true}, bad: true},
40 {name: "full of no file", w: &ports.FileWindow{From: 3, To: 3, Full: true}, bad: true},
41 }
42 for _, tt := range tests {
43 t.Run(tt.name, func(t *testing.T) {
44 got, err := usecase.WindowLimits(tt.w)
45 if tt.bad {
46 require.ErrorIs(t, err, domain.ErrInvalid)
47 return
48 }
49 require.NoError(t, err)
50 assert.Equal(t, tt.want, got)
51 })
52 }
53}
54
55func TestGetMergeRequestFullAndGenerated(t *testing.T) {
56 r := newMRRig(t)
57 r.seed()
58 r.sh("checkout", "-q", "-B", "feat", "main")
59 r.commit(author, "a.txt", "one\n", "a")
60 r.commit(author, "go.sum", "x y z\n", "sum")
61 r.push("feat")
62 n := r.open("feat").Number
63
64 t.Run("full with a wider window is invalid", func(t *testing.T) {
65 _, err := r.mrs.Get(r.ctx, reader, r.repo, n, ports.GetMergeRequestOptions{Files: &ports.FileWindow{From: 0, To: 2, Full: true}})
66 require.ErrorIs(t, err, domain.ErrInvalid)
67 })
68
69 t.Run("a windowed read collapses a generated file, a nil read does not", func(t *testing.T) {
70 d, err := r.mrs.Get(r.ctx, reader, r.repo, n, ports.GetMergeRequestOptions{Files: &ports.FileWindow{From: 0, To: 10}})
71 require.NoError(t, err)
72 byPath := map[string]domain.FileDiff{}
73 for _, f := range d.Diff.Files {
74 byPath[f.NewPath] = f
75 }
76 assert.Equal(t, domain.OmitGenerated, byPath["go.sum"].Omitted)
77 assert.Empty(t, byPath["go.sum"].Hunks)
78 assert.Equal(t, domain.OmitNone, byPath["a.txt"].Omitted)
79 assert.NotEmpty(t, byPath["a.txt"].Hunks)
80
81 d, err = r.mrs.Get(r.ctx, reader, r.repo, n, ports.GetMergeRequestOptions{})
82 require.NoError(t, err)
83 for _, f := range d.Diff.Files {
84 assert.Equal(t, domain.OmitNone, f.Omitted, f.NewPath)
85 assert.NotEmpty(t, f.Hunks, f.NewPath)
86 }
87 })
88}
89
90func bigContent(n int) string {
91 var b strings.Builder
92 for i := range n {
93 fmt.Fprintf(&b, "line %d\n", i)
94 }
95 return b.String()
96}
97
98func TestCheckLineBeyondFileLimit(t *testing.T) {
99 r := newMRRig(t)
100 r.seed()
101 r.sh("checkout", "-q", "-B", "feat", "main")
102 r.commit(author, "big.txt", bigContent(3000), "big")
103 r.commit(author, "go.sum", "h1:x\nh1:y\n", "sum")
104 r.push("feat")
105 n := r.open("feat").Number
106
107 add := func(path string, line int) error {
108 _, err := r.mrs.AddComment(r.ctx, mrW, r.repo, n, ports.AddCommentParams{Path: path, Line: line, Side: domain.SideNew, Text: "c"})
109 return err
110 }
111 require.NoError(t, add("big.txt", 1))
112 require.NoError(t, add("big.txt", 2500))
113 require.NoError(t, add("big.txt", 3000))
114 require.ErrorIs(t, add("big.txt", 3001), domain.ErrInvalid)
115 // A generated file is collapsed in the web windows, but loadable: its
116 // lines can be commented on.
117 require.NoError(t, add("go.sum", 2))
118}
119
120func TestBrowseGetCommitFullWindow(t *testing.T) {
121 r := newMRRig(t)
122 r.seed()
123 r.commit(author, "a-big.txt", bigContent(3000), "big")
124 id := r.head()
125 r.push("main")
126 b := usecase.NewBrowse(r.store, r.git)
127 rev := domain.RevisionOf(id)
128
129 d, err := b.GetCommit(r.ctx, root, r.repo, rev, ports.GetCommitOptions{})
130 require.NoError(t, err)
131 require.Len(t, d.Diff.Files, 1)
132 assert.Equal(t, domain.OmitLarge, d.Diff.Files[0].Omitted)
133 assert.Empty(t, d.Diff.Files[0].Hunks)
134
135 d, err = b.GetCommit(r.ctx, root, r.repo, rev, ports.GetCommitOptions{Files: &ports.FileWindow{From: 0, To: 1, Full: true}})
136 require.NoError(t, err)
137 require.Len(t, d.Diff.Files, 1)
138 f := d.Diff.Files[0]
139 assert.Equal(t, domain.OmitNone, f.Omitted)
140 require.NotEmpty(t, f.Hunks)
141 assert.Equal(t, 3000, f.Additions)
142
143 _, err = b.GetCommit(r.ctx, root, r.repo, rev, ports.GetCommitOptions{Files: &ports.FileWindow{From: 0, To: 2, Full: true}})
144 require.ErrorIs(t, err, domain.ErrInvalid)
145}
146
147func TestBrowseGetCommitWindowCollapsesGenerated(t *testing.T) {
148 r := newMRRig(t)
149 r.seed()
150 r.commit(author, "go.sum", "h1:x\n", "sum")
151 id := r.head()
152 r.push("main")
153 b := usecase.NewBrowse(r.store, r.git)
154 rev := domain.RevisionOf(id)
155
156 d, err := b.GetCommit(r.ctx, root, r.repo, rev, ports.GetCommitOptions{})
157 require.NoError(t, err)
158 require.Len(t, d.Diff.Files, 1)
159 assert.Equal(t, domain.OmitNone, d.Diff.Files[0].Omitted, "API reads keep generated files")
160
161 d, err = b.GetCommit(r.ctx, root, r.repo, rev, ports.GetCommitOptions{Files: &ports.FileWindow{From: 0, To: 25}})
162 require.NoError(t, err)
163 require.Len(t, d.Diff.Files, 1)
164 assert.Equal(t, domain.OmitGenerated, d.Diff.Files[0].Omitted, "web windows collapse them")
165 assert.Empty(t, d.Diff.Files[0].Hunks)
166}