fix(scheduler): skip discovery for users with no video connection
The scheduler enumerates every user_identities row (ListAllUsers) and ran a discovery pass for each — including users who never connected a video source. Their per-user runner then tried to resolve a YouTube refresh token that was never minted, logging a spurious "secrets: ref not found: youtube/<uid>/refresh_token" every tick (e.g. stale Dex-era orphan identities left by the Authentik migration). Skip users whose ConnectionsForUser is empty before running their pass. Removes the recurring noise — which actively misled a debug session into thinking a healthy onboarded user was broken — with no change to connected users. Refs #7 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+16
-2
@@ -49,10 +49,12 @@ func buildUserRunner(cfg config.Config, st *store.Store, secretStore ports.Secre
|
|||||||
runner.WithAutoWindow(cfg.AutoSummarizeWindow)), nil
|
runner.WithAutoWindow(cfg.AutoSummarizeWindow)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// userLister enumerates every registered user. *store.Store satisfies it via
|
// userLister enumerates every registered user and reports a user's video
|
||||||
// ListAllUsers. A small local interface keeps the scheduler testable with a fake.
|
// connections. *store.Store satisfies it via ListAllUsers + ConnectionsForUser.
|
||||||
|
// A small local interface keeps the scheduler testable with a fake.
|
||||||
type userLister interface {
|
type userLister interface {
|
||||||
ListAllUsers(ctx context.Context) ([]store.UserIdentity, error)
|
ListAllUsers(ctx context.Context) ([]store.UserIdentity, error)
|
||||||
|
ConnectionsForUser(ctx context.Context, userID string) ([]store.Connection, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// runDiscoveryPass runs one discovery pass for every user. runUser performs a
|
// runDiscoveryPass runs one discovery pass for every user. runUser performs a
|
||||||
@@ -78,6 +80,18 @@ func runDiscoveryPass(
|
|||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
break // shutting down: stop enumerating
|
break // shutting down: stop enumerating
|
||||||
}
|
}
|
||||||
|
// Skip users with no video connection. A discovery pass for them only
|
||||||
|
// attempts to resolve a token that was never minted, logging a spurious
|
||||||
|
// "ref not found" every tick (e.g. stale Dex-era orphan identities).
|
||||||
|
conns, err := lister.ConnectionsForUser(ctx, u.UserID)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("scheduler: list connections failed", "user", u.UserID, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(conns) == 0 {
|
||||||
|
log.Debug("scheduler: skipping user with no video connections", "user", u.UserID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
stats, err := runUser(ctx, u.UserID)
|
stats, err := runUser(ctx, u.UserID)
|
||||||
total = sumStats(total, stats)
|
total = sumStats(total, stats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -21,14 +21,25 @@ func quietLog() *slog.Logger {
|
|||||||
|
|
||||||
// fakeLister returns a fixed user set (or an error) for the scheduler under test.
|
// fakeLister returns a fixed user set (or an error) for the scheduler under test.
|
||||||
type fakeLister struct {
|
type fakeLister struct {
|
||||||
users []store.UserIdentity
|
users []store.UserIdentity
|
||||||
err error
|
err error
|
||||||
|
noConn map[string]bool // users that have NOT connected a video source
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f fakeLister) ListAllUsers(context.Context) ([]store.UserIdentity, error) {
|
func (f fakeLister) ListAllUsers(context.Context) ([]store.UserIdentity, error) {
|
||||||
return f.users, f.err
|
return f.users, f.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConnectionsForUser reports a single youtube connection for every user except
|
||||||
|
// those in noConn, which return zero — the connection-less case the scheduler
|
||||||
|
// must skip instead of running (and failing to resolve a token for).
|
||||||
|
func (f fakeLister) ConnectionsForUser(_ context.Context, userID string) ([]store.Connection, error) {
|
||||||
|
if f.noConn[userID] {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return []store.Connection{{Provider: "youtube"}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// countingRunUser records how many passes each user got, optionally failing for
|
// countingRunUser records how many passes each user got, optionally failing for
|
||||||
// specific users, under a mutex so it is safe across the scheduler goroutine.
|
// specific users, under a mutex so it is safe across the scheduler goroutine.
|
||||||
type countingRunUser struct {
|
type countingRunUser struct {
|
||||||
@@ -91,6 +102,21 @@ func TestDiscoveryPassRunsEveryUserOnce(t *testing.T) {
|
|||||||
require.Equal(t, 3, stats.Summarized, "stats are summed across users")
|
require.Equal(t, 3, stats.Summarized, "stats are summed across users")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDiscoveryPassSkipsUsersWithoutConnections(t *testing.T) {
|
||||||
|
// b never connected a video source (e.g. a stale Dex-era orphan identity).
|
||||||
|
// It must be skipped silently — not run and logged as a token error every pass.
|
||||||
|
lister := fakeLister{users: usersN("a", "b", "c"), noConn: map[string]bool{"b": true}}
|
||||||
|
rc := newCountingRunUser()
|
||||||
|
|
||||||
|
stats := runDiscoveryPass(context.Background(), lister, rc.run, quietLog())
|
||||||
|
|
||||||
|
require.Equal(t, 1, rc.count("a"))
|
||||||
|
require.Equal(t, 0, rc.count("b"), "a user with no connection must be skipped, not run")
|
||||||
|
require.Equal(t, 1, rc.count("c"))
|
||||||
|
require.Equal(t, 2, stats.Summarized, "only connected users contribute")
|
||||||
|
require.Equal(t, 0, stats.Errors, "skipping is silent — no spurious error stat")
|
||||||
|
}
|
||||||
|
|
||||||
func TestDiscoveryPassOneUserFailureDoesNotStopOthers(t *testing.T) {
|
func TestDiscoveryPassOneUserFailureDoesNotStopOthers(t *testing.T) {
|
||||||
lister := fakeLister{users: usersN("a", "b", "c")}
|
lister := fakeLister{users: usersN("a", "b", "c")}
|
||||||
rc := newCountingRunUser("b") // user b's pass errors
|
rc := newCountingRunUser("b") // user b's pass errors
|
||||||
|
|||||||
Reference in New Issue
Block a user