Files
sherlock/internal/authn/source_test.go
T
amacocian 28dcd5b8e6
CI / build (push) Failing after 1s
Grafana MCP
2026-06-13 11:43:26 +02:00

174 lines
4.5 KiB
Go

//go:build unix
package authn
import (
"context"
"sync"
"testing"
"time"
"gitea.alexandru.macocian.me/Charlie/sherlock/internal/keyring"
fakekeyring "gitea.alexandru.macocian.me/Charlie/sherlock/internal/keyring/fake"
)
func TestNewTokenSource_RequiresCallback(t *testing.T) {
defer func() {
if recover() == nil {
t.Fatal("expected panic on nil onToken")
}
}()
NewTokenSource(fakekeyring.New(), "gitea",
Config{Issuer: "https://x", ClientID: "y"},
EnsureOptions{LockPath: t.TempDir() + "/lock"}, nil)
}
func TestNewTokenSource_RequiresLockPath(t *testing.T) {
defer func() {
if recover() == nil {
t.Fatal("expected panic on empty LockPath")
}
}()
NewTokenSource(fakekeyring.New(), "gitea",
Config{Issuer: "https://x", ClientID: "y"},
EnsureOptions{}, func(keyring.TokenSet) {})
}
func TestTokenSource_Start_DeliversInitialToken(t *testing.T) {
store := fakekeyring.New()
_ = store.Set("gitea", keyring.TokenSet{
IDToken: "fresh",
AccessToken: "at-0",
IDExpiresAt: time.Now().Add(time.Hour),
Issuer: "x",
ClientID: "y",
})
var got []string
var mu sync.Mutex
cb := func(ts keyring.TokenSet) {
mu.Lock()
got = append(got, ts.AccessToken)
mu.Unlock()
}
src := NewTokenSource(store, "gitea",
Config{Issuer: "https://example.test", ClientID: "y"},
EnsureOptions{LockPath: t.TempDir() + "/lock"}, cb)
ts, err := src.Start(context.Background())
if err != nil {
t.Fatalf("Start: %v", err)
}
if ts.AccessToken != "at-0" {
t.Fatalf("Start returned %q, want at-0", ts.AccessToken)
}
mu.Lock()
defer mu.Unlock()
if len(got) != 1 || got[0] != "at-0" {
t.Fatalf("callback got %v, want [at-0]", got)
}
}
func TestTokenSource_Run_RenewsAndNotifies(t *testing.T) {
stub := newStubAuthentik(t)
store := fakekeyring.New()
// Seed a STALE token (already expired) with a refresh token so the
// renewal path fires immediately.
_ = store.Set("gitea", keyring.TokenSet{
IDToken: "old",
AccessToken: "at-stale",
RefreshToken: "rt-1",
IDExpiresAt: time.Now().Add(-time.Minute),
Issuer: stub.srv.URL,
ClientID: "test-client",
Scopes: []string{"openid"},
})
tokens := make(chan string, 4)
src := NewTokenSource(store, "gitea",
Config{Issuer: stub.srv.URL, ClientID: "test-client"},
EnsureOptions{LockPath: t.TempDir() + "/lock"},
func(ts keyring.TokenSet) { tokens <- ts.AccessToken })
src.minWait = time.Millisecond // refresh almost immediately
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
done := make(chan struct{})
go func() { _ = src.Run(ctx); close(done) }()
select {
case got := <-tokens:
if got != "at-refreshed" {
t.Fatalf("renewed token = %q, want at-refreshed", got)
}
case <-time.After(5 * time.Second):
t.Fatal("timed out waiting for renewal callback")
}
// The rotated token must be persisted to the wallet too.
stored, err := store.Get("gitea")
if err != nil {
t.Fatalf("Get: %v", err)
}
if stored.AccessToken != "at-refreshed" {
t.Fatalf("wallet access token = %q, want at-refreshed", stored.AccessToken)
}
cancel()
select {
case <-done:
case <-time.After(2 * time.Second):
t.Fatal("Run did not exit after cancel")
}
}
func TestTokenSource_Run_StopsOnCancel(t *testing.T) {
store := fakekeyring.New()
_ = store.Set("gitea", keyring.TokenSet{
IDToken: "fresh",
AccessToken: "at-0",
IDExpiresAt: time.Now().Add(time.Hour),
Issuer: "x",
ClientID: "y",
})
src := NewTokenSource(store, "gitea",
Config{Issuer: "https://example.test", ClientID: "y"},
EnsureOptions{LockPath: t.TempDir() + "/lock"},
func(keyring.TokenSet) {})
ctx, cancel := context.WithCancel(context.Background())
done := make(chan error, 1)
go func() { done <- src.Run(ctx) }()
cancel()
select {
case err := <-done:
if err != context.Canceled {
t.Fatalf("Run returned %v, want context.Canceled", err)
}
case <-time.After(2 * time.Second):
t.Fatal("Run did not return after cancel")
}
}
func TestTokenHolder(t *testing.T) {
h := NewTokenHolder()
if h.AccessToken() != "" {
t.Fatal("empty holder should return empty access token")
}
if _, err := h.Bearer(); err == nil {
t.Fatal("empty holder Bearer should error")
}
h.Set(keyring.TokenSet{AccessToken: "at-1"})
if h.AccessToken() != "at-1" {
t.Fatalf("AccessToken = %q, want at-1", h.AccessToken())
}
if b, err := h.Bearer(); err != nil || b != "Bearer at-1" {
t.Fatalf("Bearer = %q, %v", b, err)
}
h.Set(keyring.TokenSet{AccessToken: "at-2"})
if h.AccessToken() != "at-2" {
t.Fatalf("AccessToken = %q, want at-2 after update", h.AccessToken())
}
}