1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package domain_test
4
5import (
6 "errors"
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)
15
16func TestParseIssueTitle(t *testing.T) {
17 t.Parallel()
18 tests := []struct {
19 name, in, want, reason string // reason "" when valid
20 }{
21 {"plain", "Crash on start", "Crash on start", ""},
22 {"max length in code points", strings.Repeat("é", 120), strings.Repeat("é", 120), ""},
23 {"too long", strings.Repeat("a", 121), "", "longer than 120"},
24 {"emoji count as one", strings.Repeat("🎉", 120), strings.Repeat("🎉", 120), ""},
25 {"empty", "", "", "is empty"},
26 {"blank", " \t ", "", "is empty"},
27 {"only controls", "\x01\x02", "", "is empty"},
28 {"newline", "a\nb", "", "one line"},
29 {"carriage return", "a\rb", "", "one line"},
30 {"nfc", "e\u0301", "\u00e9", ""},
31 {"c0 stripped", "a\x00b\x1bc\tz", "abcz", ""},
32 {"c1 stripped", "a\u0085b\u009fc", "abc", ""},
33 {"del stripped", "a\x7fb", "ab", ""},
34 {"bidi stripped", "a\u202ab\u202ec\u2066d\u2069e\u200ef\u200fg\u061ch", "abcdefgh", ""},
35 {"not utf-8", "a\xffb", "", "UTF-8"},
36 }
37 for _, tt := range tests {
38 t.Run(tt.name, func(t *testing.T) {
39 t.Parallel()
40 got, err := domain.ParseIssueTitle(tt.in)
41 if tt.reason == "" {
42 require.NoError(t, err)
43 assert.Equal(t, tt.want, got.String())
44 return
45 }
46 var te *domain.TextError
47 require.True(t, errors.As(err, &te), "%v", err)
48 assert.ErrorIs(t, err, domain.ErrInvalidText)
49 assert.Contains(t, te.Reason, tt.reason)
50 })
51 }
52}
53
54func TestParseIssueBody(t *testing.T) {
55 t.Parallel()
56 tests := []struct {
57 name, in, want, reason string
58 }{
59 {"empty allowed", "", "", ""},
60 {"multiline and tabs kept", "a\n\tb\n", "a\n\tb\n", ""},
61 {"crlf becomes lf", "a\r\nb", "a\nb", ""},
62 {"max length", strings.Repeat("é", 10000), strings.Repeat("é", 10000), ""},
63 {"too long", strings.Repeat("a", 10001), "", "longer than 10000"},
64 {"nfc", "e\u0301\n", "\u00e9\n", ""},
65 {"controls stripped", "a\x00\x08b\u0085c\u009fd\x7f", "abcd", ""},
66 {"bidi stripped", "\u202etxt\u202c \u2067x\u2069", "txt x", ""},
67 {"separators stripped", "a\u2028b\u2029c", "abc", ""},
68 {"not utf-8", "\xc3\x28", "", "UTF-8"},
69 }
70 for _, tt := range tests {
71 t.Run(tt.name, func(t *testing.T) {
72 t.Parallel()
73 got, err := domain.ParseIssueBody(tt.in)
74 if tt.reason == "" {
75 require.NoError(t, err)
76 assert.Equal(t, tt.want, got.String())
77 return
78 }
79 var te *domain.TextError
80 require.True(t, errors.As(err, &te), "%v", err)
81 assert.Contains(t, te.Reason, tt.reason)
82 })
83 }
84}
85
86func TestParseAuthorLabel(t *testing.T) {
87 t.Parallel()
88 tests := []struct {
89 name, in, want, reason string
90 }{
91 {"plain", "Ada", "Ada", ""},
92 {"empty allowed", "", "", ""},
93 {"max length", strings.Repeat("é", 40), strings.Repeat("é", 40), ""},
94 {"too long", strings.Repeat("a", 41), "", "longer than 40"},
95 {"newline", "a\nb", "", "one line"},
96 {"nfc", "e\u0301", "\u00e9", ""},
97 {"stripped", "A\u202ed\x00a", "Ada", ""},
98 {"trimmed", " Ada ", "Ada", ""},
99 {"not utf-8", "\xff", "", "UTF-8"},
100 }
101 for _, tt := range tests {
102 t.Run(tt.name, func(t *testing.T) {
103 t.Parallel()
104 got, err := domain.ParseAuthorLabel(tt.in)
105 if tt.reason == "" {
106 require.NoError(t, err)
107 assert.Equal(t, tt.want, got)
108 return
109 }
110 var te *domain.TextError
111 require.True(t, errors.As(err, &te), "%v", err)
112 assert.Contains(t, te.Reason, tt.reason)
113 })
114 }
115}
116
117func TestIssuesMode(t *testing.T) {
118 t.Parallel()
119 for _, m := range domain.IssuesModes() {
120 got, err := domain.ParseIssuesMode(m.String())
121 require.NoError(t, err)
122 assert.Equal(t, m, got)
123 }
124 _, err := domain.ParseIssuesMode("all")
125 assert.ErrorIs(t, err, domain.ErrInvalid)
126 assert.Equal(t, domain.IssuesOff, domain.DefaultRepoSettings().Issues)
127 assert.False(t, domain.DefaultRepoSettings().IsZero())
128 assert.True(t, domain.RepoSettings{}.IsZero())
129 assert.Equal(t, domain.IssuesOff, domain.IssuesMode("").OrDefault())
130}
131
132func TestIssueEventKinds(t *testing.T) {
133 t.Parallel()
134 assert.Equal(t, "issue.opened", domain.IssueOpened{}.EventKind())
135 assert.Equal(t, "issue.closed", domain.IssueClosed{}.EventKind())
136 assert.Equal(t, "issue.reopened", domain.IssueReopened{}.EventKind())
137 assert.Equal(t, "issue.commented", domain.IssueCommented{}.EventKind())
138}