1
+ /*
2
+ Copyright 2025 The Kubernetes Authors.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ package main
18
+
19
+ import (
20
+ "fmt"
21
+ "os"
22
+ "path/filepath"
23
+ "strings"
24
+ "testing"
25
+ "time"
26
+ )
27
+
28
+ func TestExtractYAMLHeader (t * testing.T ) {
29
+ tests := []struct {
30
+ name string
31
+ content string
32
+ want string
33
+ wantErr bool
34
+ }{
35
+ {
36
+ name : "valid YAML header with exactly 61 dashes" ,
37
+ content : strings .Repeat ("-" , 61 ) + "\n " +
38
+ "name: Test User\n " +
39
+ "ID: testuser\n " +
40
+ "info:\n " +
41
+ " employer: Test Corp\n " +
42
+ " slack: '@testuser'\n " +
43
+ strings .Repeat ("-" , 61 ) + "\n " +
44
+ "## Bio content\n " ,
45
+ want : "name: Test User\n ID: testuser\n info:\n employer: Test Corp\n slack: '@testuser'" ,
46
+ wantErr : false ,
47
+ },
48
+ {
49
+ name : "invalid YAML header with wrong number of dashes" ,
50
+ content : strings .Repeat ("-" , 60 ) + "\n " +
51
+ "name: Test User\n " +
52
+ strings .Repeat ("-" , 60 ) + "\n " ,
53
+ want : "" ,
54
+ wantErr : true ,
55
+ },
56
+ {
57
+ name : "no YAML header" ,
58
+ content : "Just some content without header" ,
59
+ want : "" ,
60
+ wantErr : true ,
61
+ },
62
+ {
63
+ name : "YAML header with extra spaces" ,
64
+ content : strings .Repeat ("-" , 61 ) + " \n " +
65
+ "name: Test User\n " +
66
+ "ID: testuser\n " +
67
+ strings .Repeat ("-" , 61 ) + " \n " +
68
+ "## Bio content\n " ,
69
+ want : "name: Test User\n ID: testuser" ,
70
+ wantErr : false ,
71
+ },
72
+ }
73
+
74
+ for _ , tt := range tests {
75
+ t .Run (tt .name , func (t * testing.T ) {
76
+ got , err := extractYAMLHeader (tt .content )
77
+ if (err != nil ) != tt .wantErr {
78
+ t .Errorf ("extractYAMLHeader() error = %v, wantErr %v" , err , tt .wantErr )
79
+ return
80
+ }
81
+ if got != tt .want {
82
+ t .Errorf ("extractYAMLHeader() = %q, want %q" , got , tt .want )
83
+ }
84
+ })
85
+ }
86
+ }
87
+
88
+ func TestCountWords (t * testing.T ) {
89
+ tests := []struct {
90
+ name string
91
+ content string
92
+ want int
93
+ }{
94
+ {
95
+ name : "simple sentence" ,
96
+ content : "Hello world test" ,
97
+ want : 3 ,
98
+ },
99
+ {
100
+ name : "text with newlines" ,
101
+ content : "Hello\n world\n test" ,
102
+ want : 3 ,
103
+ },
104
+ {
105
+ name : "text with multiple spaces" ,
106
+ content : "Hello world test" ,
107
+ want : 3 ,
108
+ },
109
+ {
110
+ name : "empty content" ,
111
+ content : "" ,
112
+ want : 0 ,
113
+ },
114
+ {
115
+ name : "only whitespace" ,
116
+ content : " \n \t \n " ,
117
+ want : 0 ,
118
+ },
119
+ {
120
+ name : "mixed content with punctuation" ,
121
+ content : "Hello, world! This is a test." ,
122
+ want : 6 ,
123
+ },
124
+ }
125
+
126
+ for _ , tt := range tests {
127
+ t .Run (tt .name , func (t * testing.T ) {
128
+ // Create a temporary file with the content
129
+ tmpfile , err := os .CreateTemp ("" , "test" )
130
+ if err != nil {
131
+ t .Fatal (err )
132
+ }
133
+ defer os .Remove (tmpfile .Name ())
134
+
135
+ if _ , err := tmpfile .Write ([]byte (tt .content )); err != nil {
136
+ t .Fatal (err )
137
+ }
138
+ if err := tmpfile .Close (); err != nil {
139
+ t .Fatal (err )
140
+ }
141
+
142
+ got , err := countWords (tmpfile .Name ())
143
+ if err != nil {
144
+ t .Errorf ("countWords() error = %v" , err )
145
+ return
146
+ }
147
+ if got != tt .want {
148
+ t .Errorf ("countWords() = %v, want %v" , got , tt .want )
149
+ }
150
+ })
151
+ }
152
+ }
153
+
154
+ func TestValidateFileNameAndGitHubID (t * testing.T ) {
155
+ tests := []struct {
156
+ name string
157
+ filename string
158
+ fileContent string
159
+ wantErr bool
160
+ errContains string
161
+ }{
162
+ {
163
+ name : "valid filename and matching GitHub ID" ,
164
+ filename : "candidate-testuser.md" ,
165
+ fileContent : strings .Repeat ("-" , 61 ) + "\n " +
166
+ "name: Test User\n " +
167
+ "ID: testuser\n " +
168
+ "info:\n " +
169
+ " employer: Test Corp\n " +
170
+ " slack: '@testuser'\n " +
171
+ strings .Repeat ("-" , 61 ) + "\n " +
172
+ "## Bio content\n " ,
173
+ wantErr : false ,
174
+ },
175
+ {
176
+ name : "invalid filename format" ,
177
+ filename : "invalid-format.md" ,
178
+ fileContent : "dummy content" ,
179
+ wantErr : true ,
180
+ errContains : "filename must follow format" ,
181
+ },
182
+ {
183
+ name : "mismatched GitHub ID" ,
184
+ filename : "candidate-testuser.md" ,
185
+ fileContent : strings .Repeat ("-" , 61 ) + "\n " +
186
+ "name: Test User\n " +
187
+ "ID: differentuser\n " +
188
+ "info:\n " +
189
+ " employer: Test Corp\n " +
190
+ " slack: '@testuser'\n " +
191
+ strings .Repeat ("-" , 61 ) + "\n " +
192
+ "## Bio content\n " ,
193
+ wantErr : true ,
194
+ errContains : "does not match GitHub ID" ,
195
+ },
196
+ {
197
+ name : "missing ID field" ,
198
+ filename : "candidate-testuser.md" ,
199
+ fileContent : strings .Repeat ("-" , 61 ) + "\n name: Test User\n " + strings .Repeat ("-" , 61 ) + "\n " ,
200
+ wantErr : true ,
201
+ errContains : "missing 'ID' field" ,
202
+ },
203
+ }
204
+
205
+ for _ , tt := range tests {
206
+ t .Run (tt .name , func (t * testing.T ) {
207
+ // Create a temporary file with the content
208
+ tmpfile , err := os .CreateTemp ("" , tt .filename )
209
+ if err != nil {
210
+ t .Fatal (err )
211
+ }
212
+ defer os .Remove (tmpfile .Name ())
213
+
214
+ // Rename to the desired filename
215
+ dir := filepath .Dir (tmpfile .Name ())
216
+ targetPath := filepath .Join (dir , tt .filename )
217
+ if err := os .Rename (tmpfile .Name (), targetPath ); err != nil {
218
+ t .Fatal (err )
219
+ }
220
+ defer os .Remove (targetPath )
221
+
222
+ if err := os .WriteFile (targetPath , []byte (tt .fileContent ), 0644 ); err != nil {
223
+ t .Fatal (err )
224
+ }
225
+
226
+ err = validateFileNameAndGitHubID (targetPath )
227
+ if (err != nil ) != tt .wantErr {
228
+ t .Errorf ("validateFileNameAndGitHubID() error = %v, wantErr %v" , err , tt .wantErr )
229
+ return
230
+ }
231
+ if tt .wantErr && tt .errContains != "" && ! strings .Contains (err .Error (), tt .errContains ) {
232
+ t .Errorf ("validateFileNameAndGitHubID() error = %v, should contain %q" , err , tt .errContains )
233
+ }
234
+ })
235
+ }
236
+ }
237
+
238
+ func TestValidateTemplateCompliance (t * testing.T ) {
239
+ validContent := strings .Repeat ("-" , 61 ) + "\n " +
240
+ "name: Test User\n " +
241
+ "ID: testuser\n " +
242
+ "info:\n " +
243
+ " employer: Test Corp\n " +
244
+ " slack: '@testuser'\n " +
245
+ strings .Repeat ("-" , 61 ) + "\n " +
246
+ "## What I have done\n " +
247
+ "Some accomplishments\n " +
248
+ "## What I'll do\n " +
249
+ "Future plans\n " +
250
+ "## SIGS\n " +
251
+ "SIG involvement\n " +
252
+ "## Resources About Me\n " +
253
+ "Links and info\n "
254
+
255
+ tests := []struct {
256
+ name string
257
+ content string
258
+ wantErr bool
259
+ errContains string
260
+ }{
261
+ {
262
+ name : "valid template compliance" ,
263
+ content : validContent ,
264
+ wantErr : false ,
265
+ },
266
+ {
267
+ name : "missing required field - name" ,
268
+ content : strings .Repeat ("-" , 61 ) + "\n " +
269
+ "ID: testuser\n " +
270
+ "info:\n " +
271
+ " employer: Test Corp\n " +
272
+ " slack: '@testuser'\n " +
273
+ strings .Repeat ("-" , 61 ) + "\n " +
274
+ "## What I have done\n ## What I'll do\n ## SIGS\n ## Resources About Me\n " ,
275
+ wantErr : true ,
276
+ errContains : "missing required field: name" ,
277
+ },
278
+ {
279
+ name : "missing required section" ,
280
+ content : strings .Repeat ("-" , 61 ) + "\n " +
281
+ "name: Test User\n " +
282
+ "ID: testuser\n " +
283
+ "info:\n " +
284
+ " employer: Test Corp\n " +
285
+ " slack: '@testuser'\n " +
286
+ strings .Repeat ("-" , 61 ) + "\n " +
287
+ "## What I have done\n ## What I'll do\n ## Resources About Me\n " ,
288
+ wantErr : true ,
289
+ errContains : "missing required section: SIGs" ,
290
+ },
291
+ {
292
+ name : "alternative SIGs section name" ,
293
+ content : strings .Repeat ("-" , 61 ) + "\n " +
294
+ "name: Test User\n " +
295
+ "ID: testuser\n " +
296
+ "info:\n " +
297
+ " employer: Test Corp\n " +
298
+ " slack: '@testuser'\n " +
299
+ strings .Repeat ("-" , 61 ) + "\n " +
300
+ "## What I have done\n ## What I'll do\n ## SIGs\n ## Resources About Me\n " ,
301
+ wantErr : false ,
302
+ },
303
+ }
304
+
305
+ for _ , tt := range tests {
306
+ t .Run (tt .name , func (t * testing.T ) {
307
+ // Create a temporary file with the content
308
+ tmpfile , err := os .CreateTemp ("" , "test" )
309
+ if err != nil {
310
+ t .Fatal (err )
311
+ }
312
+ defer os .Remove (tmpfile .Name ())
313
+
314
+ if err := os .WriteFile (tmpfile .Name (), []byte (tt .content ), 0644 ); err != nil {
315
+ t .Fatal (err )
316
+ }
317
+
318
+ err = validateTemplateCompliance (tmpfile .Name ())
319
+ if (err != nil ) != tt .wantErr {
320
+ t .Errorf ("validateTemplateCompliance() error = %v, wantErr %v" , err , tt .wantErr )
321
+ return
322
+ }
323
+ if tt .wantErr && tt .errContains != "" && ! strings .Contains (err .Error (), tt .errContains ) {
324
+ t .Errorf ("validateTemplateCompliance() error = %v, should contain %q" , err , tt .errContains )
325
+ }
326
+ })
327
+ }
328
+ }
329
+
330
+ func TestFindBioFiles (t * testing.T ) {
331
+ // Create a temporary directory structure
332
+ tmpDir , err := os .MkdirTemp ("" , "elections" )
333
+ if err != nil {
334
+ t .Fatal (err )
335
+ }
336
+ defer os .RemoveAll (tmpDir )
337
+
338
+ // Create steering directory structure
339
+ steeringDir := filepath .Join (tmpDir , "steering" )
340
+ if err := os .MkdirAll (steeringDir , 0755 ); err != nil {
341
+ t .Fatal (err )
342
+ }
343
+
344
+ // Create test files
345
+ currentYear := time .Now ().Year ()
346
+ testFiles := []struct {
347
+ path string
348
+ shouldFind bool
349
+ }{
350
+ {filepath .Join (steeringDir , "2025" , "candidate-user1.md" ), false }, // Before startYear (2026)
351
+ {filepath .Join (steeringDir , "2026" , "candidate-user2.md" ), true }, // At startYear
352
+ {filepath .Join (steeringDir , fmt .Sprintf ("%d" , currentYear + 1 ), "candidate-user3.md" ), true }, // Future year
353
+ {filepath .Join (steeringDir , "2026" , "not-candidate.md" ), false }, // Wrong filename format
354
+ {filepath .Join (steeringDir , "2026" , "candidate-user4.txt" ), false }, // Wrong extension
355
+ {filepath .Join (steeringDir , fmt .Sprintf ("%d" , currentYear + 10 ), "candidate-user5.md" ), false }, // Too far in future
356
+ }
357
+
358
+ var expectedFiles []string
359
+ for _ , tf := range testFiles {
360
+ dir := filepath .Dir (tf .path )
361
+ if err := os .MkdirAll (dir , 0755 ); err != nil {
362
+ t .Fatal (err )
363
+ }
364
+ if err := os .WriteFile (tf .path , []byte ("dummy content" ), 0644 ); err != nil {
365
+ t .Fatal (err )
366
+ }
367
+ if tf .shouldFind {
368
+ expectedFiles = append (expectedFiles , tf .path )
369
+ }
370
+ }
371
+
372
+ // Test findBioFiles
373
+ bioFiles , err := findBioFiles (tmpDir )
374
+ if err != nil {
375
+ t .Errorf ("findBioFiles() error = %v" , err )
376
+ return
377
+ }
378
+
379
+ if len (bioFiles ) != len (expectedFiles ) {
380
+ t .Errorf ("findBioFiles() found %d files, expected %d" , len (bioFiles ), len (expectedFiles ))
381
+ }
382
+
383
+ // Check that all expected files are found
384
+ for _ , expected := range expectedFiles {
385
+ found := false
386
+ for _ , actual := range bioFiles {
387
+ if actual == expected {
388
+ found = true
389
+ break
390
+ }
391
+ }
392
+ if ! found {
393
+ t .Errorf ("findBioFiles() did not find expected file: %s" , expected )
394
+ }
395
+ }
396
+ }
0 commit comments