diff --git a/internal/dispatcher/agent/dispatcher_test.go b/internal/dispatcher/agent/dispatcher_test.go index b4881d9..4475a82 100644 --- a/internal/dispatcher/agent/dispatcher_test.go +++ b/internal/dispatcher/agent/dispatcher_test.go @@ -4,8 +4,10 @@ import ( "errors" "sync" "testing" + "time" configuration "git.g3e.fr/syonad/two/internal/config/agent" + "git.g3e.fr/syonad/two/internal/state" "github.com/dgraph-io/badger/v4" ) @@ -61,3 +63,66 @@ func TestDispatcher_Dispatch_ExecuteErrorLogged(t *testing.T) { d.Dispatch(cmd) wg.Wait() // Execute s'est terminé — l'erreur est loggée, pas propagée } + +func TestDispatcher_Dispatch_ExecuteErrorSetsErrorState(t *testing.T) { + d, db := newTestDispatcher(t) + if err := state.Set(db, "vpc/vpc-1", state.Creating); err != nil { + t.Fatalf("préparation du test : %v", err) + } + + done := make(chan struct{}) + cmd := mockCmd{ + key: "vpc/vpc-1", + prepareFn: func(*badger.DB, *configuration.Config) error { return nil }, + executeFn: func(*badger.DB, *configuration.Config) error { + return errors.New("execute failed") + }, + } + d.Dispatch(cmd) + + // L'état est écrit après le retour d'Execute : on scrute la DB. + go func() { + defer close(done) + for { + if s, err := state.Get(db, "vpc/vpc-1"); err == nil && s == state.Error { + return + } + time.Sleep(5 * time.Millisecond) + } + }() + select { + case <-done: + case <-time.After(2 * time.Second): + s, _ := state.Get(db, "vpc/vpc-1") + t.Fatalf("la ressource devrait être en %q, obtenu %q", state.Error, s) + } +} + +func TestDispatcher_Dispatch_ExecuteSuccessKeepsState(t *testing.T) { + d, db := newTestDispatcher(t) + if err := state.Set(db, "vpc/vpc-1", state.Running); err != nil { + t.Fatalf("préparation du test : %v", err) + } + + var wg sync.WaitGroup + wg.Add(1) + cmd := mockCmd{ + key: "vpc/vpc-1", + prepareFn: func(*badger.DB, *configuration.Config) error { return nil }, + executeFn: func(*badger.DB, *configuration.Config) error { + defer wg.Done() + return nil + }, + } + d.Dispatch(cmd) + wg.Wait() + time.Sleep(50 * time.Millisecond) // laisse le temps d'une écriture parasite + + s, err := state.Get(db, "vpc/vpc-1") + if err != nil { + t.Fatalf("Get a échoué : %v", err) + } + if s != state.Running { + t.Errorf("l'état devrait rester %q, obtenu %q", state.Running, s) + } +} diff --git a/internal/dispatcher/agent/helpers_test.go b/internal/dispatcher/agent/helpers_test.go index 2cee6fc..c14f6a3 100644 --- a/internal/dispatcher/agent/helpers_test.go +++ b/internal/dispatcher/agent/helpers_test.go @@ -25,10 +25,18 @@ func newTestDispatcher(t *testing.T) (*Dispatcher, *badger.DB) { // mockCmd implémente Command sans aucune dépendance système. type mockCmd struct { + key string prepareFn func(*badger.DB, *configuration.Config) error executeFn func(*badger.DB, *configuration.Config) error } +func (m mockCmd) Key() string { + if m.key == "" { + return "vpc/mock" + } + return m.key +} + func (m mockCmd) Prepare(db *badger.DB, cfg *configuration.Config) error { return m.prepareFn(db, cfg) }