package api import ( "context" "git.misaka.ren/m1saka/dsh-web/internal/store" "net/http" "net/http/httptest" "os" "strings" "testing" ) func TestIntentsReserveCancel(t *testing.T) { a, sid := runtimeTestAPI(t, func(w http.ResponseWriter, r *http.Request) { t.Error("reservation contacted Docker") }) if err := a.initAuth(); err != nil { t.Fatal(err) } for n := 0; n < 3; n++ { w := httptest.NewRecorder() a.intents(w, runtimeRequest("POST", "/api/intents", `{"name":"one","version":"1.2.3"}`, sid)) want := 202 if n == 2 { want = 409 } if w.Code != want { t.Fatalf("%d: %d %s", n, w.Code, w.Body.String()) } } list, err := a.s.Intents("owner") if err != nil || len(list) != 2 { t.Fatalf("%v %v", list, err) } w := httptest.NewRecorder() a.intent(w, runtimeRequest("DELETE", "/api/intents/"+list[0].ID, "", sid)) if w.Code != 200 { t.Fatal(w.Code) } if err := a.s.ReserveIntent(store.Intent{ID: store.ID(), UID: "owner"}); err != nil { t.Fatal(err) } } func TestIntentBuildSuccessUsesRealRuntime(t *testing.T) { if os.Geteuid() != 0 { t.Skip("runtime filesystem ownership requires root") } image := "sha256:" + strings.Repeat("a", 64) creates := 0 a, _ := runtimeTestAPI(t, func(w http.ResponseWriter, r *http.Request) { switch { case strings.Contains(r.URL.Path, "/images/"): writeJSON(w, map[string]any{"Id": image, "Config": map[string]any{"Labels": map[string]string{"dsh-mgr.role": "runtime", "dsh-mgr.version": "1.2.3"}}}) case strings.HasSuffix(r.URL.Path, "/containers/create"): creates++ writeJSON(w, map[string]string{"Id": "runtime-created"}) default: w.WriteHeader(204) } }) seedVerifiedImage(t, a, image, "1.2.3") if err := a.s.EnsureIntents(); err != nil { t.Fatal(err) } var build string if err := a.s.DB.QueryRow("SELECT id FROM image_builds WHERE version='1.2.3'").Scan(&build); err != nil { t.Fatal(err) } in := store.Intent{ID: store.ID(), UID: "owner", Name: "one", Version: "1.2.3", Host: "panel.test"} if err := a.s.ReserveIntent(in); err != nil { t.Fatal(err) } if _, err := a.s.DB.Exec("UPDATE creation_intents SET build_id=? WHERE id=?", build, in.ID); err != nil { t.Fatal(err) } a.processIntents(context.Background()) a.processIntents(context.Background()) list, err := a.s.Intents("owner") if err != nil || len(list) != 1 || list[0].Status != "success" || creates != 1 { t.Fatalf("%+v err=%v creates=%d", list, err, creates) } instances, err := a.s.Instances("owner") if err != nil || len(instances) != 1 || instances[0].ImageID != image || instances[0].Version != "1.2.3" { t.Fatalf("%+v %v", instances, err) } } func TestIntentFailedBuildReleasesAndCancelPreservesBuild(t *testing.T) { a, _ := runtimeTestAPI(t, func(w http.ResponseWriter, r *http.Request) { t.Error("failed intent contacted Docker") }) seedVerifiedImage(t, a, "sha256:"+strings.Repeat("b", 64), "1.2.3") if err := a.s.EnsureIntents(); err != nil { t.Fatal(err) } var build string a.s.DB.QueryRow("SELECT id FROM image_builds LIMIT 1").Scan(&build) for _, id := range []string{"cancel", "fail"} { if err := a.s.ReserveIntent(store.Intent{ID: id, UID: "owner", Version: "1.2.3"}); err != nil { t.Fatal(err) } a.s.DB.Exec("UPDATE creation_intents SET build_id=? WHERE id=?", build, id) } if err := a.s.CancelIntent("cancel", "owner"); err != nil { t.Fatal(err) } var status string a.s.DB.QueryRow("SELECT status FROM image_builds WHERE id=?", build).Scan(&status) if status != "success" { t.Fatal(status) } a.s.DB.Exec("UPDATE image_builds SET status='failed' WHERE id=?", build) a.processIntents(context.Background()) for n := 0; n < 2; n++ { if err := a.s.ReserveIntent(store.Intent{ID: store.ID(), UID: "owner"}); err != nil { t.Fatal(err) } } } func TestIntentRestartCleansInterruptedCreationAndRetainsWaiting(t *testing.T) { deletes := 0 a, _ := runtimeTestAPI(t, func(w http.ResponseWriter, r *http.Request) { if r.Method == "DELETE" { deletes++ } w.WriteHeader(204) }) if err := a.s.EnsureIntents(); err != nil { t.Fatal(err) } for _, id := range []string{"waiting", "creating"} { if err := a.s.ReserveIntent(store.Intent{ID: id, UID: "owner", Version: "1.2.3"}); err != nil { t.Fatal(err) } } if err := a.s.ClaimIntent("creating", store.Instance{ID: "reserved", UID: "owner", Version: "1.2.3", Status: "creating"}); err != nil { t.Fatal(err) } if err := a.recoverIntents(context.Background()); err != nil { t.Fatal(err) } if deletes != 2 { t.Fatalf("cleanup calls=%d", deletes) } list, err := a.s.Intents("owner") if err != nil { t.Fatal(err) } for _, in := range list { if in.ID == "waiting" && in.Status != "waiting" { t.Fatal(in) } if in.ID == "creating" && in.Status != "failed" { t.Fatal(in) } } if err := a.s.ReserveIntent(store.Intent{ID: store.ID(), UID: "owner"}); err != nil { t.Fatal(err) } }