package proxmox import ( "context" "errors" "net/http" "testing" ) func TestAPIError_403ExtractsPrivilege(t *testing.T) { d := &mockDoer{fn: func(r *http.Request) (*http.Response, error) { return jsonResp(403, `{"message":"Permission check failed (/nodes/demo-felhom, Sys.Audit)\n"}`), nil }} _, err := newTestClient(d).NodeStatus(context.Background()) var ae *APIError if !errors.As(err, &ae) { t.Fatalf("want *APIError, got %T: %v", err, err) } if !ae.IsForbidden() { t.Errorf("IsForbidden = false") } if ae.Privilege != "Sys.Audit" { t.Errorf("privilege = %q, want Sys.Audit", ae.Privilege) } if ae.DeniedPath != "/nodes/demo-felhom" { t.Errorf("denied path = %q", ae.DeniedPath) } } func TestDecode_ListLXC(t *testing.T) { // Exact shape captured from the live host. body := `{"data":[{"cpu":0,"cpus":2,"disk":0,"maxdisk":10737418240,"maxmem":2147483648,"mem":0,"name":"spike-lxc","status":"stopped","type":"lxc","uptime":0,"vmid":9001}]}` d := &mockDoer{fn: func(r *http.Request) (*http.Response, error) { return jsonResp(200, body), nil }} gs, err := newTestClient(d).ListLXC(context.Background()) if err != nil { t.Fatalf("ListLXC: %v", err) } if len(gs) != 1 { t.Fatalf("len = %d", len(gs)) } g := gs[0] if g.VMID != 9001 || g.Name != "spike-lxc" || g.Status != "stopped" || g.CPUs != 2 { t.Errorf("decoded guest wrong: %+v", g) } } func TestDecode_NodeStatus(t *testing.T) { body := `{"data":{"cpu":0.0057,"uptime":73078,"loadavg":["0.11","0.09","0.05"],"pveversion":"pve-manager/9.2.2","memory":{"total":16537989120,"used":2043027456,"free":13587857408,"available":14494961664},"rootfs":{"total":100861726720,"used":4943888384,"free":95917838336,"avail":90747101184},"cpuinfo":{"cores":4,"cpus":4,"sockets":1,"model":"Intel(R) N100"}}}` d := &mockDoer{fn: func(r *http.Request) (*http.Response, error) { return jsonResp(200, body), nil }} s, err := newTestClient(d).NodeStatus(context.Background()) if err != nil { t.Fatalf("NodeStatus: %v", err) } if len(s.LoadAvg) != 3 || s.LoadAvg[0] != "0.11" { t.Errorf("loadavg = %v", s.LoadAvg) } if s.Memory.Total != 16537989120 || s.CPUInfo.Cores != 4 { t.Errorf("decoded node status wrong: %+v", s) } } func TestDecode_GuestConfig_FeaturesAndExtra(t *testing.T) { // keyctl must survive as a string; mpN/netN land in Extra. body := `{"data":{"arch":"amd64","cores":2,"features":"nesting=1,keyctl=1","hostname":"spike-lxc","memory":2048,"net0":"name=eth0,bridge=vmbr0,hwaddr=BC:24:11:D1:6D:CB,ip=dhcp,type=veth","rootfs":"local-lvm:vm-9001-disk-0,size=10G","unprivileged":1,"mp0":"local-lvm:1,mp=/mnt/bulk,backup=0"}}` d := &mockDoer{fn: func(r *http.Request) (*http.Response, error) { return jsonResp(200, body), nil }} cfg, err := newTestClient(d).GuestConfig(context.Background(), 9001) if err != nil { t.Fatalf("GuestConfig: %v", err) } if cfg.Features != "nesting=1,keyctl=1" { t.Errorf("features = %q", cfg.Features) } if cfg.Unprivileged != 1 { t.Errorf("unprivileged = %d", cfg.Unprivileged) } if mp := cfg.MountPoints(); mp["mp0"] != "local-lvm:1,mp=/mnt/bulk,backup=0" { t.Errorf("mountpoints = %v", mp) } if nets := cfg.Nets(); nets["net0"] == "" { t.Errorf("nets = %v", nets) } // "memory" must NOT be misread as an mp/net prefix match. if mp := cfg.MountPoints(); len(mp) != 1 { t.Errorf("expected exactly 1 mountpoint, got %v", mp) } } func TestDataString_ReturnsUPID(t *testing.T) { d := &mockDoer{fn: func(r *http.Request) (*http.Response, error) { if r.Method != http.MethodPost { t.Errorf("method = %s", r.Method) } return jsonResp(200, `{"data":"`+testUPID+`"}`), nil }} upid, err := newTestClient(d).Snapshot(context.Background(), 9001, "s1", "") if err != nil { t.Fatalf("Snapshot: %v", err) } if upid != testUPID { t.Errorf("upid = %q", upid) } }