1package system
2
3import (
4 "context"
5 "errors"
6 "strings"
7 "testing"
8
9 "github.com/bnema/archup/internal/domain/ports/mocks"
10 "go.uber.org/mock/gomock"
11)
12
13// SystemConfig Tests
14
15func TestNewSystemConfig_Valid(t *testing.T) {
16 config, err := NewSystemConfig("myhost", "UTC", "en_US.UTF-8", "us")
17
18 if err != nil {
19 t.Fatalf("expected no error, got %v", err)
20 }
21
22 if config.Hostname() != "myhost" {
23 t.Errorf("expected hostname 'myhost', got '%s'", config.Hostname())
24 }
25
26 if config.Timezone() != "UTC" {
27 t.Errorf("expected timezone 'UTC', got '%s'", config.Timezone())
28 }
29
30 if config.Locale() != "en_US.UTF-8" {
31 t.Errorf("expected locale 'en_US.UTF-8', got '%s'", config.Locale())
32 }
33
34 if config.Keymap() != "us" {
35 t.Errorf("expected keymap 'us', got '%s'", config.Keymap())
36 }
37}
38
39func TestNewSystemConfig_InvalidHostname(t *testing.T) {
40 tests := []struct {
41 name string
42 hostname string
43 shouldErr bool
44 }{
45 {"empty hostname", "", true},
46 {"starts with hyphen", "-host", true},
47 {"ends with hyphen", "host-", true},
48 {"too long", "a" + string(make([]byte, 100)), true},
49 {"valid hostname", "myhost", false},
50 {"with hyphen", "my-host", false},
51 {"with digits", "host123", false},
52 }
53
54 for _, tt := range tests {
55 t.Run(tt.name, func(t *testing.T) {
56 _, err := NewSystemConfig(tt.hostname, "UTC", "en_US.UTF-8", "us")
57 if (err != nil) != tt.shouldErr {
58 t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
59 }
60 })
61 }
62}
63
64func TestNewSystemConfig_InvalidLocale(t *testing.T) {
65 tests := []struct {
66 name string
67 locale string
68 shouldErr bool
69 }{
70 {"valid locale", "en_US.UTF-8", false},
71 {"empty locale allowed", "", false},
72 {"C locale", "C", false},
73 {"C.UTF-8", "C.UTF-8", false},
74 {"too long", "a" + string(make([]byte, 100)), true},
75 }
76
77 for _, tt := range tests {
78 t.Run(tt.name, func(t *testing.T) {
79 _, err := NewSystemConfig("host", "UTC", tt.locale, "us")
80 if (err != nil) != tt.shouldErr {
81 t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
82 }
83 })
84 }
85}
86
87func TestNewSystemConfig_InvalidKeymap(t *testing.T) {
88 tests := []struct {
89 name string
90 keymap string
91 shouldErr bool
92 }{
93 {"valid simple keymap", "us", false},
94 {"with variant", "de-nodeadkeys", false},
95 {"empty keymap", "", true},
96 {"with uppercase", "US", true},
97 {"with special chars", "us!", true},
98 {"too long", "a" + string(make([]byte, 100)), true},
99 }
100
101 for _, tt := range tests {
102 t.Run(tt.name, func(t *testing.T) {
103 _, err := NewSystemConfig("host", "UTC", "en_US.UTF-8", tt.keymap)
104 if (err != nil) != tt.shouldErr {
105 t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
106 }
107 })
108 }
109}
110
111func TestSystemConfig_Equals(t *testing.T) {
112 config1, _ := NewSystemConfig("host1", "UTC", "en_US.UTF-8", "us")
113 config2, _ := NewSystemConfig("host1", "UTC", "en_US.UTF-8", "us")
114 config3, _ := NewSystemConfig("host2", "UTC", "en_US.UTF-8", "us")
115
116 if !config1.Equals(config2) {
117 t.Error("expected equal configs")
118 }
119
120 if config1.Equals(config3) {
121 t.Error("expected different configs")
122 }
123
124 if config1.Equals(nil) {
125 t.Error("expected nil to not be equal")
126 }
127}
128
129func TestValidateHostname(t *testing.T) {
130 tests := []struct {
131 name string
132 hostname string
133 shouldErr bool
134 }{
135 {"valid", "myhost", false},
136 {"with digits", "host123", false},
137 {"with hyphen", "my-host", false},
138 {"empty", "", true},
139 {"starts with hyphen", "-host", true},
140 {"ends with hyphen", "host-", true},
141 {"contains underscore", "my_host", true},
142 {"contains space", "my host", true},
143 }
144
145 for _, tt := range tests {
146 t.Run(tt.name, func(t *testing.T) {
147 err := ValidateHostname(tt.hostname)
148 if (err != nil) != tt.shouldErr {
149 t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
150 }
151 })
152 }
153}
154
155func TestValidateTimezone(t *testing.T) {
156 tests := []struct {
157 name string
158 timezone string
159 shouldErr bool
160 }{
161 {"UTC", "UTC", false},
162 {"valid timezone", "America/New_York", false},
163 {"empty allowed", "", false},
164 {"contains ..", "UTC..", true},
165 {"starts with /", "/UTC", true},
166 {"too long", "a" + string(make([]byte, 100)), true},
167 }
168
169 for _, tt := range tests {
170 t.Run(tt.name, func(t *testing.T) {
171 err := ValidateTimezone(tt.timezone)
172 if (err != nil) != tt.shouldErr {
173 t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
174 }
175 })
176 }
177}
178
179func TestValidateLocale(t *testing.T) {
180 tests := []struct {
181 name string
182 locale string
183 shouldErr bool
184 }{
185 {"valid UTF-8", "en_US.UTF-8", false},
186 {"C locale", "C", false},
187 {"empty allowed", "", false},
188 {"ISO variant", "en_US.ISO8859-1", false},
189 {"too long", "a" + string(make([]byte, 100)), true},
190 }
191
192 for _, tt := range tests {
193 t.Run(tt.name, func(t *testing.T) {
194 err := ValidateLocale(tt.locale)
195 if (err != nil) != tt.shouldErr {
196 t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
197 }
198 })
199 }
200}
201
202func TestValidateKeymap(t *testing.T) {
203 tests := []struct {
204 name string
205 keymap string
206 shouldErr bool
207 }{
208 {"us", "us", false},
209 {"de", "de", false},
210 {"with variant", "de-nodeadkeys", false},
211 {"empty", "", true},
212 {"uppercase", "US", true},
213 {"with special chars", "us!", true},
214 }
215
216 for _, tt := range tests {
217 t.Run(tt.name, func(t *testing.T) {
218 err := ValidateKeymap(tt.keymap)
219 if (err != nil) != tt.shouldErr {
220 t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
221 }
222 })
223 }
224}
225
226// CPUInfo Tests
227
228func TestNewCPUInfo_Valid(t *testing.T) {
229 cpuInfo, err := NewCPUInfo(CPUVendorIntel, "Intel(R) Core(TM) i7-9700K", MicrocodeIntel)
230
231 if err != nil {
232 t.Fatalf("expected no error, got %v", err)
233 }
234
235 if cpuInfo.Vendor() != CPUVendorIntel {
236 t.Errorf("expected Intel vendor")
237 }
238
239 if !cpuInfo.RequiresMicrocode() {
240 t.Error("expected to require microcode")
241 }
242}
243
244func TestNewCPUInfo_InvalidModel(t *testing.T) {
245 tests := []struct {
246 name string
247 model string
248 shouldErr bool
249 }{
250 {"valid", "Intel Core i7", false},
251 {"empty", "", true},
252 {"too long", "a" + string(make([]byte, 200)), true},
253 }
254
255 for _, tt := range tests {
256 t.Run(tt.name, func(t *testing.T) {
257 _, err := NewCPUInfo(CPUVendorIntel, tt.model, MicrocodeIntel)
258 if (err != nil) != tt.shouldErr {
259 t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
260 }
261 })
262 }
263}
264
265func TestNewCPUInfo_MicrocodeVendorMismatch(t *testing.T) {
266 tests := []struct {
267 name string
268 vendor CPUVendor
269 microcode Microcode
270 shouldErr bool
271 }{
272 {"Intel with Intel ucode", CPUVendorIntel, MicrocodeIntel, false},
273 {"Intel with AMD ucode", CPUVendorIntel, MicrocodeAMD, true},
274 {"AMD with AMD ucode", CPUVendorAMD, MicrocodeAMD, false},
275 {"AMD with Intel ucode", CPUVendorAMD, MicrocodeIntel, true},
276 {"Intel with no ucode", CPUVendorIntel, MicrocodeNone, false},
277 }
278
279 for _, tt := range tests {
280 t.Run(tt.name, func(t *testing.T) {
281 _, err := NewCPUInfo(tt.vendor, "Model", tt.microcode)
282 if (err != nil) != tt.shouldErr {
283 t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
284 }
285 })
286 }
287}
288
289func TestCPUInfo_Equals(t *testing.T) {
290 cpu1, _ := NewCPUInfo(CPUVendorIntel, "i7", MicrocodeIntel)
291 cpu2, _ := NewCPUInfo(CPUVendorIntel, "i7", MicrocodeIntel)
292 cpu3, _ := NewCPUInfo(CPUVendorAMD, "Ryzen", MicrocodeAMD)
293
294 if !cpu1.Equals(cpu2) {
295 t.Error("expected equal CPUInfo")
296 }
297
298 if cpu1.Equals(cpu3) {
299 t.Error("expected different CPUInfo")
300 }
301
302 if cpu1.Equals(nil) {
303 t.Error("expected nil to not be equal")
304 }
305}
306
307func TestCPUVendor_String(t *testing.T) {
308 tests := []struct {
309 vendor CPUVendor
310 want string
311 }{
312 {CPUVendorIntel, "Intel"},
313 {CPUVendorAMD, "AMD"},
314 {CPUVendorARM, "ARM"},
315 {CPUVendorUnknown, "Unknown"},
316 }
317
318 for _, tt := range tests {
319 if got := tt.vendor.String(); got != tt.want {
320 t.Errorf("expected %s, got %s", tt.want, got)
321 }
322 }
323}
324
325func TestMicrocode_String(t *testing.T) {
326 tests := []struct {
327 microcode Microcode
328 want string
329 }{
330 {MicrocodeIntel, "intel-ucode"},
331 {MicrocodeAMD, "amd-ucode"},
332 {MicrocodeNone, ""},
333 }
334
335 for _, tt := range tests {
336 if got := tt.microcode.String(); got != tt.want {
337 t.Errorf("expected %s, got %s", tt.want, got)
338 }
339 }
340}
341
342// ValidationRules Tests
343
344func TestSanitizeHostname(t *testing.T) {
345 rules := NewSystemValidationRules()
346
347 tests := []struct {
348 input string
349 expected string
350 }{
351 {"MyHost", "myhost"},
352 {"my-host", "my-host"},
353 {"my_host", "my-host"},
354 {"-myhost", "myhost"},
355 {"myhost-", "myhost"},
356 {"my--host", "my-host"},
357 {"my__host", "my-host"},
358 {"MyHost123", "myhost123"},
359 {strings.Repeat("a", 100), strings.Repeat("a", 63)},
360 }
361
362 for _, tt := range tests {
363 t.Run(tt.input[:min(10, len(tt.input))], func(t *testing.T) {
364 got := rules.SanitizeHostname(tt.input)
365 if got != tt.expected {
366 t.Errorf("expected '%s', got '%s'", tt.expected, got)
367 }
368
369 // Verify result passes validation
370 if err := ValidateHostname(got); err != nil {
371 t.Errorf("sanitized hostname failed validation: %v", err)
372 }
373 })
374 }
375}
376
377func TestValidateCombination(t *testing.T) {
378 rules := NewSystemValidationRules()
379
380 config, _ := NewSystemConfig("host", "UTC", "en_US.UTF-8", "us")
381 cpu, _ := NewCPUInfo(CPUVendorIntel, "i7", MicrocodeIntel)
382
383 err := rules.ValidateCombination(config, cpu)
384 if err != nil {
385 t.Errorf("expected no error, got %v", err)
386 }
387
388 // Test nil checks
389 if err := rules.ValidateCombination(nil, cpu); err == nil {
390 t.Error("expected error for nil config")
391 }
392
393 if err := rules.ValidateCombination(config, nil); err == nil {
394 t.Error("expected error for nil cpu")
395 }
396}
397
398func TestSuggestedLocaleForTimezone(t *testing.T) {
399 rules := NewSystemValidationRules()
400
401 tests := []struct {
402 timezone string
403 expected string
404 }{
405 {"UTC", "en_US.UTF-8"},
406 {"America/New_York", "en_US.UTF-8"},
407 {"Europe/London", "en_GB.UTF-8"},
408 {"Europe/Paris", "fr_FR.UTF-8"},
409 {"Unknown/Zone", ""},
410 }
411
412 for _, tt := range tests {
413 t.Run(tt.timezone, func(t *testing.T) {
414 got := rules.SuggestedLocaleForTimezone(tt.timezone)
415 if got != tt.expected {
416 t.Errorf("expected '%s', got '%s'", tt.expected, got)
417 }
418 })
419 }
420}
421
422func TestSuggestedKeymapForLocale(t *testing.T) {
423 rules := NewSystemValidationRules()
424
425 tests := []struct {
426 locale string
427 expected string
428 }{
429 {"en_US.UTF-8", "us"},
430 {"en_GB.UTF-8", "gb"},
431 {"de_DE.UTF-8", "de"},
432 {"fr_FR.UTF-8", "fr"},
433 {"unknown", ""},
434 }
435
436 for _, tt := range tests {
437 t.Run(tt.locale, func(t *testing.T) {
438 got := rules.SuggestedKeymapForLocale(tt.locale)
439 if got != tt.expected {
440 t.Errorf("expected '%s', got '%s'", tt.expected, got)
441 }
442 })
443 }
444}
445
446// New validation function tests
447
448func TestValidateArchLinux(t *testing.T) {
449 ctrl := gomock.NewController(t)
450 defer ctrl.Finish()
451
452 rules := NewSystemValidationRules()
453 ctx := context.Background()
454
455 tests := []struct {
456 name string
457 fileExists bool
458 shouldErr bool
459 }{
460 {"arch-release exists", true, false},
461 {"arch-release missing", false, true},
462 }
463
464 for _, tt := range tests {
465 t.Run(tt.name, func(t *testing.T) {
466 mockFS := mocks.NewMockFileSystem(ctrl)
467 mockFS.EXPECT().Exists("/etc/arch-release").Return(tt.fileExists, nil)
468
469 err := rules.ValidateArchLinux(ctx, mockFS)
470 if (err != nil) != tt.shouldErr {
471 t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
472 }
473 })
474 }
475}
476
477func TestValidateNotDerivative(t *testing.T) {
478 rules := NewSystemValidationRules()
479 ctx := context.Background()
480
481 tests := []struct {
482 name string
483 setupMock func(*mocks.MockFileSystem)
484 shouldErr bool
485 errMsg string
486 }{
487 {
488 name: "vanilla arch",
489 setupMock: func(mockFS *mocks.MockFileSystem) {
490 mockFS.EXPECT().Exists("/etc/cachyos-release").Return(false, nil)
491 mockFS.EXPECT().Exists("/etc/eos-release").Return(false, nil)
492 mockFS.EXPECT().Exists("/etc/garuda-release").Return(false, nil)
493 mockFS.EXPECT().Exists("/etc/manjaro-release").Return(false, nil)
494 },
495 shouldErr: false,
496 },
497 {
498 name: "cachyos detected",
499 setupMock: func(mockFS *mocks.MockFileSystem) {
500 // Map iteration order is undefined, so any may be checked first
501 mockFS.EXPECT().Exists("/etc/cachyos-release").Return(true, nil).AnyTimes()
502 mockFS.EXPECT().Exists("/etc/eos-release").Return(false, nil).AnyTimes()
503 mockFS.EXPECT().Exists("/etc/garuda-release").Return(false, nil).AnyTimes()
504 mockFS.EXPECT().Exists("/etc/manjaro-release").Return(false, nil).AnyTimes()
505 },
506 shouldErr: true,
507 errMsg: "CachyOS",
508 },
509 {
510 name: "endeavouros detected",
511 setupMock: func(mockFS *mocks.MockFileSystem) {
512 mockFS.EXPECT().Exists("/etc/cachyos-release").Return(false, nil).AnyTimes()
513 mockFS.EXPECT().Exists("/etc/eos-release").Return(true, nil).AnyTimes()
514 mockFS.EXPECT().Exists("/etc/garuda-release").Return(false, nil).AnyTimes()
515 mockFS.EXPECT().Exists("/etc/manjaro-release").Return(false, nil).AnyTimes()
516 },
517 shouldErr: true,
518 errMsg: "EndeavourOS",
519 },
520 {
521 name: "garuda detected",
522 setupMock: func(mockFS *mocks.MockFileSystem) {
523 mockFS.EXPECT().Exists("/etc/cachyos-release").Return(false, nil).AnyTimes()
524 mockFS.EXPECT().Exists("/etc/eos-release").Return(false, nil).AnyTimes()
525 mockFS.EXPECT().Exists("/etc/garuda-release").Return(true, nil).AnyTimes()
526 mockFS.EXPECT().Exists("/etc/manjaro-release").Return(false, nil).AnyTimes()
527 },
528 shouldErr: true,
529 errMsg: "Garuda",
530 },
531 {
532 name: "manjaro detected",
533 setupMock: func(mockFS *mocks.MockFileSystem) {
534 mockFS.EXPECT().Exists("/etc/cachyos-release").Return(false, nil).AnyTimes()
535 mockFS.EXPECT().Exists("/etc/eos-release").Return(false, nil).AnyTimes()
536 mockFS.EXPECT().Exists("/etc/garuda-release").Return(false, nil).AnyTimes()
537 mockFS.EXPECT().Exists("/etc/manjaro-release").Return(true, nil).AnyTimes()
538 },
539 shouldErr: true,
540 errMsg: "Manjaro",
541 },
542 }
543
544 for _, tt := range tests {
545 t.Run(tt.name, func(t *testing.T) {
546 ctrl := gomock.NewController(t)
547 defer ctrl.Finish()
548
549 mockFS := mocks.NewMockFileSystem(ctrl)
550 tt.setupMock(mockFS)
551
552 err := rules.ValidateNotDerivative(ctx, mockFS)
553 if (err != nil) != tt.shouldErr {
554 t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
555 }
556 if tt.shouldErr && err != nil && !strings.Contains(err.Error(), tt.errMsg) {
557 t.Errorf("expected error to contain '%s', got '%s'", tt.errMsg, err.Error())
558 }
559 })
560 }
561}
562
563func TestValidateArchitecture(t *testing.T) {
564 rules := NewSystemValidationRules()
565
566 tests := []struct {
567 name string
568 arch string
569 shouldErr bool
570 }{
571 {"x86_64", "x86_64", false},
572 {"empty", "", true},
573 {"aarch64", "aarch64", true},
574 {"i686", "i686", true},
575 }
576
577 for _, tt := range tests {
578 t.Run(tt.name, func(t *testing.T) {
579 err := rules.ValidateArchitecture(tt.arch)
580 if (err != nil) != tt.shouldErr {
581 t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
582 }
583 })
584 }
585}
586
587func TestValidateUEFIBoot(t *testing.T) {
588 ctrl := gomock.NewController(t)
589 defer ctrl.Finish()
590
591 rules := NewSystemValidationRules()
592 ctx := context.Background()
593
594 tests := []struct {
595 name string
596 efivarsExists bool
597 shouldErr bool
598 }{
599 {"efivars exists", true, false},
600 {"efivars missing", false, true},
601 }
602
603 for _, tt := range tests {
604 t.Run(tt.name, func(t *testing.T) {
605 mockFS := mocks.NewMockFileSystem(ctrl)
606 mockFS.EXPECT().Exists("/sys/firmware/efi/efivars").Return(tt.efivarsExists, nil)
607
608 err := rules.ValidateUEFIBoot(ctx, mockFS)
609 if (err != nil) != tt.shouldErr {
610 t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
611 }
612 })
613 }
614}
615
616func TestDetectSecureBoot(t *testing.T) {
617 ctrl := gomock.NewController(t)
618 defer ctrl.Finish()
619
620 rules := NewSystemValidationRules()
621 ctx := context.Background()
622
623 tests := []struct {
624 name string
625 output []byte
626 cmdErr error
627 expected bool
628 }{
629 {
630 name: "secure boot enabled",
631 output: []byte("System:\n Secure Boot: enabled\n"),
632 expected: true,
633 },
634 {
635 name: "secure boot disabled",
636 output: []byte("System:\n Secure Boot: disabled\n"),
637 expected: false,
638 },
639 {
640 name: "bootctl fails",
641 cmdErr: errors.New("bootctl not found"),
642 expected: false,
643 },
644 }
645
646 for _, tt := range tests {
647 t.Run(tt.name, func(t *testing.T) {
648 mockExec := mocks.NewMockCommandExecutor(ctrl)
649 if tt.cmdErr != nil {
650 mockExec.EXPECT().Execute(ctx, "bootctl", "status").Return(nil, tt.cmdErr)
651 } else {
652 mockExec.EXPECT().Execute(ctx, "bootctl", "status").Return(tt.output, nil)
653 }
654
655 enabled, err := rules.DetectSecureBoot(ctx, mockExec)
656 if err != nil {
657 t.Errorf("unexpected error: %v", err)
658 }
659 if enabled != tt.expected {
660 t.Errorf("expected %v, got %v", tt.expected, enabled)
661 }
662 })
663 }
664}
665
666func TestValidateSecureBootDisabled(t *testing.T) {
667 ctrl := gomock.NewController(t)
668 defer ctrl.Finish()
669
670 rules := NewSystemValidationRules()
671 ctx := context.Background()
672
673 tests := []struct {
674 name string
675 output []byte
676 shouldErr bool
677 }{
678 {
679 name: "secure boot disabled",
680 output: []byte("System:\n Secure Boot: disabled\n"),
681 shouldErr: false,
682 },
683 {
684 name: "secure boot enabled",
685 output: []byte("System:\n Secure Boot: enabled\n"),
686 shouldErr: true,
687 },
688 }
689
690 for _, tt := range tests {
691 t.Run(tt.name, func(t *testing.T) {
692 mockExec := mocks.NewMockCommandExecutor(ctrl)
693 mockExec.EXPECT().Execute(ctx, "bootctl", "status").Return(tt.output, nil)
694
695 err := rules.ValidateSecureBootDisabled(ctx, mockExec)
696 if (err != nil) != tt.shouldErr {
697 t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
698 }
699 })
700 }
701}
702
703func TestDistributionType_String(t *testing.T) {
704 tests := []struct {
705 distro DistributionType
706 want string
707 }{
708 {DistributionArch, "Arch Linux"},
709 {DistributionCachyOS, "CachyOS"},
710 {DistributionEndeavourOS, "EndeavourOS"},
711 {DistributionGaruda, "Garuda Linux"},
712 {DistributionManjaro, "Manjaro Linux"},
713 {DistributionUnknown, "Unknown"},
714 }
715
716 for _, tt := range tests {
717 if got := tt.distro.String(); got != tt.want {
718 t.Errorf("expected %s, got %s", tt.want, got)
719 }
720 }
721}
722
723func TestDetectDistribution(t *testing.T) {
724 ctrl := gomock.NewController(t)
725 defer ctrl.Finish()
726
727 ctx := context.Background()
728
729 tests := []struct {
730 name string
731 setupMock func(*mocks.MockFileSystem)
732 expected DistributionType
733 shouldErr bool
734 }{
735 {
736 name: "vanilla arch",
737 setupMock: func(mockFS *mocks.MockFileSystem) {
738 mockFS.EXPECT().Exists("/etc/arch-release").Return(true, nil)
739 mockFS.EXPECT().Exists("/etc/cachyos-release").Return(false, nil)
740 mockFS.EXPECT().Exists("/etc/eos-release").Return(false, nil)
741 mockFS.EXPECT().Exists("/etc/garuda-release").Return(false, nil)
742 mockFS.EXPECT().Exists("/etc/manjaro-release").Return(false, nil)
743 },
744 expected: DistributionArch,
745 },
746 {
747 name: "cachyos",
748 setupMock: func(mockFS *mocks.MockFileSystem) {
749 mockFS.EXPECT().Exists("/etc/arch-release").Return(true, nil)
750 mockFS.EXPECT().Exists("/etc/cachyos-release").Return(true, nil)
751 },
752 expected: DistributionCachyOS,
753 },
754 {
755 name: "endeavouros",
756 setupMock: func(mockFS *mocks.MockFileSystem) {
757 mockFS.EXPECT().Exists("/etc/arch-release").Return(true, nil)
758 mockFS.EXPECT().Exists("/etc/cachyos-release").Return(false, nil)
759 mockFS.EXPECT().Exists("/etc/eos-release").Return(true, nil)
760 },
761 expected: DistributionEndeavourOS,
762 },
763 {
764 name: "garuda",
765 setupMock: func(mockFS *mocks.MockFileSystem) {
766 mockFS.EXPECT().Exists("/etc/arch-release").Return(true, nil)
767 mockFS.EXPECT().Exists("/etc/cachyos-release").Return(false, nil)
768 mockFS.EXPECT().Exists("/etc/eos-release").Return(false, nil)
769 mockFS.EXPECT().Exists("/etc/garuda-release").Return(true, nil)
770 },
771 expected: DistributionGaruda,
772 },
773 {
774 name: "manjaro",
775 setupMock: func(mockFS *mocks.MockFileSystem) {
776 mockFS.EXPECT().Exists("/etc/arch-release").Return(true, nil)
777 mockFS.EXPECT().Exists("/etc/cachyos-release").Return(false, nil)
778 mockFS.EXPECT().Exists("/etc/eos-release").Return(false, nil)
779 mockFS.EXPECT().Exists("/etc/garuda-release").Return(false, nil)
780 mockFS.EXPECT().Exists("/etc/manjaro-release").Return(true, nil)
781 },
782 expected: DistributionManjaro,
783 },
784 {
785 name: "not arch",
786 setupMock: func(mockFS *mocks.MockFileSystem) {
787 mockFS.EXPECT().Exists("/etc/arch-release").Return(false, nil)
788 },
789 expected: DistributionUnknown,
790 shouldErr: true,
791 },
792 }
793
794 for _, tt := range tests {
795 t.Run(tt.name, func(t *testing.T) {
796 mockFS := mocks.NewMockFileSystem(ctrl)
797 tt.setupMock(mockFS)
798
799 distro, err := DetectDistribution(ctx, mockFS)
800 if (err != nil) != tt.shouldErr {
801 t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
802 }
803 if distro != tt.expected {
804 t.Errorf("expected %v, got %v", tt.expected, distro)
805 }
806 })
807 }
808}
809
810// Helper function
811func min(a, b int) int {
812 if a < b {
813 return a
814 }
815 return b
816}