1package installation
2
3import (
4 "context"
5 "testing"
6)
7
8func TestNewInstallation_Valid(t *testing.T) {
9 inst, err := NewInstallation(
10 "myhost",
11 "user",
12 "/dev/sda",
13 "none",
14 )
15
16 if err != nil {
17 t.Fatalf("expected no error, got %v", err)
18 }
19
20 if inst.ID() == "" {
21 t.Error("expected non-empty ID")
22 }
23
24 if inst.Hostname() != "myhost" {
25 t.Errorf("expected hostname 'myhost', got '%s'", inst.Hostname())
26 }
27
28 if inst.Username() != "user" {
29 t.Errorf("expected username 'user', got '%s'", inst.Username())
30 }
31
32 if inst.TargetDisk() != "/dev/sda" {
33 t.Errorf("expected target disk '/dev/sda', got '%s'", inst.TargetDisk())
34 }
35
36 if inst.EncryptionType() != "none" {
37 t.Errorf("expected encryption 'none', got '%s'", inst.EncryptionType())
38 }
39
40 if inst.State() != StateNotStarted {
41 t.Errorf("expected initial state StateNotStarted, got %v", inst.State())
42 }
43
44 if inst.IsStarted() {
45 t.Error("expected IsStarted to be false")
46 }
47
48 if inst.IsCompleted() {
49 t.Error("expected IsCompleted to be false")
50 }
51}
52
53func TestNewInstallation_InvalidHostname(t *testing.T) {
54 tests := []struct {
55 name string
56 hostname string
57 wantErr bool
58 }{
59 {"empty hostname", "", true},
60 {"too long hostname", "a" + string(make([]byte, 100)), true},
61 {"valid hostname", "myhost", false},
62 }
63
64 for _, tt := range tests {
65 t.Run(tt.name, func(t *testing.T) {
66 _, err := NewInstallation(tt.hostname, "user", "/dev/sda", "none")
67 if (err != nil) != tt.wantErr {
68 t.Errorf("got error %v, want error=%v", err, tt.wantErr)
69 }
70 })
71 }
72}
73
74func TestNewInstallation_InvalidUsername(t *testing.T) {
75 tests := []struct {
76 name string
77 username string
78 wantErr bool
79 }{
80 {"empty username", "", true},
81 {"too long username", "a" + string(make([]byte, 100)), true},
82 {"valid username", "user", false},
83 }
84
85 for _, tt := range tests {
86 t.Run(tt.name, func(t *testing.T) {
87 _, err := NewInstallation("host", tt.username, "/dev/sda", "none")
88 if (err != nil) != tt.wantErr {
89 t.Errorf("got error %v, want error=%v", err, tt.wantErr)
90 }
91 })
92 }
93}
94
95func TestNewInstallation_InvalidEncryption(t *testing.T) {
96 tests := []struct {
97 name string
98 encryption string
99 wantErr bool
100 }{
101 {"invalid encryption", "invalid", true},
102 {"none", "none", false},
103 {"luks", "luks", false},
104 {"luks-lvm", "luks-lvm", false},
105 }
106
107 for _, tt := range tests {
108 t.Run(tt.name, func(t *testing.T) {
109 _, err := NewInstallation("host", "user", "/dev/sda", tt.encryption)
110 if (err != nil) != tt.wantErr {
111 t.Errorf("got error %v, want error=%v", err, tt.wantErr)
112 }
113 })
114 }
115}
116
117func TestStart_Success(t *testing.T) {
118 inst, _ := NewInstallation("host", "user", "/dev/sda", "none")
119 ctx := context.Background()
120
121 if err := inst.Start(ctx); err != nil {
122 t.Fatalf("expected no error, got %v", err)
123 }
124
125 if inst.State() != StatePreflightChecks {
126 t.Errorf("expected state StatePreflightChecks, got %v", inst.State())
127 }
128
129 if inst.StartedAt() == nil {
130 t.Error("expected StartedAt to be set")
131 }
132
133 if inst.IsStarted() != true {
134 t.Error("expected IsStarted to be true")
135 }
136
137 // Check event was recorded
138 events := inst.UncommittedEvents()
139 if len(events) != 1 {
140 t.Errorf("expected 1 event, got %d", len(events))
141 }
142
143 if events[0].EventType() != "InstallationStarted" {
144 t.Errorf("expected InstallationStarted event, got %s", events[0].EventType())
145 }
146}
147
148func TestStart_AlreadyStarted(t *testing.T) {
149 inst, _ := NewInstallation("host", "user", "/dev/sda", "none")
150 ctx := context.Background()
151
152 if err := inst.Start(ctx); err != nil {
153 t.Fatalf("expected no error, got %v", err)
154 }
155
156 // Try to start again
157 err := inst.Start(ctx)
158 if err != ErrInstallationAlreadyStarted {
159 t.Errorf("expected ErrInstallationAlreadyStarted, got %v", err)
160 }
161}
162
163func TestStateTransition_ValidSequence(t *testing.T) {
164 inst, _ := NewInstallation("host", "user", "/dev/sda", "none")
165 ctx := context.Background()
166
167 // Start installation
168 if err := inst.Start(ctx); err != nil {
169 t.Fatalf("expected no error, got %v", err)
170 }
171 inst.events = []DomainEvent{} // Clear events
172
173 // Test valid sequence
174 phases := []State{
175 StatePreflightChecks,
176 StateDiskPartitioning,
177 StateBaseInstallation,
178 StateSystemConfiguration,
179 StateBootloaderSetup,
180 StateRepositorySetup,
181 StatePostInstallation,
182 StateCompleted,
183 }
184
185 for i := 1; i < len(phases); i++ {
186 if err := inst.TransitionToNextPhase(); err != nil {
187 t.Errorf("phase %d: unexpected error: %v", i, err)
188 }
189
190 if inst.State() != phases[i] {
191 t.Errorf("expected state %v, got %v", phases[i], inst.State())
192 }
193 }
194
195 if !inst.IsCompleted() {
196 t.Error("expected installation to be completed")
197 }
198
199 if !inst.IsSuccessful() {
200 t.Error("expected installation to be successful")
201 }
202}
203
204func TestCompleteCurrentPhase(t *testing.T) {
205 inst, _ := NewInstallation("host", "user", "/dev/sda", "none")
206 ctx := context.Background()
207
208 if err := inst.Start(ctx); err != nil {
209 t.Fatalf("expected no error, got %v", err)
210 }
211 inst.events = []DomainEvent{} // Clear events
212
213 // Complete first phase
214 if err := inst.CompleteCurrentPhase(30); err != nil {
215 t.Fatalf("expected no error, got %v", err)
216 }
217
218 if inst.State() != StateDiskPartitioning {
219 t.Errorf("expected state StateDiskPartitioning, got %v", inst.State())
220 }
221
222 // Check events
223 events := inst.UncommittedEvents()
224 if len(events) < 2 {
225 t.Errorf("expected at least 2 events, got %d", len(events))
226 }
227
228 // First event should be PhaseCompleted
229 if events[0].EventType() != "PhaseCompleted" {
230 t.Errorf("expected PhaseCompleted event, got %s", events[0].EventType())
231 }
232
233 // Second event should be PhaseStarted
234 if events[1].EventType() != "PhaseStarted" {
235 t.Errorf("expected PhaseStarted event, got %s", events[1].EventType())
236 }
237}
238
239func TestFailCurrentPhase(t *testing.T) {
240 inst, _ := NewInstallation("host", "user", "/dev/sda", "none")
241 ctx := context.Background()
242
243 if err := inst.Start(ctx); err != nil {
244 t.Fatalf("expected no error, got %v", err)
245 }
246 inst.events = []DomainEvent{} // Clear events
247
248 // Fail the phase
249 if err := inst.FailCurrentPhase("disk not found", false); err != nil {
250 t.Fatalf("expected no error, got %v", err)
251 }
252
253 if inst.State() != StateFailed {
254 t.Errorf("expected state StateFailed, got %v", inst.State())
255 }
256
257 if !inst.IsFailed() {
258 t.Error("expected IsFailed to be true")
259 }
260
261 if inst.IsSuccessful() {
262 t.Error("expected IsSuccessful to be false")
263 }
264
265 if inst.IsCompleted() != true {
266 t.Error("expected IsCompleted to be true")
267 }
268
269 // Check events
270 events := inst.UncommittedEvents()
271 if len(events) != 2 {
272 t.Errorf("expected 2 events, got %d", len(events))
273 }
274
275 if events[0].EventType() != "PhaseFailed" {
276 t.Errorf("expected PhaseFailed event, got %s", events[0].EventType())
277 }
278
279 if events[1].EventType() != "InstallationFailed" {
280 t.Errorf("expected InstallationFailed event, got %s", events[1].EventType())
281 }
282}
283
284func TestComplete_Success(t *testing.T) {
285 inst, _ := NewInstallation("host", "user", "/dev/sda", "none")
286 ctx := context.Background()
287
288 if err := inst.Start(ctx); err != nil {
289 t.Fatalf("expected no error, got %v", err)
290 }
291
292 // Advance to PostInstallation phase
293 for inst.State() != StatePostInstallation {
294 if err := inst.TransitionToNextPhase(); err != nil {
295 t.Fatalf("expected no error, got %v", err)
296 }
297 }
298
299 inst.events = []DomainEvent{} // Clear events
300
301 // Complete installation
302 if err := inst.Complete(300); err != nil {
303 t.Fatalf("expected no error, got %v", err)
304 }
305
306 if inst.State() != StateCompleted {
307 t.Errorf("expected state StateCompleted, got %v", inst.State())
308 }
309
310 if inst.CompletedAt() == nil {
311 t.Error("expected CompletedAt to be set")
312 }
313
314 // Check event
315 events := inst.UncommittedEvents()
316 if len(events) != 1 {
317 t.Errorf("expected 1 event, got %d", len(events))
318 }
319
320 if events[0].EventType() != "InstallationCompleted" {
321 t.Errorf("expected InstallationCompleted event, got %s", events[0].EventType())
322 }
323}
324
325func TestComplete_InvalidState(t *testing.T) {
326 inst, _ := NewInstallation("host", "user", "/dev/sda", "none")
327 ctx := context.Background()
328
329 if err := inst.Start(ctx); err != nil {
330 t.Fatalf("expected no error, got %v", err)
331 }
332
333 // Try to complete from wrong state
334 err := inst.Complete(300)
335 if err != nil {
336 t.Errorf("expected no error, got %v", err)
337 }
338}
339
340func TestProgressPercentage(t *testing.T) {
341 tests := []struct {
342 state State
343 percentage int
344 }{
345 {StateNotStarted, 0},
346 {StatePreflightChecks, 10},
347 {StateDiskPartitioning, 25},
348 {StateBaseInstallation, 40},
349 {StateSystemConfiguration, 60},
350 {StateBootloaderSetup, 75},
351 {StateRepositorySetup, 85},
352 {StatePostInstallation, 95},
353 {StateCompleted, 100},
354 {StateFailed, 0},
355 }
356
357 for _, tt := range tests {
358 t.Run(tt.state.String(), func(t *testing.T) {
359 if percentage := tt.state.ProgressPercentage(); percentage != tt.percentage {
360 t.Errorf("expected %d%%, got %d%%", tt.percentage, percentage)
361 }
362 })
363 }
364}
365
366func TestClearEvents(t *testing.T) {
367 inst, _ := NewInstallation("host", "user", "/dev/sda", "none")
368 ctx := context.Background()
369
370 if err := inst.Start(ctx); err != nil {
371 t.Fatalf("expected no error, got %v", err)
372 }
373
374 if len(inst.UncommittedEvents()) == 0 {
375 t.Fatal("expected events after start")
376 }
377
378 inst.ClearEvents()
379
380 if len(inst.UncommittedEvents()) != 0 {
381 t.Error("expected no events after clear")
382 }
383}
384
385func TestNewInstallation_EncryptionTypes(t *testing.T) {
386 validTypes := []string{"none", "luks", "luks-lvm"}
387
388 for _, encType := range validTypes {
389 t.Run(encType, func(t *testing.T) {
390 inst, err := NewInstallation("host", "user", "/dev/sda", encType)
391 if err != nil {
392 t.Errorf("unexpected error: %v", err)
393 }
394
395 if inst.EncryptionType() != encType {
396 t.Errorf("expected %s, got %s", encType, inst.EncryptionType())
397 }
398 })
399 }
400}
401
402func TestInstallation_TimestampAccuracy(t *testing.T) {
403 inst, _ := NewInstallation("host", "user", "/dev/sda", "none")
404
405 createdAt := inst.CreatedAt()
406 if createdAt.IsZero() {
407 t.Error("expected non-zero CreatedAt")
408 }
409
410 // StartedAt should be nil before starting
411 if inst.StartedAt() != nil {
412 t.Error("expected nil StartedAt before starting")
413 }
414
415 // CompletedAt should be nil before completing
416 if inst.CompletedAt() != nil {
417 t.Error("expected nil CompletedAt before completing")
418 }
419
420 ctx := context.Background()
421 if err := inst.Start(ctx); err != nil {
422 t.Fatalf("expected no error, got %v", err)
423 }
424
425 if inst.StartedAt() == nil {
426 t.Error("expected non-nil StartedAt after starting")
427 }
428
429 // Verify StartedAt is after CreatedAt
430 if inst.StartedAt().Before(createdAt) {
431 t.Error("expected StartedAt to be after or equal to CreatedAt")
432 }
433}
434
435func TestFailCurrentPhase_NotStarted(t *testing.T) {
436 inst, _ := NewInstallation("host", "user", "/dev/sda", "none")
437
438 err := inst.FailCurrentPhase("error", false)
439 if err != ErrInstallationNotStarted {
440 t.Errorf("expected ErrInstallationNotStarted, got %v", err)
441 }
442}
443
444func TestCompleteCurrentPhase_NotStarted(t *testing.T) {
445 inst, _ := NewInstallation("host", "user", "/dev/sda", "none")
446
447 err := inst.CompleteCurrentPhase(30)
448 if err != ErrInstallationNotStarted {
449 t.Errorf("expected ErrInstallationNotStarted, got %v", err)
450 }
451}
452
453func BenchmarkNewInstallation(b *testing.B) {
454 for i := 0; i < b.N; i++ {
455 if _, err := NewInstallation("host", "user", "/dev/sda", "none"); err != nil {
456 b.Fatalf("expected no error, got %v", err)
457 }
458 }
459}
460
461func BenchmarkStateTransition(b *testing.B) {
462 inst, _ := NewInstallation("host", "user", "/dev/sda", "none")
463 ctx := context.Background()
464 if err := inst.Start(ctx); err != nil {
465 b.Fatalf("expected no error, got %v", err)
466 }
467
468 b.ResetTimer()
469 for i := 0; i < b.N; i++ {
470 if inst.State() == StatePostInstallation {
471 // Reset by creating new instance for a fair benchmark
472 inst, _ = NewInstallation("host", "user", "/dev/sda", "none")
473 if err := inst.Start(ctx); err != nil {
474 b.Fatalf("expected no error, got %v", err)
475 }
476 }
477 if err := inst.TransitionToNextPhase(); err != nil {
478 b.Fatalf("expected no error, got %v", err)
479 }
480 }
481}