package store import "testing" // TestListProxyRoutesJoinShape verifies the new containers-based join produces // the same ProxyRoute shape the /api/proxies frontend has consumed since this // query was instances-based. Without this test, a missing column or a wrong // join condition would silently break the Proxies page. func TestListProxyRoutesJoinShape(t *testing.T) { s := newTestStore(t) p, err := s.CreateProject(Project{ Name: "wf", Image: "nginx", Port: 80, Env: "{}", Volumes: "{}", }) if err != nil { t.Fatalf("CreateProject: %v", err) } stage, err := s.CreateStage(Stage{ ProjectID: p.ID, Name: "prod", TagPattern: "*", MaxInstances: 1, EnableProxy: true, }) if err != nil { t.Fatalf("CreateStage: %v", err) } w, err := s.GetWorkloadByRef(WorkloadKindProject, p.ID) if err != nil { t.Fatalf("workload: %v", err) } // Container with both subdomain and proxy_route_id populated — the rule // the WHERE clause filters on. if _, err := s.CreateContainer(Container{ WorkloadID: w.ID, WorkloadKind: "project", Role: stage.Name, ContainerID: "docker-abc", ImageTag: "v1", State: "running", Port: 8080, Subdomain: "wf-prod", ProxyRouteID: "route-1", }); err != nil { t.Fatalf("CreateContainer: %v", err) } // Container without subdomain — must be filtered OUT. if _, err := s.CreateContainer(Container{ WorkloadID: w.ID, WorkloadKind: "project", Role: stage.Name, ContainerID: "docker-def", ImageTag: "v2", State: "running", }); err != nil { t.Fatalf("CreateContainer 2: %v", err) } routes, err := s.ListProxyRoutes("example.test") if err != nil { t.Fatalf("ListProxyRoutes: %v", err) } if len(routes) != 1 { t.Fatalf("expected 1 route, got %d (filter wrong?)", len(routes)) } r := routes[0] if r.Source != "instance" { t.Errorf("Source: got %q, want 'instance' (back-compat)", r.Source) } if r.ProjectID != p.ID { t.Errorf("ProjectID: got %q, want %q", r.ProjectID, p.ID) } if r.ProjectName != "wf" { t.Errorf("ProjectName: got %q, want 'wf'", r.ProjectName) } if r.StageID != stage.ID { t.Errorf("StageID: got %q, want %q", r.StageID, stage.ID) } if r.StageName != "prod" { t.Errorf("StageName: got %q, want 'prod'", r.StageName) } if r.ImageTag != "v1" { t.Errorf("ImageTag: got %q, want 'v1'", r.ImageTag) } if r.Subdomain != "wf-prod" { t.Errorf("Subdomain: got %q, want 'wf-prod'", r.Subdomain) } if r.Domain != "wf-prod.example.test" { t.Errorf("Domain: got %q, want 'wf-prod.example.test'", r.Domain) } if r.ContainerID != "docker-abc" { t.Errorf("ContainerID: got %q, want 'docker-abc'", r.ContainerID) } if r.Port != 8080 { t.Errorf("Port: got %d, want 8080", r.Port) } if r.ProxyRouteID != "route-1" { t.Errorf("ProxyRouteID: got %q, want 'route-1'", r.ProxyRouteID) } if r.Status != "running" { t.Errorf("Status (state): got %q, want 'running'", r.Status) } } func TestListProxyRoutesNpmOnly(t *testing.T) { // NPM-only routes (npm_proxy_id > 0, proxy_route_id == "") must still be // returned — that's the original WHERE-clause OR branch. s := newTestStore(t) p, _ := s.CreateProject(Project{ Name: "npm-only", Image: "nginx", Port: 80, Env: "{}", Volumes: "{}", }) stage, _ := s.CreateStage(Stage{ ProjectID: p.ID, Name: "dev", TagPattern: "*", MaxInstances: 1, EnableProxy: true, }) w, _ := s.GetWorkloadByRef(WorkloadKindProject, p.ID) if _, err := s.CreateContainer(Container{ WorkloadID: w.ID, WorkloadKind: "project", Role: stage.Name, ContainerID: "docker-1", Subdomain: "npm-only-dev", NpmProxyID: 42, }); err != nil { t.Fatalf("CreateContainer: %v", err) } routes, err := s.ListProxyRoutes("") if err != nil { t.Fatalf("ListProxyRoutes: %v", err) } if len(routes) != 1 { t.Fatalf("expected 1 npm route, got %d", len(routes)) } if routes[0].NpmProxyID != 42 { t.Errorf("NpmProxyID: got %d, want 42", routes[0].NpmProxyID) } } func TestListProxyRoutesIgnoresWrongRole(t *testing.T) { // Belt-and-suspenders: a container whose role doesn't match a stage name // would orphan the JOIN. Verify the row falls out cleanly (LEFT JOIN // would expose a real bug here). s := newTestStore(t) p, _ := s.CreateProject(Project{ Name: "wf", Image: "nginx", Port: 80, Env: "{}", Volumes: "{}", }) _, _ = s.CreateStage(Stage{ ProjectID: p.ID, Name: "prod", TagPattern: "*", MaxInstances: 1, }) w, _ := s.GetWorkloadByRef(WorkloadKindProject, p.ID) if _, err := s.CreateContainer(Container{ WorkloadID: w.ID, WorkloadKind: "project", Role: "ghost-stage", // intentionally not a real stage name ContainerID: "docker-x", Subdomain: "wf-ghost", ProxyRouteID: "route-x", }); err != nil { t.Fatalf("CreateContainer: %v", err) } routes, err := s.ListProxyRoutes("") if err != nil { t.Fatalf("ListProxyRoutes: %v", err) } if len(routes) != 0 { t.Fatalf("orphan-role row leaked into result: got %d", len(routes)) } }