changelog generator & diff tool
stormlightlabs.github.io/git-storm/
changelog
changeset
markdown
golang
git
1package gitlog
2
3import (
4 "testing"
5 "time"
6
7 "github.com/stormlightlabs/git-storm/internal/testutils"
8)
9
10func TestConventionalParser_Parse(t *testing.T) {
11 parser := &ConventionalParser{}
12 testTime := time.Now()
13
14 tests := []struct {
15 name string
16 subject string
17 body string
18 wantType string
19 wantScope string
20 wantDesc string
21 wantBreak bool
22 }{
23 {
24 name: "simple feat",
25 subject: "feat: add new feature",
26 body: "",
27 wantType: "feat",
28 wantScope: "",
29 wantDesc: "add new feature",
30 wantBreak: false,
31 },
32 {
33 name: "feat with scope",
34 subject: "feat(api): add pagination endpoint",
35 body: "",
36 wantType: "feat",
37 wantScope: "api",
38 wantDesc: "add pagination endpoint",
39 wantBreak: false,
40 },
41 {
42 name: "fix with scope",
43 subject: "fix(ui): correct button alignment issue",
44 body: "",
45 wantType: "fix",
46 wantScope: "ui",
47 wantDesc: "correct button alignment issue",
48 wantBreak: false,
49 },
50 {
51 name: "breaking change with !",
52 subject: "feat(api)!: remove support for legacy endpoints",
53 body: "",
54 wantType: "feat",
55 wantScope: "api",
56 wantDesc: "remove support for legacy endpoints",
57 wantBreak: true,
58 },
59 {
60 name: "breaking change without scope",
61 subject: "feat!: major API redesign",
62 body: "",
63 wantType: "feat",
64 wantScope: "",
65 wantDesc: "major API redesign",
66 wantBreak: true,
67 },
68 {
69 name: "breaking change in footer",
70 subject: "feat(api): update authentication",
71 body: "Some details here\n\nBREAKING CHANGE: API no longer accepts XML-formatted requests.",
72 wantType: "feat",
73 wantScope: "api",
74 wantDesc: "update authentication",
75 wantBreak: true,
76 },
77 {
78 name: "docs commit",
79 subject: "docs: update README installation instructions",
80 body: "",
81 wantType: "docs",
82 wantScope: "",
83 wantDesc: "update README installation instructions",
84 wantBreak: false,
85 },
86 {
87 name: "chore commit",
88 subject: "chore: update .gitignore",
89 body: "",
90 wantType: "chore",
91 wantScope: "",
92 wantDesc: "update .gitignore",
93 wantBreak: false,
94 },
95 {
96 name: "non-conventional commit",
97 subject: "some random commit message",
98 body: "",
99 wantType: "unknown",
100 wantScope: "",
101 wantDesc: "some random commit message",
102 wantBreak: false,
103 },
104 }
105
106 for _, tt := range tests {
107 t.Run(tt.name, func(t *testing.T) {
108 meta, err := parser.Parse("abc123", tt.subject, tt.body, testTime)
109 if err != nil {
110 t.Fatalf("Parse() error = %v", err)
111 }
112
113 if meta.Type != tt.wantType {
114 t.Errorf("Type = %v, want %v", meta.Type, tt.wantType)
115 }
116 if meta.Scope != tt.wantScope {
117 t.Errorf("Scope = %v, want %v", meta.Scope, tt.wantScope)
118 }
119 if meta.Description != tt.wantDesc {
120 t.Errorf("Description = %v, want %v", meta.Description, tt.wantDesc)
121 }
122 if meta.Breaking != tt.wantBreak {
123 t.Errorf("Breaking = %v, want %v", meta.Breaking, tt.wantBreak)
124 }
125 })
126 }
127}
128
129func TestConventionalParser_Categorize(t *testing.T) {
130 parser := &ConventionalParser{}
131
132 tests := []struct {
133 name string
134 meta CommitMeta
135 wantCat string
136 }{
137 {
138 name: "feat -> added",
139 meta: CommitMeta{Type: "feat"},
140 wantCat: "added",
141 },
142 {
143 name: "fix -> fixed",
144 meta: CommitMeta{Type: "fix"},
145 wantCat: "fixed",
146 },
147 {
148 name: "perf -> changed",
149 meta: CommitMeta{Type: "perf"},
150 wantCat: "changed",
151 },
152 {
153 name: "refactor -> changed",
154 meta: CommitMeta{Type: "refactor"},
155 wantCat: "changed",
156 },
157 {
158 name: "docs -> changed",
159 meta: CommitMeta{Type: "docs"},
160 wantCat: "changed",
161 },
162 {
163 name: "test -> changed",
164 meta: CommitMeta{Type: "test"},
165 wantCat: "changed",
166 },
167 {
168 name: "revert -> skip",
169 meta: CommitMeta{Type: "revert"},
170 wantCat: "",
171 },
172 {
173 name: "unknown -> skip",
174 meta: CommitMeta{Type: "unknown"},
175 wantCat: "",
176 },
177 }
178
179 for _, tt := range tests {
180 t.Run(tt.name, func(t *testing.T) {
181 got := parser.Categorize(tt.meta)
182 if got != tt.wantCat {
183 t.Errorf("Categorize() = %v, want %v", got, tt.wantCat)
184 }
185 })
186 }
187}
188
189func TestConventionalParser_IsValidType(t *testing.T) {
190 parser := &ConventionalParser{}
191
192 tests := []struct {
193 name string
194 kind CommitKind
195 want bool
196 }{
197 {
198 name: "feat is valid",
199 kind: CommitTypeFeat,
200 want: true,
201 },
202 {
203 name: "fix is valid",
204 kind: CommitTypeFix,
205 want: true,
206 },
207 {
208 name: "unknown is invalid",
209 kind: CommitTypeUnknown,
210 want: false,
211 },
212 }
213
214 for _, tt := range tests {
215 t.Run(tt.name, func(t *testing.T) {
216 got := parser.IsValidType(tt.kind)
217 if got != tt.want {
218 t.Errorf("IsValidType() = %v, want %v", got, tt.want)
219 }
220 })
221 }
222}
223
224func TestParseRefArgs(t *testing.T) {
225 tests := []struct {
226 name string
227 args []string
228 wantFrom string
229 wantTo string
230 }{
231 {
232 name: "range syntax",
233 args: []string{"v1.0.0..v1.1.0"},
234 wantFrom: "v1.0.0",
235 wantTo: "v1.1.0",
236 },
237 {
238 name: "two separate args",
239 args: []string{"v1.0.0", "v1.1.0"},
240 wantFrom: "v1.0.0",
241 wantTo: "v1.1.0",
242 },
243 {
244 name: "single arg defaults to HEAD",
245 args: []string{"v1.0.0"},
246 wantFrom: "v1.0.0",
247 wantTo: "HEAD",
248 },
249 {
250 name: "empty args",
251 args: []string{},
252 wantFrom: "",
253 wantTo: "",
254 },
255 }
256
257 for _, tt := range tests {
258 t.Run(tt.name, func(t *testing.T) {
259 from, to := ParseRefArgs(tt.args)
260 if from != tt.wantFrom {
261 t.Errorf("ParseRefArgs() from = %v, want %v", from, tt.wantFrom)
262 }
263 if to != tt.wantTo {
264 t.Errorf("ParseRefArgs() to = %v, want %v", to, tt.wantTo)
265 }
266 })
267 }
268}
269
270func TestGetCommitRange(t *testing.T) {
271 repo := testutils.SetupTestRepo(t)
272
273 commits := testutils.GetCommitHistory(t, repo)
274 if len(commits) < 3 {
275 t.Fatalf("Expected at least 3 commits, got %d", len(commits))
276 }
277
278 oldCommit := commits[len(commits)-2]
279 if err := testutils.CreateTagAtCommit(t, repo, "v1.0.0", oldCommit.Hash.String()); err != nil {
280 t.Fatalf("Failed to create tag: %v", err)
281 }
282
283 testutils.AddCommit(t, repo, "d.txt", "content d", "feat: add d feature")
284 testutils.AddCommit(t, repo, "e.txt", "content e", "fix: fix e bug")
285
286 rangeCommits, err := GetCommitRange(repo, "v1.0.0", "HEAD")
287 if err != nil {
288 t.Fatalf("GetCommitRange() error = %v", err)
289 }
290
291 if len(rangeCommits) < 2 {
292 t.Errorf("Expected at least 2 commits in range, got %d", len(rangeCommits))
293 }
294
295 for i := 1; i < len(rangeCommits); i++ {
296 if rangeCommits[i].Author.When.Before(rangeCommits[i-1].Author.When) {
297 t.Errorf("Commits are not in chronological order")
298 }
299 }
300}
301
302func TestGetCommitRange_SameRef(t *testing.T) {
303 repo := testutils.SetupTestRepo(t)
304
305 rangeCommits, err := GetCommitRange(repo, "HEAD", "HEAD")
306 if err != nil {
307 t.Fatalf("GetCommitRange() error = %v", err)
308 }
309
310 if len(rangeCommits) != 0 {
311 t.Errorf("Expected 0 commits when from and to are the same, got %d", len(rangeCommits))
312 }
313}
314
315func TestGetFileContent(t *testing.T) {
316 repo := testutils.SetupTestRepo(t)
317
318 content, err := GetFileContent(repo, "HEAD", "README.md")
319 if err != nil {
320 t.Fatalf("GetFileContent() error = %v", err)
321 }
322
323 if content == "" {
324 t.Errorf("Expected non-empty content for README.md")
325 }
326
327 if content != "# Project\n\nInitial version" {
328 t.Errorf("GetFileContent() content = %v, want %v", content, "# Project\\n\\nInitial version")
329 }
330}
331
332func TestGetFileContent_InvalidFile(t *testing.T) {
333 repo := testutils.SetupTestRepo(t)
334
335 _, err := GetFileContent(repo, "HEAD", "nonexistent.txt")
336 if err == nil {
337 t.Errorf("Expected error for non-existent file, got nil")
338 }
339}
340
341func TestGetChangedFiles(t *testing.T) {
342 repo := testutils.SetupTestRepo(t)
343
344 commits := testutils.GetCommitHistory(t, repo)
345 if len(commits) < 2 {
346 t.Fatalf("Expected at least 2 commits, got %d", len(commits))
347 }
348
349 files, err := GetChangedFiles(repo, commits[1].Hash.String(), commits[0].Hash.String())
350 if err != nil {
351 t.Fatalf("GetChangedFiles() error = %v", err)
352 }
353
354 if len(files) == 0 {
355 t.Errorf("Expected at least 1 changed file, got 0")
356 }
357}
358
359func TestGetChangedFiles_NoChanges(t *testing.T) {
360 repo := testutils.SetupTestRepo(t)
361
362 files, err := GetChangedFiles(repo, "HEAD", "HEAD")
363 if err != nil {
364 t.Fatalf("GetChangedFiles() error = %v", err)
365 }
366
367 if len(files) != 0 {
368 t.Errorf("Expected 0 changed files when refs are the same, got %d", len(files))
369 }
370}