All import paths updated to git.wntrmute.dev/mc/. Bumps mcdsl to v1.2.0, mc-proxy to v1.1.0. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
406 lines
11 KiB
Go
406 lines
11 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1"
|
|
"git.wntrmute.dev/mc/mcp/internal/registry"
|
|
)
|
|
|
|
func TestPurgeComponentRemoved(t *testing.T) {
|
|
rt := &fakeRuntime{}
|
|
a := newTestAgent(t, rt)
|
|
ctx := context.Background()
|
|
|
|
// Set up a service with a stale component.
|
|
if err := registry.CreateService(a.DB, "mcns", true); err != nil {
|
|
t.Fatalf("create service: %v", err)
|
|
}
|
|
if err := registry.CreateComponent(a.DB, ®istry.Component{
|
|
Name: "coredns",
|
|
Service: "mcns",
|
|
Image: "coredns:latest",
|
|
DesiredState: "running",
|
|
ObservedState: "removed",
|
|
}); err != nil {
|
|
t.Fatalf("create component: %v", err)
|
|
}
|
|
|
|
// Insert an event for this component.
|
|
if err := registry.InsertEvent(a.DB, "mcns", "coredns", "running", "removed"); err != nil {
|
|
t.Fatalf("insert event: %v", err)
|
|
}
|
|
|
|
resp, err := a.PurgeComponent(ctx, &mcpv1.PurgeRequest{
|
|
DefinedComponents: []string{"mcns/mcns"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("PurgeComponent: %v", err)
|
|
}
|
|
|
|
if len(resp.Results) != 1 {
|
|
t.Fatalf("expected 1 result, got %d", len(resp.Results))
|
|
}
|
|
|
|
r := resp.Results[0]
|
|
if !r.Purged {
|
|
t.Fatalf("expected purged=true, got reason: %s", r.Reason)
|
|
}
|
|
if r.Service != "mcns" || r.Component != "coredns" {
|
|
t.Fatalf("unexpected result: %s/%s", r.Service, r.Component)
|
|
}
|
|
|
|
// Verify component was deleted.
|
|
_, err = registry.GetComponent(a.DB, "mcns", "coredns")
|
|
if err == nil {
|
|
t.Fatal("component should have been deleted")
|
|
}
|
|
|
|
// Service should also be deleted since it has no remaining components.
|
|
_, err = registry.GetService(a.DB, "mcns")
|
|
if err == nil {
|
|
t.Fatal("service should have been deleted (no remaining components)")
|
|
}
|
|
}
|
|
|
|
func TestPurgeRefusesRunning(t *testing.T) {
|
|
rt := &fakeRuntime{}
|
|
a := newTestAgent(t, rt)
|
|
ctx := context.Background()
|
|
|
|
if err := registry.CreateService(a.DB, "mcr", true); err != nil {
|
|
t.Fatalf("create service: %v", err)
|
|
}
|
|
if err := registry.CreateComponent(a.DB, ®istry.Component{
|
|
Name: "api",
|
|
Service: "mcr",
|
|
Image: "mcr:latest",
|
|
DesiredState: "running",
|
|
ObservedState: "running",
|
|
}); err != nil {
|
|
t.Fatalf("create component: %v", err)
|
|
}
|
|
|
|
resp, err := a.PurgeComponent(ctx, &mcpv1.PurgeRequest{
|
|
Service: "mcr",
|
|
Component: "api",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("PurgeComponent: %v", err)
|
|
}
|
|
|
|
if len(resp.Results) != 1 {
|
|
t.Fatalf("expected 1 result, got %d", len(resp.Results))
|
|
}
|
|
if resp.Results[0].Purged {
|
|
t.Fatal("should not purge a running component")
|
|
}
|
|
|
|
// Verify component still exists.
|
|
_, err = registry.GetComponent(a.DB, "mcr", "api")
|
|
if err != nil {
|
|
t.Fatalf("component should still exist: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPurgeRefusesStopped(t *testing.T) {
|
|
rt := &fakeRuntime{}
|
|
a := newTestAgent(t, rt)
|
|
ctx := context.Background()
|
|
|
|
if err := registry.CreateService(a.DB, "mcr", true); err != nil {
|
|
t.Fatalf("create service: %v", err)
|
|
}
|
|
if err := registry.CreateComponent(a.DB, ®istry.Component{
|
|
Name: "api",
|
|
Service: "mcr",
|
|
Image: "mcr:latest",
|
|
DesiredState: "stopped",
|
|
ObservedState: "stopped",
|
|
}); err != nil {
|
|
t.Fatalf("create component: %v", err)
|
|
}
|
|
|
|
resp, err := a.PurgeComponent(ctx, &mcpv1.PurgeRequest{
|
|
Service: "mcr",
|
|
Component: "api",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("PurgeComponent: %v", err)
|
|
}
|
|
|
|
if resp.Results[0].Purged {
|
|
t.Fatal("should not purge a stopped component")
|
|
}
|
|
}
|
|
|
|
func TestPurgeSkipsDefinedComponent(t *testing.T) {
|
|
rt := &fakeRuntime{}
|
|
a := newTestAgent(t, rt)
|
|
ctx := context.Background()
|
|
|
|
if err := registry.CreateService(a.DB, "mcns", true); err != nil {
|
|
t.Fatalf("create service: %v", err)
|
|
}
|
|
if err := registry.CreateComponent(a.DB, ®istry.Component{
|
|
Name: "mcns",
|
|
Service: "mcns",
|
|
Image: "mcns:latest",
|
|
DesiredState: "running",
|
|
ObservedState: "exited",
|
|
}); err != nil {
|
|
t.Fatalf("create component: %v", err)
|
|
}
|
|
|
|
resp, err := a.PurgeComponent(ctx, &mcpv1.PurgeRequest{
|
|
DefinedComponents: []string{"mcns/mcns"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("PurgeComponent: %v", err)
|
|
}
|
|
|
|
if len(resp.Results) != 1 {
|
|
t.Fatalf("expected 1 result, got %d", len(resp.Results))
|
|
}
|
|
if resp.Results[0].Purged {
|
|
t.Fatal("should not purge a component that is still in service definitions")
|
|
}
|
|
if resp.Results[0].Reason != "still in service definitions" {
|
|
t.Fatalf("unexpected reason: %s", resp.Results[0].Reason)
|
|
}
|
|
}
|
|
|
|
func TestPurgeDryRun(t *testing.T) {
|
|
rt := &fakeRuntime{}
|
|
a := newTestAgent(t, rt)
|
|
ctx := context.Background()
|
|
|
|
if err := registry.CreateService(a.DB, "mcns", true); err != nil {
|
|
t.Fatalf("create service: %v", err)
|
|
}
|
|
if err := registry.CreateComponent(a.DB, ®istry.Component{
|
|
Name: "coredns",
|
|
Service: "mcns",
|
|
Image: "coredns:latest",
|
|
DesiredState: "running",
|
|
ObservedState: "removed",
|
|
}); err != nil {
|
|
t.Fatalf("create component: %v", err)
|
|
}
|
|
|
|
resp, err := a.PurgeComponent(ctx, &mcpv1.PurgeRequest{
|
|
DryRun: true,
|
|
DefinedComponents: []string{"mcns/mcns"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("PurgeComponent: %v", err)
|
|
}
|
|
|
|
if len(resp.Results) != 1 {
|
|
t.Fatalf("expected 1 result, got %d", len(resp.Results))
|
|
}
|
|
if !resp.Results[0].Purged {
|
|
t.Fatal("dry run should report purged=true for eligible components")
|
|
}
|
|
|
|
// Verify component was NOT deleted (dry run).
|
|
_, err = registry.GetComponent(a.DB, "mcns", "coredns")
|
|
if err != nil {
|
|
t.Fatalf("component should still exist after dry run: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPurgeServiceFilter(t *testing.T) {
|
|
rt := &fakeRuntime{}
|
|
a := newTestAgent(t, rt)
|
|
ctx := context.Background()
|
|
|
|
// Create two services.
|
|
if err := registry.CreateService(a.DB, "mcns", true); err != nil {
|
|
t.Fatalf("create service: %v", err)
|
|
}
|
|
if err := registry.CreateComponent(a.DB, ®istry.Component{
|
|
Name: "coredns", Service: "mcns", Image: "coredns:latest",
|
|
DesiredState: "running", ObservedState: "removed",
|
|
}); err != nil {
|
|
t.Fatalf("create component: %v", err)
|
|
}
|
|
if err := registry.CreateService(a.DB, "mcr", true); err != nil {
|
|
t.Fatalf("create service: %v", err)
|
|
}
|
|
if err := registry.CreateComponent(a.DB, ®istry.Component{
|
|
Name: "old", Service: "mcr", Image: "old:latest",
|
|
DesiredState: "running", ObservedState: "removed",
|
|
}); err != nil {
|
|
t.Fatalf("create component: %v", err)
|
|
}
|
|
|
|
// Purge only mcns.
|
|
resp, err := a.PurgeComponent(ctx, &mcpv1.PurgeRequest{
|
|
Service: "mcns",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("PurgeComponent: %v", err)
|
|
}
|
|
|
|
if len(resp.Results) != 1 {
|
|
t.Fatalf("expected 1 result, got %d", len(resp.Results))
|
|
}
|
|
if resp.Results[0].Service != "mcns" {
|
|
t.Fatalf("expected mcns, got %s", resp.Results[0].Service)
|
|
}
|
|
|
|
// mcr/old should still exist.
|
|
_, err = registry.GetComponent(a.DB, "mcr", "old")
|
|
if err != nil {
|
|
t.Fatalf("mcr/old should still exist: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPurgeServiceDeletedWhenEmpty(t *testing.T) {
|
|
rt := &fakeRuntime{}
|
|
a := newTestAgent(t, rt)
|
|
ctx := context.Background()
|
|
|
|
if err := registry.CreateService(a.DB, "mcns", true); err != nil {
|
|
t.Fatalf("create service: %v", err)
|
|
}
|
|
if err := registry.CreateComponent(a.DB, ®istry.Component{
|
|
Name: "coredns", Service: "mcns", Image: "coredns:latest",
|
|
DesiredState: "running", ObservedState: "removed",
|
|
}); err != nil {
|
|
t.Fatalf("create component: %v", err)
|
|
}
|
|
if err := registry.CreateComponent(a.DB, ®istry.Component{
|
|
Name: "old-thing", Service: "mcns", Image: "old:latest",
|
|
DesiredState: "stopped", ObservedState: "unknown",
|
|
}); err != nil {
|
|
t.Fatalf("create component: %v", err)
|
|
}
|
|
|
|
resp, err := a.PurgeComponent(ctx, &mcpv1.PurgeRequest{})
|
|
if err != nil {
|
|
t.Fatalf("PurgeComponent: %v", err)
|
|
}
|
|
|
|
// Both components should be purged.
|
|
if len(resp.Results) != 2 {
|
|
t.Fatalf("expected 2 results, got %d", len(resp.Results))
|
|
}
|
|
for _, r := range resp.Results {
|
|
if !r.Purged {
|
|
t.Fatalf("expected purged=true for %s/%s: %s", r.Service, r.Component, r.Reason)
|
|
}
|
|
}
|
|
|
|
// Service should be deleted.
|
|
_, err = registry.GetService(a.DB, "mcns")
|
|
if err == nil {
|
|
t.Fatal("service should have been deleted")
|
|
}
|
|
}
|
|
|
|
func TestPurgeServiceKeptWhenComponentsRemain(t *testing.T) {
|
|
rt := &fakeRuntime{}
|
|
a := newTestAgent(t, rt)
|
|
ctx := context.Background()
|
|
|
|
if err := registry.CreateService(a.DB, "mcns", true); err != nil {
|
|
t.Fatalf("create service: %v", err)
|
|
}
|
|
// Stale component (will be purged).
|
|
if err := registry.CreateComponent(a.DB, ®istry.Component{
|
|
Name: "coredns", Service: "mcns", Image: "coredns:latest",
|
|
DesiredState: "running", ObservedState: "removed",
|
|
}); err != nil {
|
|
t.Fatalf("create component: %v", err)
|
|
}
|
|
// Live component (will not be purged).
|
|
if err := registry.CreateComponent(a.DB, ®istry.Component{
|
|
Name: "mcns", Service: "mcns", Image: "mcns:latest",
|
|
DesiredState: "running", ObservedState: "running",
|
|
}); err != nil {
|
|
t.Fatalf("create component: %v", err)
|
|
}
|
|
|
|
resp, err := a.PurgeComponent(ctx, &mcpv1.PurgeRequest{})
|
|
if err != nil {
|
|
t.Fatalf("PurgeComponent: %v", err)
|
|
}
|
|
|
|
if len(resp.Results) != 2 {
|
|
t.Fatalf("expected 2 results, got %d", len(resp.Results))
|
|
}
|
|
|
|
// coredns should be purged, mcns should not.
|
|
purged := 0
|
|
for _, r := range resp.Results {
|
|
if r.Purged {
|
|
purged++
|
|
if r.Component != "coredns" {
|
|
t.Fatalf("expected coredns to be purged, got %s", r.Component)
|
|
}
|
|
}
|
|
}
|
|
if purged != 1 {
|
|
t.Fatalf("expected 1 purged, got %d", purged)
|
|
}
|
|
|
|
// Service should still exist.
|
|
_, err = registry.GetService(a.DB, "mcns")
|
|
if err != nil {
|
|
t.Fatalf("service should still exist: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPurgeExitedState(t *testing.T) {
|
|
rt := &fakeRuntime{}
|
|
a := newTestAgent(t, rt)
|
|
ctx := context.Background()
|
|
|
|
if err := registry.CreateService(a.DB, "test", true); err != nil {
|
|
t.Fatalf("create service: %v", err)
|
|
}
|
|
if err := registry.CreateComponent(a.DB, ®istry.Component{
|
|
Name: "old", Service: "test", Image: "old:latest",
|
|
DesiredState: "stopped", ObservedState: "exited",
|
|
}); err != nil {
|
|
t.Fatalf("create component: %v", err)
|
|
}
|
|
|
|
resp, err := a.PurgeComponent(ctx, &mcpv1.PurgeRequest{})
|
|
if err != nil {
|
|
t.Fatalf("PurgeComponent: %v", err)
|
|
}
|
|
|
|
if len(resp.Results) != 1 || !resp.Results[0].Purged {
|
|
t.Fatalf("exited component should be purgeable")
|
|
}
|
|
}
|
|
|
|
func TestPurgeUnknownState(t *testing.T) {
|
|
rt := &fakeRuntime{}
|
|
a := newTestAgent(t, rt)
|
|
ctx := context.Background()
|
|
|
|
if err := registry.CreateService(a.DB, "test", true); err != nil {
|
|
t.Fatalf("create service: %v", err)
|
|
}
|
|
if err := registry.CreateComponent(a.DB, ®istry.Component{
|
|
Name: "ghost", Service: "test", Image: "ghost:latest",
|
|
DesiredState: "running", ObservedState: "unknown",
|
|
}); err != nil {
|
|
t.Fatalf("create component: %v", err)
|
|
}
|
|
|
|
resp, err := a.PurgeComponent(ctx, &mcpv1.PurgeRequest{})
|
|
if err != nil {
|
|
t.Fatalf("PurgeComponent: %v", err)
|
|
}
|
|
|
|
if len(resp.Results) != 1 || !resp.Results[0].Purged {
|
|
t.Fatalf("unknown component should be purgeable")
|
|
}
|
|
}
|