diff --git a/internal/watcher/clients.go b/internal/watcher/clients.go index 7c2fd2a8..c71e442c 100644 --- a/internal/watcher/clients.go +++ b/internal/watcher/clients.go @@ -101,14 +101,7 @@ func (w *Watcher) reloadClients(rescanAuth bool, affectedOAuthProviders []string IDGenerator: synthesizer.NewStableIDGenerator(), } if generated := synthesizer.SynthesizeAuthFile(ctx, path, data); len(generated) > 0 { - pathAuths := make(map[string]*coreauth.Auth, len(generated)) - for _, a := range generated { - if a == nil || strings.TrimSpace(a.ID) == "" { - continue - } - pathAuths[a.ID] = a - } - if len(pathAuths) > 0 { + if pathAuths := authSliceToMap(generated); len(pathAuths) > 0 { w.fileAuthsByPath[normalizedPath] = pathAuths } } @@ -198,11 +191,9 @@ func (w *Watcher) addOrUpdateClient(path string) { } w.lastAuthContents[normalized] = &newAuth - oldByID := make(map[string]*coreauth.Auth) - if existing := w.fileAuthsByPath[normalized]; len(existing) > 0 { - for id, a := range existing { - oldByID[id] = a - } + oldByID := make(map[string]*coreauth.Auth, len(w.fileAuthsByPath[normalized])) + for id, a := range w.fileAuthsByPath[normalized] { + oldByID[id] = a } // Build synthesized auth entries for this single file only. @@ -213,13 +204,7 @@ func (w *Watcher) addOrUpdateClient(path string) { IDGenerator: synthesizer.NewStableIDGenerator(), } generated := synthesizer.SynthesizeAuthFile(sctx, path, data) - newByID := make(map[string]*coreauth.Auth) - for _, a := range generated { - if a == nil || strings.TrimSpace(a.ID) == "" { - continue - } - newByID[a.ID] = a - } + newByID := authSliceToMap(generated) if len(newByID) > 0 { w.fileAuthsByPath[normalized] = newByID } else { @@ -235,11 +220,9 @@ func (w *Watcher) addOrUpdateClient(path string) { func (w *Watcher) removeClient(path string) { normalized := w.normalizeAuthPath(path) w.clientsMutex.Lock() - oldByID := make(map[string]*coreauth.Auth) - if existing := w.fileAuthsByPath[normalized]; len(existing) > 0 { - for id, a := range existing { - oldByID[id] = a - } + oldByID := make(map[string]*coreauth.Auth, len(w.fileAuthsByPath[normalized])) + for id, a := range w.fileAuthsByPath[normalized] { + oldByID[id] = a } delete(w.lastAuthHashes, normalized) delete(w.lastAuthContents, normalized) @@ -279,6 +262,23 @@ func (w *Watcher) computePerPathUpdatesLocked(oldByID, newByID map[string]*corea return updates } +func authSliceToMap(auths []*coreauth.Auth) map[string]*coreauth.Auth { + if len(auths) == 0 { + return nil + } + byID := make(map[string]*coreauth.Auth, len(auths)) + for _, a := range auths { + if a == nil || strings.TrimSpace(a.ID) == "" { + continue + } + byID[a.ID] = a + } + if len(byID) == 0 { + return nil + } + return byID +} + func (w *Watcher) loadFileClients(cfg *config.Config) int { authFileCount := 0 successfulAuthCount := 0 diff --git a/internal/watcher/watcher_test.go b/internal/watcher/watcher_test.go index b4d758dd..208ae102 100644 --- a/internal/watcher/watcher_test.go +++ b/internal/watcher/watcher_test.go @@ -472,6 +472,74 @@ func TestAuthFileEventsDoNotInvokeSnapshotCoreAuths(t *testing.T) { } } +func TestAuthSliceToMap(t *testing.T) { + t.Parallel() + + valid1 := &coreauth.Auth{ID: "a"} + valid2 := &coreauth.Auth{ID: "b"} + dupOld := &coreauth.Auth{ID: "dup", Label: "old"} + dupNew := &coreauth.Auth{ID: "dup", Label: "new"} + empty := &coreauth.Auth{ID: " "} + + tests := []struct { + name string + in []*coreauth.Auth + want map[string]*coreauth.Auth + }{ + { + name: "nil input", + in: nil, + want: nil, + }, + { + name: "empty input", + in: []*coreauth.Auth{}, + want: nil, + }, + { + name: "filters invalid auths", + in: []*coreauth.Auth{nil, empty}, + want: nil, + }, + { + name: "keeps valid auths", + in: []*coreauth.Auth{valid1, nil, valid2}, + want: map[string]*coreauth.Auth{"a": valid1, "b": valid2}, + }, + { + name: "last duplicate wins", + in: []*coreauth.Auth{dupOld, dupNew}, + want: map[string]*coreauth.Auth{"dup": dupNew}, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + got := authSliceToMap(tc.in) + if len(tc.want) == 0 { + if got != nil { + t.Fatalf("expected nil map, got %#v", got) + } + return + } + if len(got) != len(tc.want) { + t.Fatalf("unexpected map length: got %d, want %d", len(got), len(tc.want)) + } + for id, wantAuth := range tc.want { + gotAuth, ok := got[id] + if !ok { + t.Fatalf("missing id %q in result map", id) + } + if !authEqual(gotAuth, wantAuth) { + t.Fatalf("unexpected auth for id %q: got %#v, want %#v", id, gotAuth, wantAuth) + } + } + }) + } +} + func TestShouldDebounceRemove(t *testing.T) { w := &Watcher{} path := filepath.Clean("test.json")