1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5package init //nolint:testpackage // testing internal validators
6
7import (
8 "errors"
9 "testing"
10)
11
12func TestValidateKeyFormat_Valid(t *testing.T) {
13 t.Parallel()
14
15 tests := []struct {
16 name string
17 input string
18 }{
19 {"simple key", "work"},
20 {"key with hyphen", "q1-goals"},
21 {"key with numbers", "area2"},
22 {"multiple hyphens", "my-special-area"},
23 }
24
25 for _, tc := range tests {
26 t.Run(tc.name, func(t *testing.T) {
27 t.Parallel()
28
29 if err := validateKeyFormat(tc.input); err != nil {
30 t.Errorf("validateKeyFormat(%q) = %v, want nil", tc.input, err)
31 }
32 })
33 }
34}
35
36func TestValidateKeyFormat_Invalid(t *testing.T) {
37 t.Parallel()
38
39 tests := []struct {
40 name string
41 input string
42 wantErr error
43 }{
44 {"empty key", "", errKeyRequired},
45 {"uppercase letters", "Work", errKeyFormat},
46 {"spaces", "my area", errKeyFormat},
47 {"underscores", "my_area", errKeyFormat},
48 {"leading hyphen", "-work", errKeyFormat},
49 {"trailing hyphen", "work-", errKeyFormat},
50 {"double hyphen", "my--area", errKeyFormat},
51 {"special characters", "work@home", errKeyFormat},
52 }
53
54 for _, tc := range tests {
55 t.Run(tc.name, func(t *testing.T) {
56 t.Parallel()
57
58 if err := validateKeyFormat(tc.input); !errors.Is(err, tc.wantErr) {
59 t.Errorf("validateKeyFormat(%q) = %v, want %v", tc.input, err, tc.wantErr)
60 }
61 })
62 }
63}
64
65func TestValidateReference_Valid(t *testing.T) {
66 t.Parallel()
67
68 const (
69 uuidLower = "123e4567-e89b-12d3-a456-426614174000"
70 uuidArea = "3bbf1923-64ae-4bcf-96a9-9bb86c799dab"
71 uuidGoal = "9d79e922-9ca8-4b8c-9aa5-dd98bb2492b2"
72 uuidNote = "e230ef3e-d9a5-4211-9dfb-515cc892d6e5"
73 )
74
75 tests := []struct {
76 name string
77 input string
78 supportsDeepLink bool
79 wantID string
80 }{
81 {"valid UUID lowercase", uuidLower, false, uuidLower},
82 {"valid UUID uppercase", "123E4567-E89B-12D3-A456-426614174000", false, "123E4567-E89B-12D3-A456-426614174000"},
83 {"deep link area", "lunatask://areas/" + uuidArea, true, uuidArea},
84 {"deep link goal", "lunatask://goals/" + uuidGoal, true, uuidGoal},
85 {"deep link note", "lunatask://notes/" + uuidNote, true, uuidNote},
86 }
87
88 for _, testCase := range tests {
89 t.Run(testCase.name, func(t *testing.T) {
90 t.Parallel()
91
92 id, err := validateReference(testCase.input, testCase.supportsDeepLink)
93 if err != nil {
94 t.Errorf("validateReference(%q, %v) = %v, want nil",
95 testCase.input, testCase.supportsDeepLink, err)
96 }
97
98 if id != testCase.wantID {
99 t.Errorf("validateReference(%q, %v) = %q, want %q",
100 testCase.input, testCase.supportsDeepLink, id, testCase.wantID)
101 }
102 })
103 }
104}
105
106func TestValidateReference_Invalid(t *testing.T) {
107 t.Parallel()
108
109 const validUUID = "123e4567-e89b-12d3-a456-426614174000"
110
111 tests := []struct {
112 name string
113 input string
114 supportsDeepLink bool
115 wantErr error
116 }{
117 {"empty reference", "", true, errRefRequired},
118 {"too short", "123e4567-e89b", true, errRefFormat},
119 {"random string", "not-a-uuid", true, errRefFormat},
120 {"wrong characters", "123e4567-e89b-12d3-a456-42661417zzzz", false, errRefFormat},
121 {"invalid scheme", "http://areas/" + validUUID, true, errRefFormat},
122 {"invalid resource", "lunatask://invalid/" + validUUID, true, errRefFormat},
123 }
124
125 for _, testCase := range tests {
126 t.Run(testCase.name, func(t *testing.T) {
127 t.Parallel()
128
129 _, err := validateReference(testCase.input, testCase.supportsDeepLink)
130 if !errors.Is(err, testCase.wantErr) {
131 t.Errorf("validateReference(%q, %v) = %v, want %v",
132 testCase.input, testCase.supportsDeepLink, err, testCase.wantErr)
133 }
134 })
135 }
136}
137
138func TestParseEditIndex_Valid(t *testing.T) {
139 t.Parallel()
140
141 tests := []struct {
142 name string
143 input string
144 wantIdx int
145 }{
146 {"index 0", "edit:0", 0},
147 {"index 5", "edit:5", 5},
148 {"large index", "edit:999", 999},
149 {"negative index", "edit:-1", -1},
150 }
151
152 for _, testCase := range tests {
153 t.Run(testCase.name, func(t *testing.T) {
154 t.Parallel()
155
156 idx, valid := parseEditIndex(testCase.input)
157 if !valid {
158 t.Errorf("parseEditIndex(%q) valid = false, want true", testCase.input)
159 }
160
161 if idx != testCase.wantIdx {
162 t.Errorf("parseEditIndex(%q) idx = %d, want %d", testCase.input, idx, testCase.wantIdx)
163 }
164 })
165 }
166}
167
168func TestParseEditIndex_Invalid(t *testing.T) {
169 t.Parallel()
170
171 tests := []struct {
172 name string
173 input string
174 }{
175 {"back choice", "back"},
176 {"add choice", "add"},
177 {"done choice", "done"},
178 {"empty prefix", "edit:"},
179 {"non-numeric", "edit:abc"},
180 {"empty string", ""},
181 }
182
183 for _, testCase := range tests {
184 t.Run(testCase.name, func(t *testing.T) {
185 t.Parallel()
186
187 _, valid := parseEditIndex(testCase.input)
188 if valid {
189 t.Errorf("parseEditIndex(%q) valid = true, want false", testCase.input)
190 }
191 })
192 }
193}
194
195func TestTitleCase(t *testing.T) {
196 t.Parallel()
197
198 tests := []struct {
199 input string
200 want string
201 }{
202 {"area", "Area"},
203 {"goal", "Goal"},
204 {"notebook", "Notebook"},
205 {"habit", "Habit"},
206 {"a", "A"},
207 {"", ""},
208 {"already Capitalized", "Already Capitalized"},
209 }
210
211 for _, tc := range tests {
212 t.Run(tc.input, func(t *testing.T) {
213 t.Parallel()
214
215 if got := titleCase(tc.input); got != tc.want {
216 t.Errorf("titleCase(%q) = %q, want %q", tc.input, got, tc.want)
217 }
218 })
219 }
220}