test(uninstaller): verify flatpak missing ref fallback and fatal failure handling
swadhinbiswas committed 2 months ago
6cdb216
1 parent abdcc64
commit 6cdb216
1 file changed
+66 -12
Showing 1 changed file+66-12
▼internal/uninstaller/uninstaller_test.go
+66-12
| @@ -14,7 +14,8 @@ import ( | ||
14 | 14 | // fakeCmder records commands and simulates failures on demand. |
15 | 15 | type fakeCmder struct { |
16 | 16 | ran []string |
17 | - fail map[string]bool // tool name -> always fail | |
17 | + fail map[string]bool // tool name -> always fail | |
18 | + errs map[string]error // specific command or tool -> custom error | |
18 | 19 | hasSudo bool |
19 | 20 | } |
20 | 21 | |
| @@ -26,7 +27,16 @@ func (f *fakeCmder) LookPath(name string) (string, error) { | ||
26 | 27 | } |
27 | 28 | |
28 | 29 | func (f *fakeCmder) Run(ctx context.Context, name string, args ...string) error { |
29 | - f.ran = append(f.ran, name+" "+strings.Join(args, " ")) | |
30 | + fullCmd := name + " " + strings.Join(args, " ") | |
31 | + f.ran = append(f.ran, fullCmd) | |
32 | + if f.errs != nil { | |
33 | + if err, ok := f.errs[fullCmd]; ok { | |
34 | + return err | |
35 | + } | |
36 | + if err, ok := f.errs[name]; ok { | |
37 | + return err | |
38 | + } | |
39 | + } | |
30 | 40 | if f.fail != nil && f.fail[name] { |
31 | 41 | return errors.New("command failed") |
32 | 42 | } |
| @@ -249,24 +259,68 @@ func TestIsSafePath(t *testing.T) { | ||
249 | 259 | |
250 | 260 | func TestRemovePackageNotFoundCleansResidualFiles(t *testing.T) { |
251 | 261 | u, cmder := testUninstaller(t) |
252 | - // Simulate flatpak failing with "No installed refs found" | |
253 | - cmder.fail = map[string]bool{"flatpak": true} | |
254 | - // Custom Run implementation for this test | |
262 | + // Seed a leftover config and cache directory before staging | |
263 | + seed(t, u.FS, "/home/u/.config/ai.jan.Jan/config.json", "{}") | |
264 | + seed(t, u.FS, "/home/u/.cache/ai.jan.Jan/cache.dat", "data") | |
265 | + | |
255 | 266 | app := model.AppInfo{Name: "ai.jan.Jan", Source: "flatpak", InstallSizeKB: 100} |
256 | 267 | if err := u.StageRemoval(&app); err != nil { |
257 | 268 | t.Fatal(err) |
258 | 269 | } |
259 | - // Seed a leftover config and cache directory | |
260 | - seed(t, u.FS, "/home/u/.config/ai.jan.Jan/config.json", "{}") | |
261 | - seed(t, u.FS, "/home/u/.cache/ai.jan.Jan/cache.dat", "data") | |
262 | 270 | |
263 | - // Set custom error | |
264 | - cmder.fail = nil // We test through StageRemoval | |
271 | + // Simulate system-level flatpak failing with "No installed refs found" | |
272 | + cmder.errs = map[string]error{ | |
273 | + "flatpak uninstall --noninteractive -y --delete-data ai.jan.Jan": errors.New("error: No installed refs found for ai.jan.Jan"), | |
274 | + } | |
275 | + | |
265 | 276 | freed, files, err := u.Remove(context.Background(), &app, nil) |
266 | 277 | if err != nil { |
267 | 278 | t.Fatalf("expected success with residual cleanup, got: %v", err) |
268 | 279 | } |
269 | - if freed == 0 && files == 0 { | |
270 | - t.Fatal("expected files to be cleaned") | |
280 | + if freed == 0 || files == 0 { | |
281 | + t.Fatalf("expected files to be cleaned, freed=%d files=%d", freed, files) | |
282 | + } | |
283 | + | |
284 | + // Verify that the user-scope fallback command ran | |
285 | + foundUserFallback := false | |
286 | + for _, cmd := range cmder.ran { | |
287 | + if cmd == "flatpak uninstall --user --noninteractive -y ai.jan.Jan" { | |
288 | + foundUserFallback = true | |
289 | + break | |
290 | + } | |
291 | + } | |
292 | + if !foundUserFallback { | |
293 | + t.Fatalf("expected flatpak --user fallback to run, ran: %v", cmder.ran) | |
294 | + } | |
295 | + | |
296 | + // Config files should have been removed | |
297 | + if exists, _ := afero.DirExists(u.FS, "/home/u/.config/ai.jan.Jan"); exists { | |
298 | + t.Fatal("config directory should have been purged") | |
299 | + } | |
300 | +} | |
301 | + | |
302 | +func TestRemoveFlatpakFatalFailureAborts(t *testing.T) { | |
303 | + u, cmder := testUninstaller(t) | |
304 | + seed(t, u.FS, "/home/u/.config/ai.jan.Jan/config.json", "{}") | |
305 | + | |
306 | + app := model.AppInfo{Name: "ai.jan.Jan", Source: "flatpak", InstallSizeKB: 100} | |
307 | + if err := u.StageRemoval(&app); err != nil { | |
308 | + t.Fatal(err) | |
309 | + } | |
310 | + | |
311 | + // Fatal uninstaller failure that is not a missing reference | |
312 | + cmder.errs = map[string]error{ | |
313 | + "flatpak uninstall --noninteractive -y --delete-data ai.jan.Jan": errors.New("error: DBus connection timed out"), | |
314 | + } | |
315 | + | |
316 | + _, files, err := u.Remove(context.Background(), &app, nil) | |
317 | + if err == nil { | |
318 | + t.Fatal("expected Remove to return error on fatal flatpak failure") | |
319 | + } | |
320 | + if files != 0 { | |
321 | + t.Fatalf("expected no files to be removed on fatal failure, removed %d", files) | |
322 | + } | |
323 | + if exists, _ := afero.DirExists(u.FS, "/home/u/.config/ai.jan.Jan"); !exists { | |
324 | + t.Fatal("config directory must survive a fatal package failure") | |
271 | 325 | } |
272 | 326 | } |
273 | 327 | |