@@ -19,8 +19,10 @@ package preciseprefixcache
1919import (
2020 "context"
2121 "encoding/json"
22+ "fmt"
2223 "testing"
2324
25+ "github.com/jellydator/ttlcache/v3"
2426 "github.com/llm-d/llm-d-router/pkg/kvcache"
2527 "github.com/llm-d/llm-d-router/pkg/kvcache/kvblock"
2628 "github.com/llm-d/llm-d-router/pkg/kvevents"
@@ -675,3 +677,99 @@ func TestNew_BlockSizeFlowsViaTokenProcessor(t *testing.T) {
675677 })
676678 }
677679}
680+
681+ type fakeSubscriberManager struct {
682+ ids []string
683+ endpoints []string
684+ }
685+
686+ func (f * fakeSubscriberManager ) EnsureSubscriber (_ context.Context , _ , _ , _ string , _ bool ) error {
687+ return nil
688+ }
689+ func (f * fakeSubscriberManager ) RemoveSubscriber (_ context.Context , _ string ) {}
690+ func (f * fakeSubscriberManager ) GetActiveSubscribers () ([]string , []string ) {
691+ return f .ids , f .endpoints
692+ }
693+ func (f * fakeSubscriberManager ) Shutdown (_ context.Context ) {}
694+
695+ func TestDumpState (t * testing.T ) {
696+ // Start() is intentionally not called: NoTTL entries never expire, and the
697+ // enumeration helpers work on an unstarted cache.
698+ specCache := ttlcache .New [string , * speculativeEntries ]()
699+ specCache .Set ("req-2" , & speculativeEntries {}, ttlcache .NoTTL )
700+ specCache .Set ("req-1" , & speculativeEntries {}, ttlcache .NoTTL )
701+
702+ p := & Producer {
703+ typedName : plugin.TypedName {Type : PluginType , Name : "x" },
704+ subscribersManager : & fakeSubscriberManager {ids : []string {"ns/pod-b" , "ns/pod-a" }},
705+ speculativeCache : specCache ,
706+ speculativeEnabled : true ,
707+ blockSizeTokens : 64 ,
708+ }
709+
710+ payload , err := p .DumpState ()
711+ require .NoError (t , err )
712+ // Subscriber pod identities and live request ids are enumerated for debugging.
713+ assert .Contains (t , string (payload ), "ns/pod-a" )
714+
715+ var state precisePrefixState
716+ require .NoError (t , json .Unmarshal (payload , & state ))
717+ assert .Equal (t , precisePrefixState {
718+ Subscribers : []string {"ns/pod-a" , "ns/pod-b" },
719+ TotalSubscribers : 2 ,
720+ MaxSubscribers : maxDumpSubscribers ,
721+ SpeculativeIndexing : true ,
722+ SpeculativeEntries : []string {"req-1" , "req-2" },
723+ TotalSpeculativeEntries : 2 ,
724+ MaxSpeculativeEntries : maxDumpSpeculativeEntries ,
725+ BlockSizeTokens : 64 ,
726+ }, state )
727+ }
728+
729+ func TestDumpStateEmpty (t * testing.T ) {
730+ p := & Producer {}
731+
732+ payload , err := p .DumpState ()
733+ require .NoError (t , err )
734+ assert .True (t , json .Valid (payload ))
735+ // Empty lists serialize as [] not null, matching the documented response shape.
736+ assert .Contains (t , string (payload ), `"subscribers":[]` )
737+ assert .Contains (t , string (payload ), `"speculativeEntries":[]` )
738+
739+ var state precisePrefixState
740+ require .NoError (t , json .Unmarshal (payload , & state ))
741+ assert .Equal (t , precisePrefixState {
742+ Subscribers : []string {},
743+ SpeculativeEntries : []string {},
744+ MaxSubscribers : maxDumpSubscribers ,
745+ MaxSpeculativeEntries : maxDumpSpeculativeEntries ,
746+ }, state )
747+ }
748+
749+ func TestDumpStateCaps (t * testing.T ) {
750+ specCache := ttlcache .New [string , * speculativeEntries ]()
751+ ids := make ([]string , 0 , maxDumpSubscribers + 5 )
752+ for i := 0 ; i < maxDumpSubscribers + 5 ; i ++ {
753+ ids = append (ids , fmt .Sprintf ("ns/pod-%03d" , i ))
754+ }
755+ for i := 0 ; i < maxDumpSpeculativeEntries + 7 ; i ++ {
756+ specCache .Set (fmt .Sprintf ("req-%04d" , i ), & speculativeEntries {}, ttlcache .NoTTL )
757+ }
758+
759+ p := & Producer {
760+ subscribersManager : & fakeSubscriberManager {ids : ids },
761+ speculativeCache : specCache ,
762+ }
763+
764+ payload , err := p .DumpState ()
765+ require .NoError (t , err )
766+
767+ var state precisePrefixState
768+ require .NoError (t , json .Unmarshal (payload , & state ))
769+ // Lists are capped, but the totals report the full counts so a consumer can
770+ // tell the dump is partial from totalX > maxX.
771+ assert .Len (t , state .Subscribers , maxDumpSubscribers )
772+ assert .Equal (t , maxDumpSubscribers + 5 , state .TotalSubscribers )
773+ assert .Len (t , state .SpeculativeEntries , maxDumpSpeculativeEntries )
774+ assert .Equal (t , maxDumpSpeculativeEntries + 7 , state .TotalSpeculativeEntries )
775+ }
0 commit comments