package proxmox import ( "context" "net/http" "testing" ) // TestRouting_APIOpsNeverShellOut asserts the API path never invokes the // privileged runner: API ops (read + mutating) go only through the HTTP doer. func TestRouting_APIOpsNeverShellOut(t *testing.T) { runner := &mockRunner{} // If any API op tried to use a runner, it would have to be wired here — it // cannot be, because Client has no runner field. We still assert structurally: // run a batch of API ops with a recording doer and confirm the runner is idle. d := &mockDoer{fn: func(r *http.Request) (*http.Response, error) { // Generic OK responses sufficient for the calls below. if r.Method == http.MethodGet { return jsonResp(200, `{"data":[]}`), nil } return jsonResp(200, `{"data":"`+testUPID+`"}`), nil }} c := newTestClient(d) ctx := context.Background() _, _ = c.Version(ctx) _, _ = c.Nodes(ctx) _, _ = c.ListLXC(ctx) _, _ = c.NodeStorage(ctx) _, _ = c.Snapshot(ctx, 9001, "s1", "") _, _ = c.Rollback(ctx, 9001, "s1") _, _ = c.Vzdump(ctx, VzdumpOptions{VMID: 9001, Storage: "local", Mode: ModeStop}) _, _ = c.RestoreLXC(ctx, RestoreLXCOptions{VMID: 9100, Archive: "local:backup/a.tar.zst", Storage: "local-lvm"}) _, _ = c.Start(ctx, 9001) _, _ = c.Stop(ctx, 9001) if runner.calls != 0 { t.Fatalf("API ops invoked the privileged runner %d time(s) — fence broken", runner.calls) } if d.calls == 0 { t.Fatalf("expected API ops to use the HTTP doer") } } // TestRouting_PrivilegedOpsNeverHTTP asserts the fenced root path never makes an // HTTP call: Privileged ops go only through the runner. func TestRouting_PrivilegedOpsNeverHTTP(t *testing.T) { d := &mockDoer{fn: func(r *http.Request) (*http.Response, error) { t.Fatalf("privileged op made an HTTP call to %s — fence broken", r.URL) return nil, nil }} _ = d // a Privileged has no doer field; this doer is unreachable by construction. runner := &mockRunner{out: []byte(`{"ok":true}`)} p := NewPrivileged(runner, "demo-felhom") ctx := context.Background() if err := p.CreateGoldenLXC(ctx, GoldenLXCSpec{VMID: 9999, OSTemplate: "local:vztmpl/x.tar.zst", Storage: "local-lvm"}); err != nil { t.Fatalf("CreateGoldenLXC: %v", err) } if err := p.MountUSBByUUID(ctx, "1234-ABCD", "/mnt/usb"); err != nil { t.Fatalf("MountUSBByUUID: %v", err) } if _, err := p.SMART(ctx, "/dev/sda"); err != nil { t.Fatalf("SMART: %v", err) } if _, err := p.Sensors(ctx); err != nil { t.Fatalf("Sensors: %v", err) } if runner.calls == 0 { t.Fatalf("expected privileged ops to use the runner") } } // TestPrivileged_CreateGoldenForcesKeyctl asserts the golden create always carries // the keyctl feature flag (the whole reason it is root-fenced). func TestPrivileged_CreateGoldenForcesKeyctl(t *testing.T) { runner := &mockRunner{} p := NewPrivileged(runner, "demo-felhom") if err := p.CreateGoldenLXC(context.Background(), GoldenLXCSpec{ VMID: 9999, OSTemplate: "local:vztmpl/x.tar.zst", Storage: "local-lvm", RootFSGB: 8, }); err != nil { t.Fatalf("CreateGoldenLXC: %v", err) } if runner.lastCmd != "pct" { t.Errorf("cmd = %q, want pct", runner.lastCmd) } var sawFeatures bool for i, a := range runner.lastArg { if a == "--features" && i+1 < len(runner.lastArg) && runner.lastArg[i+1] == "nesting=1,keyctl=1" { sawFeatures = true } } if !sawFeatures { t.Errorf("pct create args missing keyctl features: %v", runner.lastArg) } }