From 9950e0e24af0e99f94b5addbfe52ee529a12d59c Mon Sep 17 00:00:00 2001 From: GnomeZworc Date: Sun, 26 Apr 2026 15:32:46 +0200 Subject: [PATCH] f-21: test: add test for agent api Signed-off-by: GnomeZworc --- internal/api/agent/helpers_test.go | 27 ++++ internal/api/agent/subnet_test.go | 234 +++++++++++++++++++++++++++++ internal/api/agent/vpc_test.go | 188 +++++++++++++++++++++++ 3 files changed, 449 insertions(+) create mode 100644 internal/api/agent/helpers_test.go create mode 100644 internal/api/agent/subnet_test.go create mode 100644 internal/api/agent/vpc_test.go diff --git a/internal/api/agent/helpers_test.go b/internal/api/agent/helpers_test.go new file mode 100644 index 0000000..206874c --- /dev/null +++ b/internal/api/agent/helpers_test.go @@ -0,0 +1,27 @@ +package agentapi + +import ( + "io" + "log/slog" + "testing" + + configuration "git.g3e.fr/syonad/two/internal/config/agent" + dispatcher "git.g3e.fr/syonad/two/internal/dispatcher/agent" + "git.g3e.fr/syonad/two/pkg/db/kv" + "git.g3e.fr/syonad/two/pkg/worker" + "github.com/dgraph-io/badger/v4" +) + +// newTestServer builds a Server backed by an in-memory Badger DB. +// The worker queue is buffered but has no running goroutines: Dispatch enqueues +// without blocking and Execute never runs, so DB state reflects only Prepare writes. +func newTestServer(t *testing.T) (*Server, *badger.DB) { + t.Helper() + db := kv.InitDB(kv.Config{Path: t.TempDir()}, false) + t.Cleanup(func() { db.Close() }) + q := worker.New(100) + cfg := &configuration.Config{DefaultInterface: "br-test"} + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + d := dispatcher.New(q, db, cfg, logger) + return New(d, db, logger), db +} diff --git a/internal/api/agent/subnet_test.go b/internal/api/agent/subnet_test.go new file mode 100644 index 0000000..0fdd18b --- /dev/null +++ b/internal/api/agent/subnet_test.go @@ -0,0 +1,234 @@ +package agentapi + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "git.g3e.fr/syonad/two/pkg/db/kv" +) + +// --- SubnetsHandler --- + +func TestListSubnets_Empty(t *testing.T) { + s, _ := newTestServer(t) + w := httptest.NewRecorder() + s.SubnetsHandler(w, httptest.NewRequest(http.MethodGet, "/subnets", nil)) + if w.Code != http.StatusOK { + t.Fatalf("attendu 200, obtenu %d", w.Code) + } + var result []Subnet + json.NewDecoder(w.Body).Decode(&result) + if len(result) != 0 { + t.Errorf("attendu liste vide, obtenu %v", result) + } +} + +func TestListSubnets_WithData(t *testing.T) { + s, db := newTestServer(t) + kv.AddInDB(db, "subnet/sn-1/state", "created") + kv.AddInDB(db, "subnet/sn-1/vpc", "vpc-1") + kv.AddInDB(db, "subnet/sn-2/state", "creating") + kv.AddInDB(db, "subnet/sn-2/vpc", "vpc-1") + w := httptest.NewRecorder() + s.SubnetsHandler(w, httptest.NewRequest(http.MethodGet, "/subnets", nil)) + if w.Code != http.StatusOK { + t.Fatalf("attendu 200, obtenu %d", w.Code) + } + var result []Subnet + json.NewDecoder(w.Body).Decode(&result) + if len(result) != 2 { + t.Errorf("attendu 2 subnets, obtenu %d", len(result)) + } +} + +func TestListSubnets_InvalidMethod(t *testing.T) { + s, _ := newTestServer(t) + w := httptest.NewRecorder() + s.SubnetsHandler(w, httptest.NewRequest(http.MethodPut, "/subnets", nil)) + if w.Code != http.StatusMethodNotAllowed { + t.Errorf("attendu 405, obtenu %d", w.Code) + } +} + +func TestPostSubnet_Created(t *testing.T) { + s, db := newTestServer(t) + kv.AddInDB(db, "vpc/vpc-1/state", "created") + req := SubnetCreateRequest{ + Name: "sn-new", + VPC: "vpc-1", + IfaceType: "vms", + GatewayIP: "10.0.0.1", + CIDR: "10.0.0.0/24", + } + body, _ := json.Marshal(req) + w := httptest.NewRecorder() + s.SubnetsHandler(w, httptest.NewRequest(http.MethodPost, "/subnets", bytes.NewReader(body))) + if w.Code != http.StatusAccepted { + t.Fatalf("attendu 202, obtenu %d: %s", w.Code, w.Body.String()) + } + var result Subnet + json.NewDecoder(w.Body).Decode(&result) + if result.Name != "sn-new" { + t.Errorf("name attendu sn-new, obtenu %q", result.Name) + } + if result.State != "creating" { + t.Errorf("state attendu creating, obtenu %q", result.State) + } +} + +func TestPostSubnet_MissingFields(t *testing.T) { + s, _ := newTestServer(t) + body, _ := json.Marshal(SubnetCreateRequest{Name: "sn-1"}) // vpc, iface_type, gateway_ip, cidr manquants + w := httptest.NewRecorder() + s.SubnetsHandler(w, httptest.NewRequest(http.MethodPost, "/subnets", bytes.NewReader(body))) + if w.Code != http.StatusBadRequest { + t.Errorf("attendu 400, obtenu %d", w.Code) + } +} + +func TestPostSubnet_VPCNotFound(t *testing.T) { + s, _ := newTestServer(t) + req := SubnetCreateRequest{ + Name: "sn-1", + VPC: "vpc-inexistant", + IfaceType: "vms", + GatewayIP: "10.0.0.1", + CIDR: "10.0.0.0/24", + } + body, _ := json.Marshal(req) + w := httptest.NewRecorder() + s.SubnetsHandler(w, httptest.NewRequest(http.MethodPost, "/subnets", bytes.NewReader(body))) + if w.Code != http.StatusConflict { + t.Errorf("attendu 409, obtenu %d", w.Code) + } +} + +func TestPostSubnet_Duplicate(t *testing.T) { + s, db := newTestServer(t) + kv.AddInDB(db, "vpc/vpc-1/state", "created") + kv.AddInDB(db, "subnet/sn-exist/state", "created") + req := SubnetCreateRequest{ + Name: "sn-exist", + VPC: "vpc-1", + IfaceType: "vms", + GatewayIP: "10.0.0.1", + CIDR: "10.0.0.0/24", + } + body, _ := json.Marshal(req) + w := httptest.NewRecorder() + s.SubnetsHandler(w, httptest.NewRequest(http.MethodPost, "/subnets", bytes.NewReader(body))) + if w.Code != http.StatusConflict { + t.Errorf("attendu 409, obtenu %d", w.Code) + } +} + +func TestPostSubnet_VPCDeleting(t *testing.T) { + s, db := newTestServer(t) + kv.AddInDB(db, "vpc/vpc-dying/state", "deleting") + req := SubnetCreateRequest{ + Name: "sn-1", + VPC: "vpc-dying", + IfaceType: "vms", + GatewayIP: "10.0.0.1", + CIDR: "10.0.0.0/24", + } + body, _ := json.Marshal(req) + w := httptest.NewRecorder() + s.SubnetsHandler(w, httptest.NewRequest(http.MethodPost, "/subnets", bytes.NewReader(body))) + if w.Code != http.StatusConflict { + t.Errorf("attendu 409, obtenu %d", w.Code) + } +} + +func TestPostSubnet_InvalidBody(t *testing.T) { + s, _ := newTestServer(t) + w := httptest.NewRecorder() + s.SubnetsHandler(w, httptest.NewRequest(http.MethodPost, "/subnets", bytes.NewReader([]byte("not json")))) + if w.Code != http.StatusBadRequest { + t.Errorf("attendu 400, obtenu %d", w.Code) + } +} + +// --- SubnetByNameHandler --- + +func TestGetSubnet_Found(t *testing.T) { + s, db := newTestServer(t) + kv.AddInDB(db, "subnet/sn-1/state", "created") + kv.AddInDB(db, "subnet/sn-1/vpc", "vpc-1") + kv.AddInDB(db, "subnet/sn-1/cidr", "10.0.0.0/24") + kv.AddInDB(db, "subnet/sn-1/gateway_ip", "10.0.0.1") + req := httptest.NewRequest(http.MethodGet, "/subnets/sn-1", nil) + w := httptest.NewRecorder() + s.SubnetByNameHandler(w, req) + if w.Code != http.StatusOK { + t.Fatalf("attendu 200, obtenu %d", w.Code) + } + var result Subnet + json.NewDecoder(w.Body).Decode(&result) + if result.Name != "sn-1" || result.State != "created" { + t.Errorf("résultat inattendu : %+v", result) + } + if result.VPC != "vpc-1" { + t.Errorf("vpc attendu vpc-1, obtenu %q", result.VPC) + } +} + +func TestGetSubnet_NotFound(t *testing.T) { + s, _ := newTestServer(t) + req := httptest.NewRequest(http.MethodGet, "/subnets/inexistant", nil) + w := httptest.NewRecorder() + s.SubnetByNameHandler(w, req) + if w.Code != http.StatusNotFound { + t.Errorf("attendu 404, obtenu %d", w.Code) + } +} + +func TestGetSubnet_EmptyName(t *testing.T) { + s, _ := newTestServer(t) + req := httptest.NewRequest(http.MethodGet, "/subnets/", nil) + w := httptest.NewRecorder() + s.SubnetByNameHandler(w, req) + if w.Code != http.StatusNotFound { + t.Errorf("attendu 404, obtenu %d", w.Code) + } +} + +func TestDeleteSubnet_Success(t *testing.T) { + s, db := newTestServer(t) + kv.AddInDB(db, "subnet/sn-del/state", "created") + req := httptest.NewRequest(http.MethodDelete, "/subnets/sn-del", nil) + w := httptest.NewRecorder() + s.SubnetByNameHandler(w, req) + if w.Code != http.StatusAccepted { + t.Fatalf("attendu 202, obtenu %d: %s", w.Code, w.Body.String()) + } + var result Subnet + json.NewDecoder(w.Body).Decode(&result) + if result.State != "deleting" { + t.Errorf("state attendu deleting, obtenu %q", result.State) + } +} + +func TestDeleteSubnet_NotFound(t *testing.T) { + s, _ := newTestServer(t) + req := httptest.NewRequest(http.MethodDelete, "/subnets/inexistant", nil) + w := httptest.NewRecorder() + s.SubnetByNameHandler(w, req) + if w.Code != http.StatusNotFound { + t.Errorf("attendu 404, obtenu %d", w.Code) + } +} + +func TestSubnetByName_InvalidMethod(t *testing.T) { + s, db := newTestServer(t) + kv.AddInDB(db, "subnet/sn-1/state", "created") + req := httptest.NewRequest(http.MethodPut, "/subnets/sn-1", nil) + w := httptest.NewRecorder() + s.SubnetByNameHandler(w, req) + if w.Code != http.StatusMethodNotAllowed { + t.Errorf("attendu 405, obtenu %d", w.Code) + } +} diff --git a/internal/api/agent/vpc_test.go b/internal/api/agent/vpc_test.go new file mode 100644 index 0000000..0edcd5b --- /dev/null +++ b/internal/api/agent/vpc_test.go @@ -0,0 +1,188 @@ +package agentapi + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "git.g3e.fr/syonad/two/pkg/db/kv" +) + +// --- VpcsHandler --- + +func TestListVpcs_Empty(t *testing.T) { + s, _ := newTestServer(t) + w := httptest.NewRecorder() + s.VpcsHandler(w, httptest.NewRequest(http.MethodGet, "/vpcs", nil)) + if w.Code != http.StatusOK { + t.Fatalf("attendu 200, obtenu %d", w.Code) + } + var result []VPC + json.NewDecoder(w.Body).Decode(&result) + if len(result) != 0 { + t.Errorf("attendu liste vide, obtenu %v", result) + } +} + +func TestListVpcs_WithData(t *testing.T) { + s, db := newTestServer(t) + kv.AddInDB(db, "vpc/v1/state", "created") + kv.AddInDB(db, "vpc/v2/state", "creating") + w := httptest.NewRecorder() + s.VpcsHandler(w, httptest.NewRequest(http.MethodGet, "/vpcs", nil)) + if w.Code != http.StatusOK { + t.Fatalf("attendu 200, obtenu %d", w.Code) + } + var result []VPC + json.NewDecoder(w.Body).Decode(&result) + if len(result) != 2 { + t.Errorf("attendu 2 VPCs, obtenu %d", len(result)) + } +} + +func TestListVpcs_InvalidMethod(t *testing.T) { + s, _ := newTestServer(t) + w := httptest.NewRecorder() + s.VpcsHandler(w, httptest.NewRequest(http.MethodPut, "/vpcs", nil)) + if w.Code != http.StatusMethodNotAllowed { + t.Errorf("attendu 405, obtenu %d", w.Code) + } +} + +func TestPostVpc_Created(t *testing.T) { + s, _ := newTestServer(t) + body, _ := json.Marshal(VPCCreateRequest{Name: "vpc-new"}) + w := httptest.NewRecorder() + s.VpcsHandler(w, httptest.NewRequest(http.MethodPost, "/vpcs", bytes.NewReader(body))) + if w.Code != http.StatusAccepted { + t.Fatalf("attendu 202, obtenu %d: %s", w.Code, w.Body.String()) + } + var result VPC + json.NewDecoder(w.Body).Decode(&result) + if result.Name != "vpc-new" { + t.Errorf("name attendu vpc-new, obtenu %q", result.Name) + } + if result.State != "creating" { + t.Errorf("state attendu creating, obtenu %q", result.State) + } +} + +func TestPostVpc_MissingName(t *testing.T) { + s, _ := newTestServer(t) + body, _ := json.Marshal(VPCCreateRequest{}) + w := httptest.NewRecorder() + s.VpcsHandler(w, httptest.NewRequest(http.MethodPost, "/vpcs", bytes.NewReader(body))) + if w.Code != http.StatusBadRequest { + t.Errorf("attendu 400, obtenu %d", w.Code) + } +} + +func TestPostVpc_Duplicate(t *testing.T) { + s, db := newTestServer(t) + kv.AddInDB(db, "vpc/vpc-exist/state", "created") + body, _ := json.Marshal(VPCCreateRequest{Name: "vpc-exist"}) + w := httptest.NewRecorder() + s.VpcsHandler(w, httptest.NewRequest(http.MethodPost, "/vpcs", bytes.NewReader(body))) + if w.Code != http.StatusConflict { + t.Errorf("attendu 409, obtenu %d", w.Code) + } +} + +func TestPostVpc_InvalidBody(t *testing.T) { + s, _ := newTestServer(t) + w := httptest.NewRecorder() + s.VpcsHandler(w, httptest.NewRequest(http.MethodPost, "/vpcs", bytes.NewReader([]byte("not json")))) + if w.Code != http.StatusBadRequest { + t.Errorf("attendu 400, obtenu %d", w.Code) + } +} + +// --- VpcByNameHandler --- + +func TestGetVpc_Found(t *testing.T) { + s, db := newTestServer(t) + kv.AddInDB(db, "vpc/vpc-1/state", "created") + req := httptest.NewRequest(http.MethodGet, "/vpcs/vpc-1", nil) + w := httptest.NewRecorder() + s.VpcByNameHandler(w, req) + if w.Code != http.StatusOK { + t.Fatalf("attendu 200, obtenu %d", w.Code) + } + var result VPC + json.NewDecoder(w.Body).Decode(&result) + if result.Name != "vpc-1" || result.State != "created" { + t.Errorf("résultat inattendu : %+v", result) + } +} + +func TestGetVpc_NotFound(t *testing.T) { + s, _ := newTestServer(t) + req := httptest.NewRequest(http.MethodGet, "/vpcs/inexistant", nil) + w := httptest.NewRecorder() + s.VpcByNameHandler(w, req) + if w.Code != http.StatusNotFound { + t.Errorf("attendu 404, obtenu %d", w.Code) + } +} + +func TestGetVpc_EmptyName(t *testing.T) { + s, _ := newTestServer(t) + req := httptest.NewRequest(http.MethodGet, "/vpcs/", nil) + w := httptest.NewRecorder() + s.VpcByNameHandler(w, req) + if w.Code != http.StatusNotFound { + t.Errorf("attendu 404, obtenu %d", w.Code) + } +} + +func TestDeleteVpc_Success(t *testing.T) { + s, db := newTestServer(t) + kv.AddInDB(db, "vpc/vpc-del/state", "created") + req := httptest.NewRequest(http.MethodDelete, "/vpcs/vpc-del", nil) + w := httptest.NewRecorder() + s.VpcByNameHandler(w, req) + if w.Code != http.StatusAccepted { + t.Fatalf("attendu 202, obtenu %d: %s", w.Code, w.Body.String()) + } + var result VPC + json.NewDecoder(w.Body).Decode(&result) + if result.State != "deleting" { + t.Errorf("state attendu deleting, obtenu %q", result.State) + } +} + +func TestDeleteVpc_NotFound(t *testing.T) { + s, _ := newTestServer(t) + req := httptest.NewRequest(http.MethodDelete, "/vpcs/inexistant", nil) + w := httptest.NewRecorder() + s.VpcByNameHandler(w, req) + if w.Code != http.StatusNotFound { + t.Errorf("attendu 404, obtenu %d", w.Code) + } +} + +func TestDeleteVpc_BlockedByActiveSubnet(t *testing.T) { + s, db := newTestServer(t) + kv.AddInDB(db, "vpc/vpc-busy/state", "created") + kv.AddInDB(db, "subnet/sn-1/state", "created") + kv.AddInDB(db, "subnet/sn-1/vpc", "vpc-busy") + req := httptest.NewRequest(http.MethodDelete, "/vpcs/vpc-busy", nil) + w := httptest.NewRecorder() + s.VpcByNameHandler(w, req) + if w.Code != http.StatusNotFound { + t.Errorf("attendu 404 (Prepare échoue), obtenu %d: %s", w.Code, w.Body.String()) + } +} + +func TestVpcByName_InvalidMethod(t *testing.T) { + s, db := newTestServer(t) + kv.AddInDB(db, "vpc/vpc-1/state", "created") + req := httptest.NewRequest(http.MethodPut, "/vpcs/vpc-1", nil) + w := httptest.NewRecorder() + s.VpcByNameHandler(w, req) + if w.Code != http.StatusMethodNotAllowed { + t.Errorf("attendu 405, obtenu %d", w.Code) + } +}