diff --git a/src/handlers/instance/public.go b/src/handlers/instance/public.go index c16ff56..5956b5f 100644 --- a/src/handlers/instance/public.go +++ b/src/handlers/instance/public.go @@ -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 } diff --git a/src/handlers/instance/public_test.go b/src/handlers/instance/public_test.go index 3378ae8..abc688f 100644 --- a/src/handlers/instance/public_test.go +++ b/src/handlers/instance/public_test.go @@ -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())