package agent import ( "context" "testing" mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1" "git.wntrmute.dev/mc/mcp/internal/registry" "git.wntrmute.dev/mc/mcp/internal/runtime" ) func TestAdoptContainers(t *testing.T) { rt := &fakeRuntime{ containers: []runtime.ContainerInfo{ { Name: "metacrypt-api", Image: "mcr.svc.mcp.metacircular.net:8443/metacrypt:v1.0.0", State: "running", Network: "docker_default", User: "0:0", Restart: "unless-stopped", Ports: []string{"127.0.0.1:18443:8443"}, Volumes: []string{"/srv/metacrypt:/srv/metacrypt"}, Cmd: []string{"server"}, Version: "v1.0.0", }, { Name: "metacrypt-web", Image: "mcr.svc.mcp.metacircular.net:8443/metacrypt-web:v1.0.0", State: "running", Network: "docker_default", Restart: "unless-stopped", Ports: []string{"127.0.0.1:18080:8080"}, Version: "v1.0.0", }, { Name: "unrelated-container", Image: "nginx:latest", State: "running", }, }, } a := newTestAgent(t, rt) ctx := context.Background() resp, err := a.AdoptContainers(ctx, &mcpv1.AdoptContainersRequest{Service: "metacrypt"}) if err != nil { t.Fatalf("AdoptContainers: %v", err) } if len(resp.Results) != 2 { t.Fatalf("expected 2 results, got %d", len(resp.Results)) } for _, r := range resp.Results { if !r.Success { t.Fatalf("expected success for %q, got error: %s", r.Container, r.Error) } } // Verify components were created in the registry. components, err := registry.ListComponents(a.DB, "metacrypt") if err != nil { t.Fatalf("ListComponents: %v", err) } if len(components) != 2 { t.Fatalf("expected 2 components, got %d", len(components)) } api, err := registry.GetComponent(a.DB, "metacrypt", "api") if err != nil { t.Fatalf("GetComponent api: %v", err) } if api.Image != "mcr.svc.mcp.metacircular.net:8443/metacrypt:v1.0.0" { t.Fatalf("api image: got %q", api.Image) } if api.DesiredState != "running" { t.Fatalf("api desired state: got %q, want running", api.DesiredState) } if api.Network != "docker_default" { t.Fatalf("api network: got %q, want docker_default", api.Network) } } func TestAdoptContainersNoMatch(t *testing.T) { rt := &fakeRuntime{ containers: []runtime.ContainerInfo{ {Name: "unrelated", Image: "nginx:latest", State: "running"}, }, } a := newTestAgent(t, rt) ctx := context.Background() resp, err := a.AdoptContainers(ctx, &mcpv1.AdoptContainersRequest{Service: "metacrypt"}) if err != nil { t.Fatalf("AdoptContainers: %v", err) } if len(resp.Results) != 0 { t.Fatalf("expected 0 results, got %d", len(resp.Results)) } } func TestAdoptContainersAlreadyManaged(t *testing.T) { rt := &fakeRuntime{ containers: []runtime.ContainerInfo{ { Name: "metacrypt-api", Image: "mcr.svc.mcp.metacircular.net:8443/metacrypt:v1.0.0", State: "running", Network: "docker_default", Restart: "unless-stopped", Version: "v1.0.0", }, }, } a := newTestAgent(t, rt) ctx := context.Background() // First adopt succeeds. resp, err := a.AdoptContainers(ctx, &mcpv1.AdoptContainersRequest{Service: "metacrypt"}) if err != nil { t.Fatalf("first adopt: %v", err) } if len(resp.Results) != 1 || !resp.Results[0].Success { t.Fatalf("first adopt should succeed: %+v", resp.Results) } // Second adopt should report "already managed". resp, err = a.AdoptContainers(ctx, &mcpv1.AdoptContainersRequest{Service: "metacrypt"}) if err != nil { t.Fatalf("second adopt: %v", err) } if len(resp.Results) != 1 { t.Fatalf("expected 1 result, got %d", len(resp.Results)) } if resp.Results[0].Success { t.Fatal("second adopt should fail for already-managed container") } if resp.Results[0].Error != "already managed" { t.Fatalf("expected 'already managed' error, got %q", resp.Results[0].Error) } } func TestAdoptContainersSingleComponent(t *testing.T) { rt := &fakeRuntime{ containers: []runtime.ContainerInfo{ { Name: "mc-proxy", Image: "mcr.svc.mcp.metacircular.net:8443/mc-proxy:v1.0.0", State: "running", Network: "host", Restart: "unless-stopped", Version: "v1.0.0", }, }, } a := newTestAgent(t, rt) ctx := context.Background() resp, err := a.AdoptContainers(ctx, &mcpv1.AdoptContainersRequest{Service: "mc-proxy"}) if err != nil { t.Fatalf("AdoptContainers: %v", err) } if len(resp.Results) != 1 { t.Fatalf("expected 1 result, got %d", len(resp.Results)) } if resp.Results[0].Component != "mc-proxy" { t.Fatalf("expected component name 'mc-proxy', got %q", resp.Results[0].Component) } if !resp.Results[0].Success { t.Fatalf("expected success, got error: %s", resp.Results[0].Error) } } func TestAdoptContainersStoppedContainer(t *testing.T) { rt := &fakeRuntime{ containers: []runtime.ContainerInfo{ { Name: "metacrypt-api", Image: "mcr.svc.mcp.metacircular.net:8443/metacrypt:v1.0.0", State: "exited", Network: "docker_default", Restart: "unless-stopped", Version: "v1.0.0", }, }, } a := newTestAgent(t, rt) ctx := context.Background() resp, err := a.AdoptContainers(ctx, &mcpv1.AdoptContainersRequest{Service: "metacrypt"}) if err != nil { t.Fatalf("AdoptContainers: %v", err) } if len(resp.Results) != 1 || !resp.Results[0].Success { t.Fatalf("expected success: %+v", resp.Results) } comp, err := registry.GetComponent(a.DB, "metacrypt", "api") if err != nil { t.Fatalf("GetComponent: %v", err) } if comp.DesiredState != "stopped" { t.Fatalf("desired state: got %q, want stopped", comp.DesiredState) } if comp.ObservedState != "exited" { t.Fatalf("observed state: got %q, want exited", comp.ObservedState) } } func TestDesiredFromObserved(t *testing.T) { tests := []struct { observed string want string }{ {"running", "running"}, {"exited", "stopped"}, {"stopped", "stopped"}, {"created", "stopped"}, {"", "stopped"}, } for _, tt := range tests { got := desiredFromObserved(tt.observed) if got != tt.want { t.Errorf("desiredFromObserved(%q) = %q, want %q", tt.observed, got, tt.want) } } } func TestAdoptContainersEmptyService(t *testing.T) { rt := &fakeRuntime{} a := newTestAgent(t, rt) ctx := context.Background() _, err := a.AdoptContainers(ctx, &mcpv1.AdoptContainersRequest{Service: ""}) if err == nil { t.Fatal("expected error for empty service name") } }