fix(instance): sort public services so the profile order is stable

The cache is a Go map; unsorted output reshuffled on every poll,
which made hub-client lists visibly jump around.
This commit is contained in:
2026-08-23 12:03:55 +01:00
parent 5d11e80837
commit 707fe619f7
2 changed files with 27 additions and 1 deletions
+7 -1
View File
@@ -22,6 +22,8 @@ package instance
import (
"encoding/json"
"net/http"
"sort"
"strings"
"git.dcntrlized.org/Dcntrlized/Maestro/lib/env"
"git.dcntrlized.org/Dcntrlized/Maestro/lib/federation"
@@ -103,7 +105,8 @@ func buildIdentity(dataRoot string) InstanceIdentity {
// publicServices filters hidden infrastructure (role-labeled containers such
// as dcntrlized-db / dcntrlized-auth and anything flagged hidden) down to the
// public field set.
// public field set. The result is sorted case-insensitively by service name:
// the cache is a map, so an unsorted profile would reshuffle on every poll.
func publicServices(all []types.Container) []PublicService {
public := make([]PublicService, 0, len(all))
for _, c := range all {
@@ -125,5 +128,8 @@ func publicServices(all []types.Container) []PublicService {
Image: c.Image,
})
}
sort.Slice(public, func(i, j int) bool {
return strings.ToLower(public[i].ServiceName) < strings.ToLower(public[j].ServiceName)
})
return public
}
+20
View File
@@ -61,6 +61,26 @@ func serve(t *testing.T, dataRoot string, services []types.Container) map[string
return body
}
func TestPublicServicesSortedCaseInsensitive(t *testing.T) {
// The cache is a Go map, so input order is random per call; the profile
// must still be stable across polls.
all := []types.Container{
{ID: "1", ServiceName: "calendar", Status: "running"},
{ID: "2", ServiceName: "Blog", Status: "running"},
{ID: "3", ServiceName: "auth-proxy", Status: "running"},
}
got := publicServices(all)
want := []string{"auth-proxy", "Blog", "calendar"}
if len(got) != len(want) {
t.Fatalf("len = %d, want %d", len(got), len(want))
}
for i := range want {
if got[i].ServiceName != want[i] {
t.Fatalf("order[%d] = %q, want %q (full: %v)", i, got[i].ServiceName, want[i], got)
}
}
}
func TestPublicProfileFiltersHiddenInfra(t *testing.T) {
dataRoot := t.TempDir()
body := serve(t, dataRoot, testServices())