package proxmox import ( "context" "io" "net/http" "testing" ) // PoolAddVMID issues PUT /pools/{pool} with vms={vmid} and treats an already-member error as success. func TestPoolAddVMID_PUTShape(t *testing.T) { var gotMethod, gotPath, gotVMS string d := &mockDoer{fn: func(r *http.Request) (*http.Response, error) { gotMethod = r.Method gotPath = r.URL.Path body, _ := io.ReadAll(r.Body) // the client encodes url.Values into the request body for _, kv := range splitAmp(string(body)) { if k, v, ok := cut(kv, "="); ok && k == "vms" { gotVMS = v } } return jsonResp(http.StatusOK, `{"data":null}`), nil }} c := newTestClient(d) if err := c.PoolAddVMID(context.Background(), "felhom", 9201); err != nil { t.Fatalf("PoolAddVMID: %v", err) } if gotMethod != http.MethodPut { t.Errorf("method = %q, want PUT", gotMethod) } if gotPath != "/api2/json/pools/felhom" { t.Errorf("path = %q, want /api2/json/pools/felhom", gotPath) } if gotVMS != "9201" { t.Errorf("vms param = %q, want 9201", gotVMS) } } // Idempotent: an "already in pool" error from PVE is swallowed as success. func TestPoolAddVMID_IdempotentOnAlreadyMember(t *testing.T) { d := &mockDoer{fn: func(_ *http.Request) (*http.Response, error) { return jsonResp(http.StatusInternalServerError, `{"data":null,"errors":{"vms":"VM 9201 is already in pool 'felhom'"}}`), nil }} c := newTestClient(d) if err := c.PoolAddVMID(context.Background(), "felhom", 9201); err != nil { t.Fatalf("already-member should be idempotent success, got: %v", err) } } // A real failure (e.g. a 403 with no "already") is surfaced, not swallowed. func TestPoolAddVMID_RealErrorSurfaces(t *testing.T) { d := &mockDoer{fn: func(_ *http.Request) (*http.Response, error) { return jsonResp(http.StatusForbidden, `Permission check failed (/pool/felhom, Pool.Allocate)`), nil }} c := newTestClient(d) if err := c.PoolAddVMID(context.Background(), "felhom", 9201); err == nil { t.Fatal("expected a real 403 to surface, got nil") } } func TestPoolAddVMID_Validation(t *testing.T) { c := newTestClient(&mockDoer{fn: func(_ *http.Request) (*http.Response, error) { t.Fatal("should not issue a request on bad input") return nil, nil }}) if err := c.PoolAddVMID(context.Background(), "", 9201); err == nil { t.Error("empty pool should error") } if err := c.PoolAddVMID(context.Background(), "felhom", 0); err == nil { t.Error("zero vmid should error") } } // tiny helpers (avoid pulling strings.Split into an assertion path that could mask a bug) func splitAmp(s string) []string { var out []string cur := "" for _, r := range s { if r == '&' { out = append(out, cur) cur = "" continue } cur += string(r) } return append(out, cur) } func cut(s, sep string) (string, string, bool) { for i := 0; i+len(sep) <= len(s); i++ { if s[i:i+len(sep)] == sep { return s[:i], s[i+len(sep):], true } } return s, "", false }