1package handlers
2
3import (
4 "context"
5 "fmt"
6 "testing"
7
8 "github.com/bnema/archup/internal/application/commands"
9 "github.com/bnema/archup/internal/domain/packages"
10 "github.com/bnema/archup/internal/domain/ports/mocks"
11 "go.uber.org/mock/gomock"
12)
13
14func errNotFound(name string) error {
15 return fmt.Errorf("%s: not found", name)
16}
17
18// chaoticMocks sets up the mock expectations that are always required because
19// Chaotic-AUR is unconditionally enabled.
20func chaoticMocks(mockChrExec *mocks.MockChrootExecutor, mockFS *mocks.MockFileSystem) {
21 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman-key", "--recv-key", gomock.Any(), "--keyserver", gomock.Any()).Return([]byte{}, nil).AnyTimes()
22 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman-key", "--lsign-key", gomock.Any()).Return([]byte{}, nil).AnyTimes()
23 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman", "-U", "--noconfirm", gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes()
24 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman", "-Sy", "--noconfirm").Return([]byte{}, nil).AnyTimes()
25 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman", "-S", "--noconfirm", gomock.Any()).Return([]byte{}, nil).AnyTimes()
26 mockFS.EXPECT().ReadFile("/mnt/etc/pacman.conf").Return([]byte("[core]\nInclude = /etc/pacman.d/mirrorlist\n"), nil).AnyTimes()
27 mockFS.EXPECT().WriteFile("/mnt/etc/pacman.conf", gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
28}
29
30func TestReposHandler_Handle_Minimal(t *testing.T) {
31 ctrl := gomock.NewController(t)
32 defer ctrl.Finish()
33
34 mockFS := mocks.NewMockFileSystem(ctrl)
35 mockChrExec := mocks.NewMockChrootExecutor(ctrl)
36 mockLogger := mocks.NewMockLogger(ctrl)
37
38 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
39 mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
40
41 chaoticMocks(mockChrExec, mockFS)
42 mockFS.EXPECT().ReadFile(gomock.Any()).Return(nil, errNotFound("extra.packages")).AnyTimes()
43
44 handler := NewReposHandler(mockFS, mockChrExec, mockLogger)
45
46 cmd := commands.SetupRepositoriesCommand{
47 MountPoint: "/mnt",
48 EnableMultilib: false,
49 AURHelper: packages.AURHelperParu,
50 }
51
52 result, err := handler.Handle(context.Background(), cmd)
53
54 if err != nil {
55 t.Fatalf("expected no error, got %v", err)
56 }
57 if !result.Success {
58 t.Error("expected success")
59 }
60 if result.Multilib {
61 t.Error("expected multilib to be disabled")
62 }
63 if result.AURHelper != "paru" {
64 t.Errorf("expected paru helper, got %s", result.AURHelper)
65 }
66}
67
68func TestReposHandler_Handle_MultilibEnabled(t *testing.T) {
69 ctrl := gomock.NewController(t)
70 defer ctrl.Finish()
71
72 mockFS := mocks.NewMockFileSystem(ctrl)
73 mockChrExec := mocks.NewMockChrootExecutor(ctrl)
74 mockLogger := mocks.NewMockLogger(ctrl)
75
76 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
77 mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
78
79 mockFS.EXPECT().ReadFile("/mnt/etc/pacman.conf").Return(
80 []byte("[core]\n#[multilib]\n#Include = /etc/pacman.d/mirrorlist\n"), nil,
81 ).AnyTimes()
82 mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
83 mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
84
85 chaoticMocks(mockChrExec, mockFS)
86 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman", "-S", "--noconfirm", "--needed", gomock.Any()).Return([]byte{}, nil).AnyTimes()
87 mockFS.EXPECT().ReadFile(gomock.Any()).Return(nil, errNotFound("extra.packages")).AnyTimes()
88
89 handler := NewReposHandler(mockFS, mockChrExec, mockLogger)
90
91 cmd := commands.SetupRepositoriesCommand{
92 MountPoint: "/mnt",
93 EnableMultilib: true,
94 AURHelper: packages.AURHelperYay,
95 }
96
97 result, err := handler.Handle(context.Background(), cmd)
98
99 if err != nil {
100 t.Fatalf("expected no error, got %v", err)
101 }
102 if !result.Success {
103 t.Error("expected success")
104 }
105 if !result.Multilib {
106 t.Error("expected multilib to be enabled")
107 }
108 if result.AURHelper != "yay" {
109 t.Errorf("expected yay helper, got %s", result.AURHelper)
110 }
111}
112
113func TestReposHandler_Handle_CachyOSKernel_AddsCachyOSRepo(t *testing.T) {
114 ctrl := gomock.NewController(t)
115 defer ctrl.Finish()
116
117 mockFS := mocks.NewMockFileSystem(ctrl)
118 mockChrExec := mocks.NewMockChrootExecutor(ctrl)
119 mockLogger := mocks.NewMockLogger(ctrl)
120
121 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
122 mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
123
124 // pacman.conf read: first call for CachyOS, rest for Chaotic
125 mockFS.EXPECT().ReadFile("/mnt/etc/pacman.conf").Return(
126 []byte("[core]\nInclude = /etc/pacman.d/mirrorlist\n"), nil,
127 ).AnyTimes()
128 mockFS.EXPECT().ReadFile(gomock.Any()).Return(nil, errNotFound("extra.packages")).AnyTimes()
129
130 mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
131
132 // WriteFile: cachyos mirrorlist written once; pacman.conf written multiple times
133 // (once for chaotic-aur, once for cachyos) — verify the final write has [cachyos]
134 mockFS.EXPECT().WriteFile("/mnt/etc/pacman.d/cachyos-mirrorlist", gomock.Any(), gomock.Any()).Return(nil)
135 pacmanConfWriteCount := 0
136 mockFS.EXPECT().WriteFile("/mnt/etc/pacman.conf", gomock.Any(), gomock.Any()).DoAndReturn(
137 func(path string, data []byte, perm interface{}) error {
138 pacmanConfWriteCount++
139 return nil
140 },
141 ).AnyTimes()
142 t.Cleanup(func() {
143 if pacmanConfWriteCount == 0 {
144 t.Error("expected pacman.conf to be written at least once")
145 }
146 })
147
148 // CachyOS key ops
149 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), "/mnt", "pacman-key", "--init").Return([]byte{}, nil)
150 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), "/mnt", "pacman-key", "--list-keys", "F3B607488DB35A47").Return(nil, errNotFound("key"))
151 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), "/mnt", "pacman-key", "--recv-keys",
152 "F3B607488DB35A47", "--keyserver", "keyserver.ubuntu.com").Return([]byte{}, nil)
153 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), "/mnt", "pacman-key", "--lsign-key",
154 "F3B607488DB35A47").Return([]byte{}, nil)
155
156 // Chaotic AUR key ops (always enabled)
157 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman-key", "--recv-key", gomock.Any(), "--keyserver", gomock.Any()).Return([]byte{}, nil).AnyTimes()
158 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman-key", "--lsign-key", gomock.Any()).Return([]byte{}, nil).AnyTimes()
159 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman", "-U", "--noconfirm", gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes()
160 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman", "-Sy", "--noconfirm").Return([]byte{}, nil).AnyTimes()
161 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman", "-S", "--noconfirm", gomock.Any()).Return([]byte{}, nil).AnyTimes()
162
163 handler := NewReposHandler(mockFS, mockChrExec, mockLogger)
164
165 cmd := commands.SetupRepositoriesCommand{
166 MountPoint: "/mnt",
167 AURHelper: packages.AURHelperParu,
168 KernelVariant: packages.KernelCachyOS,
169 }
170
171 result, err := handler.Handle(context.Background(), cmd)
172
173 if err != nil {
174 t.Fatalf("expected no error, got %v", err)
175 }
176 if !result.Success {
177 t.Error("expected success")
178 }
179}
180
181func TestReposHandler_Handle_WithAdditionalRepos(t *testing.T) {
182 ctrl := gomock.NewController(t)
183 defer ctrl.Finish()
184
185 mockFS := mocks.NewMockFileSystem(ctrl)
186 mockChrExec := mocks.NewMockChrootExecutor(ctrl)
187 mockLogger := mocks.NewMockLogger(ctrl)
188
189 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
190 mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
191
192 chaoticMocks(mockChrExec, mockFS)
193 mockFS.EXPECT().ReadFile(gomock.Any()).Return(nil, errNotFound("extra.packages")).AnyTimes()
194
195 handler := NewReposHandler(mockFS, mockChrExec, mockLogger)
196
197 cmd := commands.SetupRepositoriesCommand{
198 MountPoint: "/mnt",
199 AURHelper: packages.AURHelperParu,
200 AdditionalRepos: []string{
201 "https://example.com/archup",
202 "https://custom.repo/packages",
203 },
204 }
205
206 result, err := handler.Handle(context.Background(), cmd)
207
208 if err != nil {
209 t.Fatalf("expected no error, got %v", err)
210 }
211 if !result.Success {
212 t.Error("expected success")
213 }
214}