slice 8C Phase B.1: agentapi disk client (Disks/AssignDisk/EjectDisk/FormatDisk)
ErrFormatRefused surfaces the agent's data-bearing refusal distinctly. Tests: list, blank format OK, data-bearing refused, eject dependents. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
package agentapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func diskStub(t *testing.T) (*httptest.Server, string) {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /disks", func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte(`{"ok":true,"data":{"vmid":8200,"disks":[{"name":"bulk","data_bearing":true,"data_reason":"has ext4"}]}}`))
|
||||
})
|
||||
mux.HandleFunc("POST /disks/assign", func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte(`{"ok":true,"data":{"assigned":"/mnt/data"}}`))
|
||||
})
|
||||
mux.HandleFunc("POST /disks/eject", func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte(`{"ok":true,"data":{"ejected":"/mnt/bulk","dependent_guests":[8200]}}`))
|
||||
})
|
||||
mux.HandleFunc("POST /disks/format", func(w http.ResponseWriter, r *http.Request) {
|
||||
var body struct{ Device, FSType string }
|
||||
_ = decodeJSON(r, &body)
|
||||
if strings.Contains(body.Device, "data") {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
_, _ = w.Write([]byte(`{"ok":false,"error":"device is data-bearing — operator authorization (pending_signature)"}`))
|
||||
return
|
||||
}
|
||||
_, _ = w.Write([]byte(`{"ok":true,"data":{"device":"` + body.Device + `","formatted":true}}`))
|
||||
})
|
||||
s := httptest.NewTLSServer(mux)
|
||||
return s, strings.TrimPrefix(s.URL, "https://")
|
||||
}
|
||||
|
||||
func decodeJSON(r *http.Request, v any) error {
|
||||
return json.NewDecoder(r.Body).Decode(v)
|
||||
}
|
||||
|
||||
func clientFor(t *testing.T, s *httptest.Server, endpoint string) *Client {
|
||||
pin := leafPin(t, s)
|
||||
c, err := New(endpoint, "TOK", pin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func TestDisks_List(t *testing.T) {
|
||||
s, ep := diskStub(t)
|
||||
defer s.Close()
|
||||
c := clientFor(t, s, ep)
|
||||
resp, err := c.Disks(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(resp.Disks) != 1 || !resp.Disks[0].DataBearing {
|
||||
t.Fatalf("unexpected: %+v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormat_BlankOK(t *testing.T) {
|
||||
s, ep := diskStub(t)
|
||||
defer s.Close()
|
||||
c := clientFor(t, s, ep)
|
||||
res, err := c.FormatDisk(context.Background(), "/dev/sdb", "ext4")
|
||||
if err != nil || !res.Formatted {
|
||||
t.Fatalf("blank format: %v %+v", err, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormat_DataBearingRefused(t *testing.T) {
|
||||
s, ep := diskStub(t)
|
||||
defer s.Close()
|
||||
c := clientFor(t, s, ep)
|
||||
_, err := c.FormatDisk(context.Background(), "/dev/data-disk", "ext4")
|
||||
if !errors.Is(err, ErrFormatRefused) {
|
||||
t.Fatalf("expected ErrFormatRefused, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEject_Dependents(t *testing.T) {
|
||||
s, ep := diskStub(t)
|
||||
defer s.Close()
|
||||
c := clientFor(t, s, ep)
|
||||
res, err := c.EjectDisk(context.Background(), "/mnt/bulk")
|
||||
if err != nil || len(res.DependentGuests) != 1 || res.DependentGuests[0] != 8200 {
|
||||
t.Fatalf("eject: %v %+v", err, res)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user