package api import ( "net/http" "net/http/httptest" "os" "path/filepath" "testing" "git.misaka.ren/m1saka/dsh-web/internal/store" ) func TestInstanceDeleteDataAndRetry(t *testing.T) { for _, blocked := range []bool{false, true} { t.Run(map[bool]string{false: "success", true: "retry"}[blocked], func(t *testing.T) { a, sid := runtimeTestAPI(t, func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodDelete { w.WriteHeader(204) } else { w.WriteHeader(404) } }) i := store.Instance{ID: "one", UID: "owner", ContainerID: "container"} if err := a.s.AddInstance(i); err != nil { t.Fatal(err) } write := func(rel string) { p := filepath.Join(a.root, rel) if err := os.MkdirAll(filepath.Dir(p), 0750); err != nil { t.Fatal(err) } if err := os.WriteFile(p, []byte("keep"), 0600); err != nil { t.Fatal(err) } } write("workspaces/owner/instances/one/home/data") write("workspaces/owner/instances/one/pre-restore-test/data") write("workspaces/owner/instances/two/home/data") write("backups/owner/one/archive.dshbak") write("backups/owner/two/archive.dshbak") outside := t.TempDir() sentinel := filepath.Join(outside, "sentinel") if err := os.WriteFile(sentinel, []byte("safe"), 0600); err != nil { t.Fatal(err) } if err := os.Symlink(outside, filepath.Join(a.root, "workspaces/owner/instances/one/home/link")); err != nil { t.Fatal(err) } backups := filepath.Join(a.root, "backups/owner") if blocked { if err := os.Rename(backups, backups+"-saved"); err != nil { t.Fatal(err) } if err := os.Symlink(backups+"-saved", backups); err != nil { t.Fatal(err) } } remove := func() *httptest.ResponseRecorder { w := httptest.NewRecorder() a.instance(w, runtimeRequest("DELETE", "/api/instances/one", "", sid)) return w } w := remove() if blocked { if w.Code != 500 { t.Fatal(w.Code, w.Body.String()) } current, err := a.s.GetInstance(i.ID) if err != nil || current.Status != "error" { t.Fatal(current, err) } if err := os.Remove(backups); err != nil { t.Fatal(err) } if err := os.Rename(backups+"-saved", backups); err != nil { t.Fatal(err) } w = remove() } if w.Code != 200 { t.Fatal(w.Code, w.Body.String()) } for _, rel := range []string{"workspaces/owner/instances/one", "backups/owner/one"} { if _, err := os.Lstat(filepath.Join(a.root, rel)); !os.IsNotExist(err) { t.Fatal("data retained", rel, err) } } for _, p := range []string{sentinel, filepath.Join(a.root, "workspaces/owner/instances/two/home/data"), filepath.Join(a.root, "backups/owner/two/archive.dshbak")} { if _, err := os.Stat(p); err != nil { t.Fatal("unrelated data removed", err) } } }) } } func TestUsageDoesNotCreateLegacyFiles(t *testing.T) { a, sid := runtimeTestAPI(t, func(w http.ResponseWriter, r *http.Request) { t.Fatal("unexpected Docker request") }) w := httptest.NewRecorder() a.workspace(w, runtimeRequest("GET", "/api/ws/usage", "", sid)) if w.Code != 200 { t.Fatal(w.Code, w.Body.String()) } if _, err := os.Stat(filepath.Join(a.root, "workspaces/owner/files")); !os.IsNotExist(err) { t.Fatal("legacy directory created", err) } if _, err := a.openWorkspace(store.User{ID: "owner"}, ""); err == nil { t.Fatal("shared workspace accepted") } }