package tokens import ( "context" "errors" "sync/atomic" "testing" "time" "git.hangman-lab.top/hzhang/Plexum-fabric-channel-plugin/internal/fabric" ) func fakeSession(guildNodes ...string) *fabric.Session { s := &fabric.Session{User: fabric.SessionUser{ID: "u1", Email: "u@x"}} for _, g := range guildNodes { s.Guilds = append(s.Guilds, fabric.GuildInfo{NodeID: g, Endpoint: "http://" + g}) s.GuildAccessTokens = append(s.GuildAccessTokens, fabric.GuildAccessToken{ GuildNodeID: g, Token: "tok-" + g, }) } return s } func TestGetFirstLogsIn(t *testing.T) { var calls atomic.Int32 c := New(0, func(context.Context, string) (*fabric.Session, error) { calls.Add(1) return fakeSession("g1"), nil }) s, err := c.Get(context.Background(), "alice") if err != nil || s.User.ID != "u1" { t.Fatalf("get err=%v", err) } if calls.Load() != 1 { t.Errorf("calls = %d", calls.Load()) } } func TestGetWithinTTLReusesCached(t *testing.T) { var calls atomic.Int32 c := New(time.Minute, func(context.Context, string) (*fabric.Session, error) { calls.Add(1) return fakeSession("g1"), nil }) c.Get(context.Background(), "alice") c.Get(context.Background(), "alice") c.Get(context.Background(), "alice") if calls.Load() != 1 { t.Errorf("calls = %d (TTL fresh)", calls.Load()) } } func TestGetAfterTTLReLogs(t *testing.T) { var calls atomic.Int32 c := New(10*time.Millisecond, func(context.Context, string) (*fabric.Session, error) { calls.Add(1) return fakeSession("g1"), nil }) c.Get(context.Background(), "alice") time.Sleep(20 * time.Millisecond) c.Get(context.Background(), "alice") if calls.Load() != 2 { t.Errorf("calls = %d, want 2 after TTL expiry", calls.Load()) } } func TestInvalidateForcesReLogin(t *testing.T) { var calls atomic.Int32 c := New(time.Minute, func(context.Context, string) (*fabric.Session, error) { calls.Add(1) return fakeSession("g1"), nil }) c.Get(context.Background(), "alice") c.Invalidate("alice") c.Get(context.Background(), "alice") if calls.Load() != 2 { t.Errorf("calls = %d, want 2 after Invalidate", calls.Load()) } } func TestLoginErrorBubbles(t *testing.T) { sentinel := errors.New("boom") c := New(0, func(context.Context, string) (*fabric.Session, error) { return nil, sentinel }) _, err := c.Get(context.Background(), "alice") if !errors.Is(err, sentinel) { t.Errorf("err = %v", err) } } func TestGuildTokenHappy(t *testing.T) { c := New(time.Minute, func(context.Context, string) (*fabric.Session, error) { return fakeSession("g1", "g2"), nil }) tok, err := c.GuildToken(context.Background(), "alice", "g2") if err != nil || tok != "tok-g2" { t.Errorf("token=%q err=%v", tok, err) } } func TestGuildTokenMissingGuildRetriesThenErrors(t *testing.T) { var calls atomic.Int32 c := New(time.Minute, func(context.Context, string) (*fabric.Session, error) { calls.Add(1) return fakeSession("g1"), nil }) _, err := c.GuildToken(context.Background(), "alice", "missing") if err == nil { t.Fatal("expected error") } // First Get + post-invalidate Get = 2 logins if calls.Load() != 2 { t.Errorf("calls = %d, want 2 (initial + retry)", calls.Load()) } } func TestPeekDoesNotLogin(t *testing.T) { var calls atomic.Int32 c := New(time.Minute, func(context.Context, string) (*fabric.Session, error) { calls.Add(1) return fakeSession("g1"), nil }) if c.Peek("alice") != nil { t.Errorf("Peek on empty should be nil") } if calls.Load() != 0 { t.Errorf("Peek should not call Login") } }