diff --git a/internal/api/agent/helpers_test.go b/internal/api/agent/helpers_test.go deleted file mode 100644 index 206874c..0000000 --- a/internal/api/agent/helpers_test.go +++ /dev/null @@ -1,27 +0,0 @@ -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.go b/internal/api/agent/subnet.go index 31eddc8..556b7c3 100644 --- a/internal/api/agent/subnet.go +++ b/internal/api/agent/subnet.go @@ -69,12 +69,7 @@ func (s *Server) deleteSubnet(w http.ResponseWriter, _ *http.Request, name strin return } s.dispatcher.Dispatch(cmd) - state, err := kv.GetFromDB(s.db, "subnet/"+name+"/state") - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - json.NewEncoder(w).Encode(ErrorResponse{Error: "failed to read subnet state"}) - return - } + state, _ := kv.GetFromDB(s.db, "subnet/"+name+"/state") w.WriteHeader(http.StatusAccepted) json.NewEncoder(w).Encode(Subnet{Name: name, State: state}) } diff --git a/internal/api/agent/subnet_test.go b/internal/api/agent/subnet_test.go deleted file mode 100644 index 0fdd18b..0000000 --- a/internal/api/agent/subnet_test.go +++ /dev/null @@ -1,234 +0,0 @@ -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/subnets.go b/internal/api/agent/subnets.go index b0fb2fb..9e99a45 100644 --- a/internal/api/agent/subnets.go +++ b/internal/api/agent/subnets.go @@ -88,12 +88,7 @@ func (s *Server) postSubnet(w http.ResponseWriter, r *http.Request) { return } s.dispatcher.Dispatch(cmd) - entries, err := kv.ListByPrefix(s.db, "subnet/"+req.Name+"/") - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - json.NewEncoder(w).Encode(ErrorResponse{Error: "failed to read subnet state"}) - return - } + entries, _ := kv.ListByPrefix(s.db, "subnet/"+req.Name+"/") sub := Subnet{Name: req.Name} for key, value := range entries { parts := strings.Split(key, "/") diff --git a/internal/api/agent/vpc.go b/internal/api/agent/vpc.go index df2d47c..be0724e 100644 --- a/internal/api/agent/vpc.go +++ b/internal/api/agent/vpc.go @@ -47,12 +47,7 @@ func (s *Server) deleteVpc(w http.ResponseWriter, _ *http.Request, name string) return } s.dispatcher.Dispatch(cmd) - state, err := kv.GetFromDB(s.db, "vpc/"+name+"/state") - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - json.NewEncoder(w).Encode(ErrorResponse{Error: "failed to read vpc state"}) - return - } + state, _ := kv.GetFromDB(s.db, "vpc/"+name+"/state") w.WriteHeader(http.StatusAccepted) json.NewEncoder(w).Encode(VPC{Name: name, State: state}) } diff --git a/internal/api/agent/vpc_test.go b/internal/api/agent/vpc_test.go deleted file mode 100644 index 0edcd5b..0000000 --- a/internal/api/agent/vpc_test.go +++ /dev/null @@ -1,188 +0,0 @@ -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) - } -} diff --git a/internal/api/agent/vpcs.go b/internal/api/agent/vpcs.go index b062d1c..3087456 100644 --- a/internal/api/agent/vpcs.go +++ b/internal/api/agent/vpcs.go @@ -69,12 +69,7 @@ func (s *Server) postVpc(w http.ResponseWriter, r *http.Request) { return } s.dispatcher.Dispatch(cmd) - state, err := kv.GetFromDB(s.db, "vpc/"+req.Name+"/state") - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - json.NewEncoder(w).Encode(ErrorResponse{Error: "failed to read vpc state"}) - return - } + state, _ := kv.GetFromDB(s.db, "vpc/"+req.Name+"/state") w.WriteHeader(http.StatusAccepted) json.NewEncoder(w).Encode(VPC{Name: req.Name, State: state}) } diff --git a/internal/dispatcher/agent/dispatcher_test.go b/internal/dispatcher/agent/dispatcher_test.go deleted file mode 100644 index b4881d9..0000000 --- a/internal/dispatcher/agent/dispatcher_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package dispatcher - -import ( - "errors" - "sync" - "testing" - - configuration "git.g3e.fr/syonad/two/internal/config/agent" - "github.com/dgraph-io/badger/v4" -) - -func TestDispatcher_Prepare_Success(t *testing.T) { - d, _ := newTestDispatcher(t) - cmd := mockCmd{ - prepareFn: func(*badger.DB, *configuration.Config) error { return nil }, - executeFn: func(*badger.DB, *configuration.Config) error { return nil }, - } - if err := d.Prepare(cmd); err != nil { - t.Errorf("Prepare devrait retourner nil, obtenu : %v", err) - } -} - -func TestDispatcher_Prepare_PropagatesError(t *testing.T) { - d, _ := newTestDispatcher(t) - want := errors.New("prepare failed") - cmd := mockCmd{ - prepareFn: func(*badger.DB, *configuration.Config) error { return want }, - executeFn: func(*badger.DB, *configuration.Config) error { return nil }, - } - if err := d.Prepare(cmd); !errors.Is(err, want) { - t.Errorf("attendu %v, obtenu %v", want, err) - } -} - -func TestDispatcher_Dispatch_ExecutesCommand(t *testing.T) { - d, _ := newTestDispatcher(t) - var wg sync.WaitGroup - wg.Add(1) - cmd := mockCmd{ - prepareFn: func(*badger.DB, *configuration.Config) error { return nil }, - executeFn: func(*badger.DB, *configuration.Config) error { - wg.Done() - return nil - }, - } - d.Dispatch(cmd) - wg.Wait() -} - -func TestDispatcher_Dispatch_ExecuteErrorLogged(t *testing.T) { - d, _ := newTestDispatcher(t) - var wg sync.WaitGroup - wg.Add(1) - cmd := mockCmd{ - prepareFn: func(*badger.DB, *configuration.Config) error { return nil }, - executeFn: func(*badger.DB, *configuration.Config) error { - defer wg.Done() - return errors.New("execute failed") - }, - } - d.Dispatch(cmd) - wg.Wait() // Execute s'est terminé — l'erreur est loggée, pas propagée -} diff --git a/internal/dispatcher/agent/helpers_test.go b/internal/dispatcher/agent/helpers_test.go deleted file mode 100644 index 2cee6fc..0000000 --- a/internal/dispatcher/agent/helpers_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package dispatcher - -import ( - "io" - "log/slog" - "testing" - - configuration "git.g3e.fr/syonad/two/internal/config/agent" - "git.g3e.fr/syonad/two/pkg/db/kv" - "git.g3e.fr/syonad/two/pkg/worker" - "github.com/dgraph-io/badger/v4" -) - -func newTestDispatcher(t *testing.T) (*Dispatcher, *badger.DB) { - t.Helper() - db := kv.InitDB(kv.Config{Path: t.TempDir()}, false) - t.Cleanup(func() { db.Close() }) - q := worker.New(100) - q.Start(2) - cfg := &configuration.Config{DefaultInterface: "br-default"} - cfg.Interfaces = map[string]string{"vms": "br-vms"} - logger := slog.New(slog.NewTextHandler(io.Discard, nil)) - return New(q, db, cfg, logger), db -} - -// mockCmd implémente Command sans aucune dépendance système. -type mockCmd struct { - prepareFn func(*badger.DB, *configuration.Config) error - executeFn func(*badger.DB, *configuration.Config) error -} - -func (m mockCmd) Prepare(db *badger.DB, cfg *configuration.Config) error { - return m.prepareFn(db, cfg) -} - -func (m mockCmd) Execute(db *badger.DB, cfg *configuration.Config) error { - return m.executeFn(db, cfg) -} diff --git a/internal/dispatcher/agent/subnet_commands_test.go b/internal/dispatcher/agent/subnet_commands_test.go deleted file mode 100644 index 3b118bc..0000000 --- a/internal/dispatcher/agent/subnet_commands_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package dispatcher - -import ( - "testing" - - configuration "git.g3e.fr/syonad/two/internal/config/agent" - "git.g3e.fr/syonad/two/pkg/db/kv" -) - -func testCfg() *configuration.Config { - cfg := &configuration.Config{DefaultInterface: "br-default"} - cfg.Interfaces = map[string]string{"vms": "br-vms"} - return cfg -} - -// --- CreateSubnetCommand.Prepare --- - -func TestCreateSubnetCommand_Prepare_Success(t *testing.T) { - _, db := newTestDispatcher(t) - kv.AddInDB(db, "vpc/vpc-1/state", "created") - cmd := CreateSubnetCommand{ - Name: "sn-1", VPC: "vpc-1", VxlanID: 100, - IfaceType: "vms", GatewayIP: "10.0.0.1", CIDR: "10.0.0.0/24", - } - if err := cmd.Prepare(db, testCfg()); err != nil { - t.Fatalf("Prepare a échoué : %v", err) - } - state, _ := kv.GetFromDB(db, "subnet/sn-1/state") - if state != "creating" { - t.Errorf("state attendu creating, obtenu %q", state) - } - vpc, _ := kv.GetFromDB(db, "subnet/sn-1/vpc") - if vpc != "vpc-1" { - t.Errorf("vpc attendu vpc-1, obtenu %q", vpc) - } -} - -func TestCreateSubnetCommand_Prepare_UsesIfaceTypeMapping(t *testing.T) { - _, db := newTestDispatcher(t) - kv.AddInDB(db, "vpc/vpc-1/state", "created") - cmd := CreateSubnetCommand{ - Name: "sn-1", VPC: "vpc-1", VxlanID: 100, - IfaceType: "vms", GatewayIP: "10.0.0.1", CIDR: "10.0.0.0/24", - } - cmd.Prepare(db, testCfg()) - iface, _ := kv.GetFromDB(db, "subnet/sn-1/local_iface") - if iface != "br-vms" { - t.Errorf("local_iface attendu br-vms, obtenu %q", iface) - } -} - -func TestCreateSubnetCommand_Prepare_UsesDefaultIfaceWhenTypeUnknown(t *testing.T) { - _, db := newTestDispatcher(t) - kv.AddInDB(db, "vpc/vpc-1/state", "created") - cmd := CreateSubnetCommand{ - Name: "sn-1", VPC: "vpc-1", VxlanID: 100, - IfaceType: "inconnu", GatewayIP: "10.0.0.1", CIDR: "10.0.0.0/24", - } - cmd.Prepare(db, testCfg()) - iface, _ := kv.GetFromDB(db, "subnet/sn-1/local_iface") - if iface != "br-default" { - t.Errorf("local_iface attendu br-default, obtenu %q", iface) - } -} - -func TestCreateSubnetCommand_Prepare_Duplicate(t *testing.T) { - _, db := newTestDispatcher(t) - kv.AddInDB(db, "vpc/vpc-1/state", "created") - kv.AddInDB(db, "subnet/sn-exist/state", "created") - cmd := CreateSubnetCommand{ - Name: "sn-exist", VPC: "vpc-1", VxlanID: 100, - IfaceType: "vms", GatewayIP: "10.0.0.1", CIDR: "10.0.0.0/24", - } - if err := cmd.Prepare(db, testCfg()); err == nil { - t.Error("Prepare devrait échouer sur un subnet déjà existant") - } -} - -func TestCreateSubnetCommand_Prepare_VPCNotFound(t *testing.T) { - _, db := newTestDispatcher(t) - cmd := CreateSubnetCommand{ - Name: "sn-1", VPC: "vpc-inexistant", VxlanID: 100, - IfaceType: "vms", GatewayIP: "10.0.0.1", CIDR: "10.0.0.0/24", - } - if err := cmd.Prepare(db, testCfg()); err == nil { - t.Error("Prepare devrait échouer si le VPC n'existe pas") - } -} - -func TestCreateSubnetCommand_Prepare_VPCDeleting(t *testing.T) { - _, db := newTestDispatcher(t) - kv.AddInDB(db, "vpc/vpc-dying/state", "deleting") - cmd := CreateSubnetCommand{ - Name: "sn-1", VPC: "vpc-dying", VxlanID: 100, - IfaceType: "vms", GatewayIP: "10.0.0.1", CIDR: "10.0.0.0/24", - } - if err := cmd.Prepare(db, testCfg()); err == nil { - t.Error("Prepare devrait échouer si le VPC est en cours de suppression") - } -} - -func TestCreateSubnetCommand_Prepare_VPCDeleted(t *testing.T) { - _, db := newTestDispatcher(t) - kv.AddInDB(db, "vpc/vpc-gone/state", "deleted") - cmd := CreateSubnetCommand{ - Name: "sn-1", VPC: "vpc-gone", VxlanID: 100, - IfaceType: "vms", GatewayIP: "10.0.0.1", CIDR: "10.0.0.0/24", - } - if err := cmd.Prepare(db, testCfg()); err == nil { - t.Error("Prepare devrait échouer si le VPC est supprimé") - } -} - -// --- DeleteSubnetCommand.Prepare --- - -func TestDeleteSubnetCommand_Prepare_Success(t *testing.T) { - _, db := newTestDispatcher(t) - kv.AddInDB(db, "subnet/sn-del/state", "created") - cmd := DeleteSubnetCommand{Name: "sn-del"} - if err := cmd.Prepare(db, nil); err != nil { - t.Fatalf("Prepare a échoué : %v", err) - } - state, _ := kv.GetFromDB(db, "subnet/sn-del/state") - if state != "deleting" { - t.Errorf("state attendu deleting, obtenu %q", state) - } -} - -func TestDeleteSubnetCommand_Prepare_NotFound(t *testing.T) { - _, db := newTestDispatcher(t) - cmd := DeleteSubnetCommand{Name: "sn-inexistant"} - if err := cmd.Prepare(db, nil); err == nil { - t.Error("Prepare devrait échouer si le subnet n'existe pas") - } -} diff --git a/internal/dispatcher/agent/vpc_commands_test.go b/internal/dispatcher/agent/vpc_commands_test.go deleted file mode 100644 index e2f2162..0000000 --- a/internal/dispatcher/agent/vpc_commands_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package dispatcher - -import ( - "testing" - - "git.g3e.fr/syonad/two/pkg/db/kv" -) - -// --- CreateVPCCommand.Prepare --- - -func TestCreateVPCCommand_Prepare_NewVPC(t *testing.T) { - _, db := newTestDispatcher(t) - cmd := CreateVPCCommand{Name: "vpc-1"} - if err := cmd.Prepare(db, nil); err != nil { - t.Fatalf("Prepare a échoué : %v", err) - } - state, err := kv.GetFromDB(db, "vpc/vpc-1/state") - if err != nil { - t.Fatalf("état non écrit en DB : %v", err) - } - if state != "creating" { - t.Errorf("state attendu creating, obtenu %q", state) - } -} - -func TestCreateVPCCommand_Prepare_Duplicate(t *testing.T) { - _, db := newTestDispatcher(t) - kv.AddInDB(db, "vpc/vpc-exist/state", "created") - cmd := CreateVPCCommand{Name: "vpc-exist"} - if err := cmd.Prepare(db, nil); err == nil { - t.Error("Prepare devrait échouer sur un VPC déjà existant") - } -} - -// --- DeleteVPCCommand.Prepare --- - -func TestDeleteVPCCommand_Prepare_Success(t *testing.T) { - _, db := newTestDispatcher(t) - kv.AddInDB(db, "vpc/vpc-del/state", "created") - cmd := DeleteVPCCommand{Name: "vpc-del"} - if err := cmd.Prepare(db, nil); err != nil { - t.Fatalf("Prepare a échoué : %v", err) - } - state, _ := kv.GetFromDB(db, "vpc/vpc-del/state") - if state != "deleting" { - t.Errorf("state attendu deleting, obtenu %q", state) - } -} - -func TestDeleteVPCCommand_Prepare_NotFound(t *testing.T) { - _, db := newTestDispatcher(t) - cmd := DeleteVPCCommand{Name: "vpc-inexistant"} - if err := cmd.Prepare(db, nil); err == nil { - t.Error("Prepare devrait échouer si le VPC n'existe pas") - } -} - -func TestDeleteVPCCommand_Prepare_BlockedByActiveSubnet(t *testing.T) { - _, db := newTestDispatcher(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") - cmd := DeleteVPCCommand{Name: "vpc-busy"} - if err := cmd.Prepare(db, nil); err == nil { - t.Error("Prepare devrait échouer si un subnet actif existe") - } -} - -func TestDeleteVPCCommand_Prepare_AllowedWhenSubnetDeleted(t *testing.T) { - _, db := newTestDispatcher(t) - kv.AddInDB(db, "vpc/vpc-ok/state", "created") - kv.AddInDB(db, "subnet/sn-1/state", "deleted") - kv.AddInDB(db, "subnet/sn-1/vpc", "vpc-ok") - cmd := DeleteVPCCommand{Name: "vpc-ok"} - if err := cmd.Prepare(db, nil); err != nil { - t.Fatalf("Prepare devrait réussir si le subnet est deleted : %v", err) - } -} - -func TestDeleteVPCCommand_Prepare_AllowedWhenSubnetDeleting(t *testing.T) { - _, db := newTestDispatcher(t) - kv.AddInDB(db, "vpc/vpc-ok/state", "created") - kv.AddInDB(db, "subnet/sn-1/state", "deleting") - kv.AddInDB(db, "subnet/sn-1/vpc", "vpc-ok") - cmd := DeleteVPCCommand{Name: "vpc-ok"} - if err := cmd.Prepare(db, nil); err != nil { - t.Fatalf("Prepare devrait réussir si le subnet est deleting : %v", err) - } -} diff --git a/internal/ebtables/ebtables.go b/internal/ebtables/ebtables.go deleted file mode 100644 index 31e276b..0000000 --- a/internal/ebtables/ebtables.go +++ /dev/null @@ -1,58 +0,0 @@ -package ebtables - -import ( - "fmt" - "os/exec" -) - -func addRule(args ...string) error { - return exec.Command("ebtables", append([]string{"-A"}, args...)...).Run() -} - -func deleteRule(args ...string) error { - return exec.Command("ebtables", append([]string{"-D"}, args...)...).Run() -} - -func DropARPToGateway(bridge, gatewayIP string) error { - if err := addRule("FORWARD", - "--out-interface", bridge, - "-p", "arp", - "--arp-op", "Request", - "--arp-ip-dst", gatewayIP, - "-j", "DROP"); err != nil { - return fmt.Errorf("ebtables arp rule: %w", err) - } - return nil -} - -func DropDHCP(bridge string) error { - if err := addRule("FORWARD", - "--out-interface", bridge, - "-p", "IPv4", - "--ip-protocol", "udp", - "--ip-source-port", "67:68", - "--ip-destination-port", "67:68", - "-j", "DROP"); err != nil { - return fmt.Errorf("ebtables dhcp rule: %w", err) - } - return nil -} - -func DeleteARPToGateway(bridge, gatewayIP string) error { - return deleteRule("FORWARD", - "--out-interface", bridge, - "-p", "arp", - "--arp-op", "Request", - "--arp-ip-dst", gatewayIP, - "-j", "DROP") -} - -func DeleteDHCP(bridge string) error { - return deleteRule("FORWARD", - "--out-interface", bridge, - "-p", "IPv4", - "--ip-protocol", "udp", - "--ip-source-port", "67:68", - "--ip-destination-port", "67:68", - "-j", "DROP") -} diff --git a/internal/metadata/metadata_test.go b/internal/metadata/metadata_test.go index 5884151..62a2830 100644 --- a/internal/metadata/metadata_test.go +++ b/internal/metadata/metadata_test.go @@ -1,8 +1,6 @@ package metadata import ( - "net/http" - "net/http/httptest" "strings" "testing" @@ -183,103 +181,6 @@ func TestUnLoadNoCloudInDB_RemovesAllKeys(t *testing.T) { } } -// --- getIP --- - -func TestGetIP_ValidHostPort(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, "/", nil) - req.RemoteAddr = "10.0.0.1:4567" - if ip := getIP(req); ip != "10.0.0.1" { - t.Errorf("attendu 10.0.0.1, obtenu %q", ip) - } -} - -func TestGetIP_IPv6(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, "/", nil) - req.RemoteAddr = "[::1]:8080" - if ip := getIP(req); ip != "::1" { - t.Errorf("attendu ::1, obtenu %q", ip) - } -} - -func TestGetIP_NoPort(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, "/", nil) - req.RemoteAddr = "10.0.0.1" - if ip := getIP(req); ip != "10.0.0.1" { - t.Errorf("attendu RemoteAddr brut, obtenu %q", ip) - } -} - -// --- rootHandler --- - -func TestRootHandler_UserData(t *testing.T) { - data = NoCloudData{UserData: "userdata-content"} - w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/user-data", nil) - rootHandler(w, req) - if w.Code != http.StatusOK { - t.Errorf("attendu 200, obtenu %d", w.Code) - } - if body := w.Body.String(); body != "userdata-content" { - t.Errorf("body inattendu : %q", body) - } -} - -func TestRootHandler_MetaData(t *testing.T) { - data = NoCloudData{MetaData: "metadata-content"} - w := httptest.NewRecorder() - rootHandler(w, httptest.NewRequest(http.MethodGet, "/meta-data", nil)) - if w.Code != http.StatusOK { - t.Errorf("attendu 200, obtenu %d", w.Code) - } - if body := w.Body.String(); body != "metadata-content" { - t.Errorf("body inattendu : %q", body) - } -} - -func TestRootHandler_NetworkConfig(t *testing.T) { - data = NoCloudData{NetworkConfig: "network-content"} - w := httptest.NewRecorder() - rootHandler(w, httptest.NewRequest(http.MethodGet, "/network-config", nil)) - if w.Code != http.StatusOK { - t.Errorf("attendu 200, obtenu %d", w.Code) - } - if body := w.Body.String(); body != "network-content" { - t.Errorf("body inattendu : %q", body) - } -} - -func TestRootHandler_VendorData(t *testing.T) { - data = NoCloudData{VendorData: "vendor-content"} - w := httptest.NewRecorder() - rootHandler(w, httptest.NewRequest(http.MethodGet, "/vendor-data", nil)) - if w.Code != http.StatusOK { - t.Errorf("attendu 200, obtenu %d", w.Code) - } - if body := w.Body.String(); body != "vendor-content" { - t.Errorf("body inattendu : %q", body) - } -} - -func TestRootHandler_UnknownPath(t *testing.T) { - data = NoCloudData{} - w := httptest.NewRecorder() - rootHandler(w, httptest.NewRequest(http.MethodGet, "/unknown", nil)) - if w.Code != http.StatusNotFound { - t.Errorf("attendu 404, obtenu %d", w.Code) - } -} - -func TestRootHandler_ContentType(t *testing.T) { - data = NoCloudData{MetaData: "x"} - w := httptest.NewRecorder() - rootHandler(w, httptest.NewRequest(http.MethodGet, "/meta-data", nil)) - if ct := w.Header().Get("Content-Type"); ct != "text/yaml" { - t.Errorf("Content-Type attendu text/yaml, obtenu %q", ct) - } -} - -// --- UnLoadNoCloudInDB_DoesNotAffectOtherVMs --- - func TestUnLoadNoCloudInDB_DoesNotAffectOtherVMs(t *testing.T) { db := kv.InitDB(kv.Config{Path: t.TempDir()}, false) t.Cleanup(func() { db.Close() }) diff --git a/internal/netif/vxlan.go b/internal/netif/vxlan.go index 70740bc..6523f37 100644 --- a/internal/netif/vxlan.go +++ b/internal/netif/vxlan.go @@ -4,7 +4,7 @@ import ( "github.com/vishvananda/netlink" ) -func CreateVxlan(name string, vxlanID int, localIface string, mtu int) error { +func CreateVxlan(name string, vxlanID int, localIface string) error { link, err := netlink.LinkByName(localIface) if err != nil { return err @@ -12,7 +12,6 @@ func CreateVxlan(name string, vxlanID int, localIface string, mtu int) error { vxlan := &netlink.Vxlan{ LinkAttrs: netlink.LinkAttrs{ Name: name, - MTU: mtu, }, VxlanId: vxlanID, Port: 4789, diff --git a/internal/subnet/create.go b/internal/subnet/create.go index 496c257..1ed514e 100644 --- a/internal/subnet/create.go +++ b/internal/subnet/create.go @@ -3,11 +3,11 @@ package subnet import ( "fmt" "net" + "os/exec" "strconv" "strings" "git.g3e.fr/syonad/two/internal/dhcp" - "git.g3e.fr/syonad/two/internal/ebtables" "git.g3e.fr/syonad/two/internal/netif" "git.g3e.fr/syonad/two/internal/netns" "git.g3e.fr/syonad/two/pkg/db/kv" @@ -86,7 +86,7 @@ func CreateSubnet(db *badger.DB, subnetName string) error { } // vxlan - if err := netif.CreateVxlan(vxlanIface, vxlanID, localIface, 1500); err != nil { + if err := netif.CreateVxlan(vxlanIface, vxlanID, localIface); err != nil { return fmt.Errorf("create vxlan: %w", err) } @@ -136,11 +136,25 @@ func CreateSubnet(db *badger.DB, subnetName string) error { return fmt.Errorf("add route in netns: %w", err) } - if err := ebtables.DropARPToGateway(bridge, gatewayIP.String()); err != nil { - return err + // ebtables : drop ARP Request vers la gateway sur ce bridge + if err := exec.Command("ebtables", "-A", "FORWARD", + "--out-interface", bridge, + "-p", "arp", + "--arp-op", "Request", + "--arp-ip-dst", gatewayIP.String(), + "-j", "DROP").Run(); err != nil { + return fmt.Errorf("ebtables arp rule: %w", err) } - if err := ebtables.DropDHCP(bridge); err != nil { - return err + + // ebtables : drop trafic DHCP sur ce bridge + if err := exec.Command("ebtables", "-A", "FORWARD", + "--out-interface", bridge, + "-p", "IPv4", + "--ip-protocol", "udp", + "--ip-source-port", "67:68", + "--ip-destination-port", "67:68", + "-j", "DROP").Run(); err != nil { + return fmt.Errorf("ebtables dhcp rule: %w", err) } // génération de la config dnsmasq et démarrage du service diff --git a/internal/subnet/delete.go b/internal/subnet/delete.go index 8ce91ff..650aa6f 100644 --- a/internal/subnet/delete.go +++ b/internal/subnet/delete.go @@ -3,9 +3,9 @@ package subnet import ( "fmt" "os" + "os/exec" "strings" - "git.g3e.fr/syonad/two/internal/ebtables" "git.g3e.fr/syonad/two/internal/netif" "git.g3e.fr/syonad/two/internal/netns" "git.g3e.fr/syonad/two/pkg/db/kv" @@ -33,11 +33,6 @@ func DeleteSubnet(db *badger.DB, subnetName string) error { return fmt.Errorf("get vxlan_id: %w", err) } - gatewayIP, err := kv.GetFromDB(db, "subnet/"+subnetName+"/gateway_ip") - if err != nil { - return fmt.Errorf("get gateway_ip: %w", err) - } - subnetID := strings.SplitN(subnetName, "-", 2)[1] bridge := "br-" + subnetID vxlanIface := "vxlan-" + vxlanIDStr @@ -60,12 +55,19 @@ func DeleteSubnet(db *badger.DB, subnetName string) error { } // suppression des règles ebtables - if err := ebtables.DeleteARPToGateway(bridge, gatewayIP); err != nil { - return fmt.Errorf("delete ebtables arp rule: %w", err) - } - if err := ebtables.DeleteDHCP(bridge); err != nil { - return fmt.Errorf("delete ebtables dhcp rule: %w", err) - } + exec.Command("ebtables", "-D", "FORWARD", + "--out-interface", bridge, + "-p", "arp", + "--arp-op", "Request", + "-j", "DROP").Run() + + exec.Command("ebtables", "-D", "FORWARD", + "--out-interface", bridge, + "-p", "IPv4", + "--ip-protocol", "udp", + "--ip-source-port", "67:68", + "--ip-destination-port", "67:68", + "-j", "DROP").Run() // suppression du bridge dans le netns VPC if err := netns.Call(vpcName, func() error { diff --git a/pkg/db/kv/deleteInDB.go b/pkg/db/kv/deleteInDB.go index e1335de..1943c81 100644 --- a/pkg/db/kv/deleteInDB.go +++ b/pkg/db/kv/deleteInDB.go @@ -1,6 +1,8 @@ package kv import ( + "log" + "github.com/dgraph-io/badger/v4" ) @@ -33,7 +35,7 @@ func DeleteInDB(db *badger.DB, key string) error { return nil }) if err != nil { - return err + log.Fatal(err) } return deleteKey(db, key) diff --git a/pkg/db/kv/kv_test.go b/pkg/db/kv/kv_test.go index b45422b..de4fd68 100644 --- a/pkg/db/kv/kv_test.go +++ b/pkg/db/kv/kv_test.go @@ -151,69 +151,3 @@ func TestDeleteInDB_MissingKey(t *testing.T) { t.Logf("DeleteInDB clé inexistante retourne : %v (non bloquant)", err) } } - -// --- ListByPrefix --- - -func TestListByPrefix_MatchingKeys(t *testing.T) { - db := newTestDB(t) - AddInDB(db, "subnet/sn1/state", "created") - AddInDB(db, "subnet/sn1/vpc", "vpc-1") - AddInDB(db, "subnet/sn1/cidr", "10.0.0.0/24") - - entries, err := ListByPrefix(db, "subnet/sn1/") - if err != nil { - t.Fatalf("ListByPrefix a échoué : %v", err) - } - if len(entries) != 3 { - t.Fatalf("attendu 3 entrées, obtenu %d", len(entries)) - } - if entries["subnet/sn1/state"] != "created" { - t.Errorf("valeur inattendue pour state : %q", entries["subnet/sn1/state"]) - } - if entries["subnet/sn1/vpc"] != "vpc-1" { - t.Errorf("valeur inattendue pour vpc : %q", entries["subnet/sn1/vpc"]) - } -} - -func TestListByPrefix_NoMatch(t *testing.T) { - db := newTestDB(t) - AddInDB(db, "vpc/v1/state", "created") - - entries, err := ListByPrefix(db, "subnet/") - if err != nil { - t.Fatalf("ListByPrefix a échoué : %v", err) - } - if len(entries) != 0 { - t.Errorf("attendu 0 entrées, obtenu %d", len(entries)) - } -} - -func TestListByPrefix_IsolatesPrefix(t *testing.T) { - db := newTestDB(t) - AddInDB(db, "subnet/sn1/state", "created") - AddInDB(db, "subnet/sn2/state", "creating") - AddInDB(db, "vpc/v1/state", "created") - - entries, err := ListByPrefix(db, "subnet/sn1/") - if err != nil { - t.Fatalf("ListByPrefix a échoué : %v", err) - } - if len(entries) != 1 { - t.Errorf("attendu 1 entrée, obtenu %d : %v", len(entries), entries) - } - if _, ok := entries["subnet/sn1/state"]; !ok { - t.Error("subnet/sn1/state devrait être présent") - } -} - -func TestListByPrefix_EmptyDB(t *testing.T) { - db := newTestDB(t) - - entries, err := ListByPrefix(db, "subnet/") - if err != nil { - t.Fatalf("ListByPrefix a échoué : %v", err) - } - if len(entries) != 0 { - t.Errorf("attendu 0 entrées sur DB vide, obtenu %d", len(entries)) - } -} diff --git a/pkg/worker/queue_test.go b/pkg/worker/queue_test.go deleted file mode 100644 index 5353b06..0000000 --- a/pkg/worker/queue_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package worker - -import ( - "sync" - "sync/atomic" - "testing" - "time" -) - -func TestNew_ReturnsQueue(t *testing.T) { - q := New(10) - if q == nil { - t.Fatal("New devrait retourner une queue non-nil") - } -} - -func TestQueue_SingleTaskExecuted(t *testing.T) { - q := New(1) - q.Start(1) - - var done atomic.Bool - var wg sync.WaitGroup - wg.Add(1) - q.Submit(func() { - done.Store(true) - wg.Done() - }) - - wg.Wait() - if !done.Load() { - t.Error("la tâche n'a pas été exécutée") - } -} - -func TestQueue_AllTasksExecuted(t *testing.T) { - const n = 50 - q := New(n) - q.Start(1) - - var count atomic.Int32 - var wg sync.WaitGroup - wg.Add(n) - for range n { - q.Submit(func() { - count.Add(1) - wg.Done() - }) - } - - wg.Wait() - if count.Load() != n { - t.Errorf("attendu %d exécutions, obtenu %d", n, count.Load()) - } -} - -func TestQueue_MultipleWorkers(t *testing.T) { - const n = 100 - q := New(n) - q.Start(4) - - var count atomic.Int32 - var wg sync.WaitGroup - wg.Add(n) - for range n { - q.Submit(func() { - count.Add(1) - wg.Done() - }) - } - - wg.Wait() - if count.Load() != n { - t.Errorf("attendu %d exécutions, obtenu %d", n, count.Load()) - } -} - -func TestQueue_SubmitBlocksWhenFull(t *testing.T) { - q := New(1) - // Remplit le buffer sans worker - q.Submit(func() {}) - - submitted := make(chan struct{}) - go func() { - q.Submit(func() {}) // doit bloquer jusqu'à ce qu'un worker consomme - close(submitted) - }() - - select { - case <-submitted: - t.Error("Submit aurait dû bloquer sur une queue pleine") - case <-time.After(50 * time.Millisecond): - // comportement attendu : goroutine bloquée - } - - // Démarre un worker pour débloquer - q.Start(1) - select { - case <-submitted: - // Submit a pu avancer - case <-time.After(time.Second): - t.Error("Submit aurait dû se débloquer après démarrage d'un worker") - } -}