Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/installer/template_test.go
2843 views
1
package installer
2
3
import (
4
"os"
5
"path/filepath"
6
"strings"
7
"testing"
8
9
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
10
fileutil "github.com/projectdiscovery/utils/file"
11
mapsutil "github.com/projectdiscovery/utils/maps"
12
"github.com/stretchr/testify/require"
13
)
14
15
func TestTemplateInstallation(t *testing.T) {
16
// test that the templates are installed correctly
17
// along with necessary changes that are made
18
HideProgressBar = true
19
20
tm := &TemplateManager{}
21
dir, err := os.MkdirTemp("", "nuclei-templates-*")
22
require.Nil(t, err)
23
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
24
require.Nil(t, err)
25
defer func() {
26
_ = os.RemoveAll(dir)
27
_ = os.RemoveAll(cfgdir)
28
}()
29
30
// set the config directory to a temporary directory
31
config.DefaultConfig.SetConfigDir(cfgdir)
32
// set the templates directory to a temporary directory
33
templatesTempDir := filepath.Join(dir, "templates")
34
config.DefaultConfig.SetTemplatesDir(templatesTempDir)
35
36
err = tm.FreshInstallIfNotExists()
37
if err != nil {
38
if strings.Contains(err.Error(), "rate limit") {
39
t.Skip("Skipping test due to github rate limit")
40
}
41
require.Nil(t, err)
42
}
43
44
// we should switch to more fine granular tests for template
45
// integrity, but for now, we just check that the templates are installed
46
counter := 0
47
err = filepath.Walk(templatesTempDir, func(path string, info os.FileInfo, err error) error {
48
if err != nil {
49
return err
50
}
51
if !info.IsDir() {
52
counter++
53
}
54
return nil
55
})
56
require.Nil(t, err)
57
58
// we should have at least 1000 templates
59
require.Greater(t, counter, 1000)
60
// every time we install templates, it should override the ignore file with latest one
61
require.FileExists(t, config.DefaultConfig.GetIgnoreFilePath())
62
t.Logf("Installed %d templates", counter)
63
}
64
65
func TestIsOutdatedVersion(t *testing.T) {
66
testCases := []struct {
67
current string
68
latest string
69
expected bool
70
desc string
71
}{
72
// Test the empty latest version case (main bug fix)
73
{"v10.2.7", "", false, "Empty latest version should not trigger update"},
74
75
// Test same versions
76
{"v10.2.7", "v10.2.7", false, "Same versions should not trigger update"},
77
78
// Test outdated version
79
{"v10.2.6", "v10.2.7", true, "Older version should trigger update"},
80
81
// Test newer current version (edge case)
82
{"v10.2.8", "v10.2.7", false, "Newer current version should not trigger update"},
83
84
// Test dev versions
85
{"v10.2.7-dev", "v10.2.7", false, "Dev version matching release should not trigger update"},
86
{"v10.2.6-dev", "v10.2.7", true, "Outdated dev version should trigger update"},
87
88
// Test invalid semver fallback
89
{"invalid-version", "v10.2.7", true, "Invalid current version should trigger update (fallback)"},
90
{"v10.2.7", "invalid-version", true, "Invalid latest version should trigger update (fallback)"},
91
{"same-invalid", "same-invalid", false, "Same invalid versions should not trigger update (fallback)"},
92
}
93
94
for _, tc := range testCases {
95
t.Run(tc.desc, func(t *testing.T) {
96
result := config.IsOutdatedVersion(tc.current, tc.latest)
97
require.Equal(t, tc.expected, result,
98
"IsOutdatedVersion(%q, %q) = %t, expected %t",
99
tc.current, tc.latest, result, tc.expected)
100
})
101
}
102
}
103
104
func TestCleanupOrphanedTemplates(t *testing.T) {
105
HideProgressBar = true
106
107
tm := &TemplateManager{}
108
109
t.Run("removes orphaned templates", func(t *testing.T) {
110
// Create temporary directories
111
tmpDir, err := os.MkdirTemp("", "nuclei-cleanup-test-*")
112
require.NoError(t, err)
113
defer func() {
114
_ = os.RemoveAll(tmpDir)
115
}()
116
117
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
118
require.NoError(t, err)
119
defer func() {
120
_ = os.RemoveAll(cfgdir)
121
}()
122
123
config.DefaultConfig.SetConfigDir(cfgdir)
124
config.DefaultConfig.SetTemplatesDir(tmpDir)
125
126
// Create subdirectories for templates
127
templatesDir1 := filepath.Join(tmpDir, "cves", "2023")
128
templatesDir2 := filepath.Join(tmpDir, "exposures", "configs")
129
require.NoError(t, os.MkdirAll(templatesDir1, 0755))
130
require.NoError(t, os.MkdirAll(templatesDir2, 0755))
131
132
// Create template files
133
template1 := filepath.Join(templatesDir1, "CVE-2023-1234.yaml")
134
template2 := filepath.Join(templatesDir1, "CVE-2023-5678.yaml")
135
template3 := filepath.Join(templatesDir2, "git-config-exposure.yaml")
136
orphanedTemplate1 := filepath.Join(templatesDir1, "old-template.yaml")
137
orphanedTemplate2 := filepath.Join(templatesDir2, "removed-template.yaml")
138
139
// Write valid template files
140
templateContent := `id: test-template
141
info:
142
name: Test Template
143
author: test
144
severity: info`
145
require.NoError(t, os.WriteFile(template1, []byte(templateContent), 0644))
146
require.NoError(t, os.WriteFile(template2, []byte(templateContent), 0644))
147
require.NoError(t, os.WriteFile(template3, []byte(templateContent), 0644))
148
require.NoError(t, os.WriteFile(orphanedTemplate1, []byte(templateContent), 0644))
149
require.NoError(t, os.WriteFile(orphanedTemplate2, []byte(templateContent), 0644))
150
151
// Simulate written paths from new release (only template1, template2, template3)
152
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
153
absTemplate1, _ := filepath.Abs(template1)
154
absTemplate2, _ := filepath.Abs(template2)
155
absTemplate3, _ := filepath.Abs(template3)
156
// Normalize paths consistently (same as cleanupOrphanedTemplates does)
157
absTemplate1 = filepath.Clean(absTemplate1)
158
absTemplate2 = filepath.Clean(absTemplate2)
159
absTemplate3 = filepath.Clean(absTemplate3)
160
_ = writtenPaths.Set(absTemplate1, struct{}{})
161
_ = writtenPaths.Set(absTemplate2, struct{}{})
162
_ = writtenPaths.Set(absTemplate3, struct{}{})
163
164
// Run cleanup
165
err = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)
166
require.NoError(t, err)
167
168
// Verify orphaned templates were removed
169
require.NoFileExists(t, orphanedTemplate1, "orphaned template should be removed")
170
require.NoFileExists(t, orphanedTemplate2, "orphaned template should be removed")
171
172
// Verify non-orphaned templates still exist
173
require.FileExists(t, template1, "template from new release should exist")
174
require.FileExists(t, template2, "template from new release should exist")
175
require.FileExists(t, template3, "template from new release should exist")
176
})
177
178
t.Run("preserves custom templates", func(t *testing.T) {
179
// Create temporary directories
180
tmpDir, err := os.MkdirTemp("", "nuclei-cleanup-custom-test-*")
181
require.NoError(t, err)
182
defer func() {
183
_ = os.RemoveAll(tmpDir)
184
}()
185
186
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
187
require.NoError(t, err)
188
defer func() {
189
_ = os.RemoveAll(cfgdir)
190
}()
191
192
config.DefaultConfig.SetConfigDir(cfgdir)
193
config.DefaultConfig.SetTemplatesDir(tmpDir)
194
195
// Create custom template directory
196
customGitHubDir := filepath.Join(tmpDir, "github", "owner", "repo")
197
require.NoError(t, os.MkdirAll(customGitHubDir, 0755))
198
199
// Create custom template file
200
customTemplate := filepath.Join(customGitHubDir, "custom-template.yaml")
201
templateContent := `id: custom-template
202
info:
203
name: Custom Template
204
author: test
205
severity: info`
206
require.NoError(t, os.WriteFile(customTemplate, []byte(templateContent), 0644))
207
208
// Empty written paths (simulating no custom templates in new release)
209
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
210
211
// Run cleanup
212
err = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)
213
require.NoError(t, err)
214
215
// Verify custom template was NOT removed
216
require.FileExists(t, customTemplate, "custom template should be preserved")
217
})
218
219
t.Run("skips non-template files", func(t *testing.T) {
220
// Create temporary directories
221
tmpDir, err := os.MkdirTemp("", "nuclei-cleanup-nontemplate-test-*")
222
require.NoError(t, err)
223
defer func() {
224
_ = os.RemoveAll(tmpDir)
225
}()
226
227
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
228
require.NoError(t, err)
229
defer func() {
230
_ = os.RemoveAll(cfgdir)
231
}()
232
233
config.DefaultConfig.SetConfigDir(cfgdir)
234
config.DefaultConfig.SetTemplatesDir(tmpDir)
235
236
// Create non-template files
237
readmeFile := filepath.Join(tmpDir, "README.md")
238
configFile := filepath.Join(tmpDir, "cves.json")
239
checksumFile := filepath.Join(tmpDir, ".checksum")
240
241
require.NoError(t, os.WriteFile(readmeFile, []byte("# Templates"), 0644))
242
require.NoError(t, os.WriteFile(configFile, []byte("{}"), 0644))
243
require.NoError(t, os.WriteFile(checksumFile, []byte(""), 0644))
244
245
// Empty written paths
246
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
247
248
// Run cleanup
249
err = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)
250
require.NoError(t, err)
251
252
// Verify non-template files were NOT removed
253
require.FileExists(t, readmeFile, "README.md should be preserved")
254
require.FileExists(t, configFile, "config file should be preserved")
255
require.FileExists(t, checksumFile, "checksum file should be preserved")
256
})
257
258
t.Run("handles empty written paths", func(t *testing.T) {
259
// Create temporary directories
260
tmpDir, err := os.MkdirTemp("", "nuclei-cleanup-empty-test-*")
261
require.NoError(t, err)
262
defer func() {
263
_ = os.RemoveAll(tmpDir)
264
}()
265
266
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
267
require.NoError(t, err)
268
defer func() {
269
_ = os.RemoveAll(cfgdir)
270
}()
271
272
config.DefaultConfig.SetConfigDir(cfgdir)
273
config.DefaultConfig.SetTemplatesDir(tmpDir)
274
275
// Create template files
276
template1 := filepath.Join(tmpDir, "template1.yaml")
277
templateContent := `id: test-template
278
info:
279
name: Test Template
280
author: test
281
severity: info`
282
require.NoError(t, os.WriteFile(template1, []byte(templateContent), 0644))
283
284
// Empty written paths
285
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
286
287
// Run cleanup
288
err = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)
289
require.NoError(t, err)
290
291
// Verify template was removed (since it's not in written paths)
292
require.NoFileExists(t, template1, "template should be removed when not in written paths")
293
})
294
295
t.Run("handles relative and absolute paths correctly", func(t *testing.T) {
296
// Create temporary directories
297
tmpDir, err := os.MkdirTemp("", "nuclei-cleanup-path-test-*")
298
require.NoError(t, err)
299
defer func() {
300
_ = os.RemoveAll(tmpDir)
301
}()
302
303
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
304
require.NoError(t, err)
305
defer func() {
306
_ = os.RemoveAll(cfgdir)
307
}()
308
309
config.DefaultConfig.SetConfigDir(cfgdir)
310
config.DefaultConfig.SetTemplatesDir(tmpDir)
311
312
// Create template file
313
template1 := filepath.Join(tmpDir, "template1.yaml")
314
templateContent := `id: test-template
315
info:
316
name: Test Template
317
author: test
318
severity: info`
319
require.NoError(t, os.WriteFile(template1, []byte(templateContent), 0644))
320
321
// Use relative path in written paths
322
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
323
_ = writtenPaths.Set(template1, struct{}{}) // relative path
324
325
// Run cleanup - should normalize paths correctly
326
err = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)
327
require.NoError(t, err)
328
329
// Verify template was NOT removed (it was in written paths)
330
require.FileExists(t, template1, "template should be preserved when in written paths")
331
})
332
333
t.Run("handles empty templates directory", func(t *testing.T) {
334
// Create temporary directories
335
tmpDir, err := os.MkdirTemp("", "nuclei-cleanup-empty-dir-test-*")
336
require.NoError(t, err)
337
defer func() {
338
_ = os.RemoveAll(tmpDir)
339
}()
340
341
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
342
require.NoError(t, err)
343
defer func() {
344
_ = os.RemoveAll(cfgdir)
345
}()
346
347
config.DefaultConfig.SetConfigDir(cfgdir)
348
config.DefaultConfig.SetTemplatesDir(tmpDir)
349
350
// Directory exists but is empty (user deleted all templates)
351
require.True(t, fileutil.FolderExists(tmpDir), "templates directory should exist")
352
353
// Written paths from new release
354
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
355
356
// Run cleanup - should handle empty directory gracefully
357
err = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)
358
require.NoError(t, err, "cleanup should handle empty directory without error")
359
360
// Directory should still exist after cleanup
361
require.True(t, fileutil.FolderExists(tmpDir), "templates directory should still exist")
362
})
363
364
t.Run("handles non-existent directory gracefully", func(t *testing.T) {
365
// Use a non-existent directory path
366
nonExistentDir := "/tmp/nuclei-test-non-existent-dir-12345"
367
368
// Ensure it doesn't exist
369
_ = os.RemoveAll(nonExistentDir)
370
require.False(t, fileutil.FolderExists(nonExistentDir), "directory should not exist")
371
372
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
373
374
// Run cleanup - should handle non-existent directory gracefully
375
err := tm.cleanupOrphanedTemplates(nonExistentDir, writtenPaths)
376
require.NoError(t, err, "cleanup should handle non-existent directory without error")
377
})
378
}
379
380
func TestRegenerateTemplateMetadata(t *testing.T) {
381
HideProgressBar = true
382
tm := &TemplateManager{}
383
384
t.Run("creates index and checksum files", func(t *testing.T) {
385
tmpDir, err := os.MkdirTemp("", "nuclei-metadata-test-*")
386
require.NoError(t, err)
387
defer func() {
388
_ = os.RemoveAll(tmpDir)
389
}()
390
391
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
392
require.NoError(t, err)
393
defer func() {
394
_ = os.RemoveAll(cfgdir)
395
}()
396
397
config.DefaultConfig.SetConfigDir(cfgdir)
398
config.DefaultConfig.SetTemplatesDir(tmpDir)
399
400
// Create template files with unique IDs
401
template1 := filepath.Join(tmpDir, "template1.yaml")
402
template2 := filepath.Join(tmpDir, "cves", "template2.yaml")
403
require.NoError(t, os.MkdirAll(filepath.Dir(template2), 0755))
404
405
template1Content := `id: template-one
406
info:
407
name: Template One
408
author: test
409
severity: info`
410
template2Content := `id: template-two
411
info:
412
name: Template Two
413
author: test
414
severity: high`
415
416
require.NoError(t, os.WriteFile(template1, []byte(template1Content), 0644))
417
require.NoError(t, os.WriteFile(template2, []byte(template2Content), 0644))
418
419
// Regenerate metadata
420
err = tm.regenerateTemplateMetadata(tmpDir)
421
require.NoError(t, err)
422
423
// Verify index file was created
424
indexPath := config.DefaultConfig.GetTemplateIndexFilePath()
425
require.FileExists(t, indexPath, "template index file should be created")
426
427
// Verify checksum file was created
428
checksumPath := config.DefaultConfig.GetChecksumFilePath()
429
require.FileExists(t, checksumPath, "checksum file should be created")
430
431
// Verify index contains both templates
432
index, err := config.GetNucleiTemplatesIndex()
433
require.NoError(t, err)
434
require.Contains(t, index, "template-one", "index should contain template-one")
435
require.Contains(t, index, "template-two", "index should contain template-two")
436
437
// Verify checksum file contains both templates
438
checksums, err := tm.getChecksumFromDir(tmpDir)
439
require.NoError(t, err)
440
require.Contains(t, checksums, template1, "checksum should contain template1")
441
require.Contains(t, checksums, template2, "checksum should contain template2")
442
})
443
444
t.Run("excludes deleted templates from index after cleanup", func(t *testing.T) {
445
tmpDir, err := os.MkdirTemp("", "nuclei-metadata-cleanup-test-*")
446
require.NoError(t, err)
447
defer func() {
448
_ = os.RemoveAll(tmpDir)
449
}()
450
451
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
452
require.NoError(t, err)
453
defer func() {
454
_ = os.RemoveAll(cfgdir)
455
}()
456
457
config.DefaultConfig.SetConfigDir(cfgdir)
458
config.DefaultConfig.SetTemplatesDir(tmpDir)
459
460
// Create template files
461
template1 := filepath.Join(tmpDir, "kept-template.yaml")
462
template2 := filepath.Join(tmpDir, "deleted-template.yaml")
463
orphanedTemplate := filepath.Join(tmpDir, "orphaned-template.yaml")
464
465
template1Content := `id: test-template-1
466
info:
467
name: Test Template 1
468
author: test
469
severity: info`
470
template2Content := `id: test-template-2
471
info:
472
name: Test Template 2
473
author: test
474
severity: info`
475
orphanedContent := `id: test-template-orphaned
476
info:
477
name: Test Template Orphaned
478
author: test
479
severity: info`
480
481
require.NoError(t, os.WriteFile(template1, []byte(template1Content), 0644))
482
require.NoError(t, os.WriteFile(template2, []byte(template2Content), 0644))
483
require.NoError(t, os.WriteFile(orphanedTemplate, []byte(orphanedContent), 0644))
484
485
// Create initial index with all templates (simulating state before cleanup)
486
initialIndex := map[string]string{
487
"test-template-1": template1,
488
"test-template-2": template2,
489
"test-template-orphaned": orphanedTemplate,
490
}
491
err = config.DefaultConfig.WriteTemplatesIndex(initialIndex)
492
require.NoError(t, err)
493
494
// Verify initial index contains all templates
495
index, err := config.GetNucleiTemplatesIndex()
496
require.NoError(t, err)
497
require.Contains(t, index, "test-template-orphaned", "initial index should contain orphaned template")
498
499
// Simulate cleanup: remove orphaned template
500
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
501
absTemplate1, _ := filepath.Abs(template1)
502
// Normalize path consistently (same as cleanupOrphanedTemplates does)
503
absTemplate1 = filepath.Clean(absTemplate1)
504
_ = writtenPaths.Set(absTemplate1, struct{}{})
505
506
err = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)
507
require.NoError(t, err)
508
require.NoFileExists(t, orphanedTemplate, "orphaned template should be deleted")
509
require.NoFileExists(t, template2, "template2 should be deleted since it's not in writtenPaths")
510
511
// Regenerate metadata after cleanup
512
err = tm.regenerateTemplateMetadata(tmpDir)
513
require.NoError(t, err)
514
515
// Verify index no longer contains deleted template
516
index, err = config.GetNucleiTemplatesIndex()
517
require.NoError(t, err)
518
require.NotContains(t, index, "test-template-orphaned", "index should not contain deleted orphaned template")
519
require.Contains(t, index, "test-template-1", "index should still contain kept template")
520
require.NotContains(t, index, "test-template-2", "index should not contain template that was deleted but not cleaned")
521
})
522
523
t.Run("excludes deleted templates from checksum after cleanup", func(t *testing.T) {
524
tmpDir, err := os.MkdirTemp("", "nuclei-checksum-cleanup-test-*")
525
require.NoError(t, err)
526
defer func() {
527
_ = os.RemoveAll(tmpDir)
528
}()
529
530
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
531
require.NoError(t, err)
532
defer func() {
533
_ = os.RemoveAll(cfgdir)
534
}()
535
536
config.DefaultConfig.SetConfigDir(cfgdir)
537
config.DefaultConfig.SetTemplatesDir(tmpDir)
538
539
// Create template files
540
keptTemplate := filepath.Join(tmpDir, "kept.yaml")
541
orphanedTemplate := filepath.Join(tmpDir, "orphaned.yaml")
542
543
templateContent := `id: test-template
544
info:
545
name: Test Template
546
author: test
547
severity: info`
548
549
require.NoError(t, os.WriteFile(keptTemplate, []byte(templateContent), 0644))
550
require.NoError(t, os.WriteFile(orphanedTemplate, []byte(templateContent), 0644))
551
552
// Create initial checksum with both templates
553
err = tm.writeChecksumFileInDir(tmpDir)
554
require.NoError(t, err)
555
556
// Verify initial checksum contains both templates
557
initialChecksums, err := tm.getChecksumFromDir(tmpDir)
558
require.NoError(t, err)
559
require.Contains(t, initialChecksums, orphanedTemplate, "initial checksum should contain orphaned template")
560
561
// Simulate cleanup: remove orphaned template
562
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
563
absKept, _ := filepath.Abs(keptTemplate)
564
// Normalize path consistently (same as cleanupOrphanedTemplates does)
565
absKept = filepath.Clean(absKept)
566
_ = writtenPaths.Set(absKept, struct{}{})
567
568
err = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)
569
require.NoError(t, err)
570
require.NoFileExists(t, orphanedTemplate, "orphaned template should be deleted")
571
572
// Regenerate metadata after cleanup
573
err = tm.regenerateTemplateMetadata(tmpDir)
574
require.NoError(t, err)
575
576
// Verify checksum no longer contains deleted template
577
checksums, err := tm.getChecksumFromDir(tmpDir)
578
require.NoError(t, err)
579
require.NotContains(t, checksums, orphanedTemplate, "checksum should not contain deleted orphaned template")
580
require.Contains(t, checksums, keptTemplate, "checksum should still contain kept template")
581
})
582
583
t.Run("cleanup and metadata regeneration integration", func(t *testing.T) {
584
tmpDir, err := os.MkdirTemp("", "nuclei-integration-test-*")
585
require.NoError(t, err)
586
defer func() {
587
_ = os.RemoveAll(tmpDir)
588
}()
589
590
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
591
require.NoError(t, err)
592
defer func() {
593
_ = os.RemoveAll(cfgdir)
594
}()
595
596
config.DefaultConfig.SetConfigDir(cfgdir)
597
config.DefaultConfig.SetTemplatesDir(tmpDir)
598
599
// Create multiple templates
600
template1 := filepath.Join(tmpDir, "cves", "2023", "cve1.yaml")
601
template2 := filepath.Join(tmpDir, "cves", "2023", "cve2.yaml")
602
orphaned1 := filepath.Join(tmpDir, "cves", "2022", "old-cve.yaml")
603
orphaned2 := filepath.Join(tmpDir, "exposures", "old-exposure.yaml")
604
605
require.NoError(t, os.MkdirAll(filepath.Dir(template1), 0755))
606
require.NoError(t, os.MkdirAll(filepath.Dir(orphaned1), 0755))
607
require.NoError(t, os.MkdirAll(filepath.Dir(orphaned2), 0755))
608
609
template1Content := `id: cve1
610
info:
611
name: CVE1
612
author: test
613
severity: info`
614
template2Content := `id: cve2
615
info:
616
name: CVE2
617
author: test
618
severity: info`
619
orphaned1Content := `id: old-cve
620
info:
621
name: Old CVE
622
author: test
623
severity: info`
624
orphaned2Content := `id: old-exposure
625
info:
626
name: Old Exposure
627
author: test
628
severity: info`
629
630
require.NoError(t, os.WriteFile(template1, []byte(template1Content), 0644))
631
require.NoError(t, os.WriteFile(template2, []byte(template2Content), 0644))
632
require.NoError(t, os.WriteFile(orphaned1, []byte(orphaned1Content), 0644))
633
require.NoError(t, os.WriteFile(orphaned2, []byte(orphaned2Content), 0644))
634
635
// Simulate written paths from new release
636
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
637
absTemplate1, _ := filepath.Abs(template1)
638
absTemplate2, _ := filepath.Abs(template2)
639
// Normalize paths consistently (same as cleanupOrphanedTemplates does)
640
absTemplate1 = filepath.Clean(absTemplate1)
641
absTemplate2 = filepath.Clean(absTemplate2)
642
_ = writtenPaths.Set(absTemplate1, struct{}{})
643
_ = writtenPaths.Set(absTemplate2, struct{}{})
644
645
// Perform cleanup
646
err = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)
647
require.NoError(t, err)
648
require.NoFileExists(t, orphaned1, "orphaned template 1 should be deleted")
649
require.NoFileExists(t, orphaned2, "orphaned template 2 should be deleted")
650
651
// Regenerate metadata (simulating what updateTemplatesAt does)
652
err = tm.regenerateTemplateMetadata(tmpDir)
653
require.NoError(t, err)
654
655
// Verify index only contains kept templates
656
index, err := config.GetNucleiTemplatesIndex()
657
require.NoError(t, err)
658
require.Contains(t, index, "cve1", "index should contain kept template cve1")
659
require.Contains(t, index, "cve2", "index should contain kept template cve2")
660
require.NotContains(t, index, "old-cve", "index should not contain deleted template")
661
require.NotContains(t, index, "old-exposure", "index should not contain deleted template")
662
663
// Verify checksum only contains kept templates
664
checksums, err := tm.getChecksumFromDir(tmpDir)
665
require.NoError(t, err)
666
require.Contains(t, checksums, template1, "checksum should contain kept template1")
667
require.Contains(t, checksums, template2, "checksum should contain kept template2")
668
require.NotContains(t, checksums, orphaned1, "checksum should not contain deleted template")
669
require.NotContains(t, checksums, orphaned2, "checksum should not contain deleted template")
670
671
// Verify empty directories are purged
672
require.False(t, fileutil.FolderExists(filepath.Dir(orphaned1)), "empty directory should be purged")
673
require.False(t, fileutil.FolderExists(filepath.Dir(orphaned2)), "empty directory should be purged")
674
})
675
676
t.Run("handles empty templates directory", func(t *testing.T) {
677
tmpDir, err := os.MkdirTemp("", "nuclei-metadata-empty-test-*")
678
require.NoError(t, err)
679
defer func() {
680
_ = os.RemoveAll(tmpDir)
681
}()
682
683
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
684
require.NoError(t, err)
685
defer func() {
686
_ = os.RemoveAll(cfgdir)
687
}()
688
689
config.DefaultConfig.SetConfigDir(cfgdir)
690
config.DefaultConfig.SetTemplatesDir(tmpDir)
691
692
// Ensure templates directory exists (even if empty)
693
require.NoError(t, os.MkdirAll(tmpDir, 0755))
694
695
// Regenerate metadata on empty directory
696
err = tm.regenerateTemplateMetadata(tmpDir)
697
require.NoError(t, err, "should handle empty directory without error")
698
699
// Index should exist but be empty or minimal
700
indexPath := config.DefaultConfig.GetTemplateIndexFilePath()
701
if fileutil.FileExists(indexPath) {
702
index, err := config.GetNucleiTemplatesIndex()
703
require.NoError(t, err)
704
require.Empty(t, index, "index should be empty for empty templates directory")
705
}
706
})
707
708
t.Run("purges empty directories", func(t *testing.T) {
709
tmpDir, err := os.MkdirTemp("", "nuclei-metadata-purge-test-*")
710
require.NoError(t, err)
711
defer func() {
712
_ = os.RemoveAll(tmpDir)
713
}()
714
715
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
716
require.NoError(t, err)
717
defer func() {
718
_ = os.RemoveAll(cfgdir)
719
}()
720
721
config.DefaultConfig.SetConfigDir(cfgdir)
722
config.DefaultConfig.SetTemplatesDir(tmpDir)
723
724
// Create empty nested directories
725
emptyDir1 := filepath.Join(tmpDir, "empty1", "nested", "deep")
726
emptyDir2 := filepath.Join(tmpDir, "empty2")
727
require.NoError(t, os.MkdirAll(emptyDir1, 0755))
728
require.NoError(t, os.MkdirAll(emptyDir2, 0755))
729
730
// Create one template in a different directory
731
templateFile := filepath.Join(tmpDir, "kept", "template.yaml")
732
require.NoError(t, os.MkdirAll(filepath.Dir(templateFile), 0755))
733
require.NoError(t, os.WriteFile(templateFile, []byte(`id: kept-template
734
info:
735
name: Kept
736
author: test
737
severity: info`), 0644))
738
739
// Regenerate metadata (should purge empty directories)
740
err = tm.regenerateTemplateMetadata(tmpDir)
741
require.NoError(t, err)
742
743
// Verify empty directories were purged
744
require.False(t, fileutil.FolderExists(emptyDir1), "empty nested directory should be purged")
745
require.False(t, fileutil.FolderExists(emptyDir2), "empty directory should be purged")
746
require.True(t, fileutil.FolderExists(filepath.Dir(templateFile)), "directory with template should not be purged")
747
})
748
}
749
750