main 6af0a61

installation_test.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 0

    ↑↓ moveenter openesc close