Files
dobotai-platform/internal/runtime/manager_test.go
2026-08-13 13:36:26 +08:00

89 lines
2.5 KiB
Go

package runtime
import (
"context"
"errors"
"sync"
"testing"
)
type fakeComponent struct {
mu sync.Mutex
running string
starts []string
stops int
fail map[string]error
}
func (f *fakeComponent) Start(_ context.Context, cfg Credentials) error {
f.mu.Lock()
defer f.mu.Unlock()
if f.running != "" {
return errors.New("already_running")
}
f.starts = append(f.starts, cfg.ClientID)
if err := f.fail[cfg.ClientID]; err != nil {
return err
}
f.running = cfg.ClientID
return nil
}
func (f *fakeComponent) Stop(context.Context) error {
f.mu.Lock()
defer f.mu.Unlock()
f.stops++
f.running = ""
return nil
}
func TestManagerSwitchesWithAtMostOneRuntime(t *testing.T) {
component := &fakeComponent{fail: map[string]error{}}
m := NewManager(component)
if err := m.Start(context.Background(), Credentials{ClientID: "old", Secret: "s1"}); err != nil {
t.Fatal(err)
}
if err := m.Switch(context.Background(), Credentials{ClientID: "new", Secret: "s2"}); err != nil {
t.Fatal(err)
}
if component.running != "new" || component.stops != 1 {
t.Fatalf("running=%q stops=%d", component.running, component.stops)
}
status := m.Status()
if status.ClientID != "new" || status.State != "running" {
t.Fatalf("status=%+v", status)
}
}
func TestManagerRollsBackWhenCandidateFails(t *testing.T) {
component := &fakeComponent{fail: map[string]error{"bad": errors.New("stream_failed")}}
m := NewManager(component)
if err := m.Start(context.Background(), Credentials{ClientID: "old", Secret: "s1"}); err != nil {
t.Fatal(err)
}
if err := m.Switch(context.Background(), Credentials{ClientID: "bad", Secret: "s2"}); err == nil {
t.Fatal("expected failure")
}
if component.running != "old" {
t.Fatalf("running=%q starts=%v", component.running, component.starts)
}
status := m.Status()
if status.State != "running" || status.LastCode != "candidate_start_failed_rolled_back" {
t.Fatalf("status=%+v", status)
}
}
func TestManagerReportsRollbackFailure(t *testing.T) {
component := &fakeComponent{fail: map[string]error{"bad": errors.New("bad"), "old": nil}}
m := NewManager(component)
if err := m.Start(context.Background(), Credentials{ClientID: "old", Secret: "s1"}); err != nil {
t.Fatal(err)
}
component.fail["old"] = errors.New("old_failed")
if err := m.Switch(context.Background(), Credentials{ClientID: "bad", Secret: "s2"}); err == nil {
t.Fatal("expected failure")
}
if m.Status().State != "error" || m.Status().LastCode != "rollback_failed" {
t.Fatalf("status=%+v", m.Status())
}
}