@@ -132,3 +132,94 @@ func TestSuspectFallbackEdges_FindPageBoundsFallbackEdgeAnalysis(t *testing.T) {
132132 t .Fatalf ("unexpected pagination: %+v" , got .Pagination )
133133 }
134134}
135+
136+ func TestSuspectFallbackEdges_FindPageCanReturnFewerSuspectsThanLimitWhileHasMoreRemains (t * testing.T ) {
137+ db := setupFallbackDB (t )
138+ ctx := context .Background ()
139+ store := gormstore .New (db )
140+
141+ // First fallback edge is suspect.
142+ {
143+ source := model.Node {QualifiedName : "pkg.SourceA" , Kind : model .NodeKindFunction , Name : "SourceA" , FilePath : "src.go" , StartLine : 1 , EndLine : 2 , Language : "go" }
144+ target := model.Node {QualifiedName : "pkg.TargetA" , Kind : model .NodeKindFunction , Name : "TargetA" , FilePath : "target.go" , StartLine : 1 , EndLine : 2 , Language : "go" }
145+ if err := store .UpsertNodes (ctx , []model.Node {source , target }); err != nil {
146+ t .Fatal (err )
147+ }
148+ sourceNode , _ := store .GetNode (ctx , source .QualifiedName )
149+ targetNode , _ := store .GetNode (ctx , target .QualifiedName )
150+ if err := store .UpsertEdges (ctx , []model.Edge {{FromNodeID : sourceNode .ID , ToNodeID : targetNode .ID , Kind : model .EdgeKindFallbackCalls , Fingerprint : "fallback-a" }}); err != nil {
151+ t .Fatal (err )
152+ }
153+ if err := store .UpsertAnnotation (ctx , & model.Annotation {NodeID : sourceNode .ID , Tags : []model.DocTag {{Kind : model .TagIntent , Value : "source auth" , Ordinal : 0 }}}); err != nil {
154+ t .Fatal (err )
155+ }
156+ if err := store .UpsertAnnotation (ctx , & model.Annotation {NodeID : targetNode .ID , Tags : []model.DocTag {{Kind : model .TagIntent , Value : "invoice render" , Ordinal : 0 }}}); err != nil {
157+ t .Fatal (err )
158+ }
159+ }
160+
161+ // Second fallback edge is suppressed by overlapping annotation context.
162+ {
163+ source := model.Node {QualifiedName : "pkg.SourceB" , Kind : model .NodeKindFunction , Name : "SourceB" , FilePath : "src.go" , StartLine : 1 , EndLine : 2 , Language : "go" }
164+ target := model.Node {QualifiedName : "pkg.TargetB" , Kind : model .NodeKindFunction , Name : "TargetB" , FilePath : "target.go" , StartLine : 1 , EndLine : 2 , Language : "go" }
165+ if err := store .UpsertNodes (ctx , []model.Node {source , target }); err != nil {
166+ t .Fatal (err )
167+ }
168+ sourceNode , _ := store .GetNode (ctx , source .QualifiedName )
169+ targetNode , _ := store .GetNode (ctx , target .QualifiedName )
170+ if err := store .UpsertEdges (ctx , []model.Edge {{FromNodeID : sourceNode .ID , ToNodeID : targetNode .ID , Kind : model .EdgeKindFallbackCalls , Fingerprint : "fallback-b" }}); err != nil {
171+ t .Fatal (err )
172+ }
173+ if err := store .UpsertAnnotation (ctx , & model.Annotation {NodeID : sourceNode .ID , Tags : []model.DocTag {{Kind : model .TagIntent , Value : "session token" , Ordinal : 0 }}}); err != nil {
174+ t .Fatal (err )
175+ }
176+ if err := store .UpsertAnnotation (ctx , & model.Annotation {NodeID : targetNode .ID , Tags : []model.DocTag {{Kind : model .TagIntent , Value : "session token refresh" , Ordinal : 0 }}}); err != nil {
177+ t .Fatal (err )
178+ }
179+ }
180+
181+ // Third fallback edge is suspect and keeps HasMore true even when the page returns only one item.
182+ {
183+ source := model.Node {QualifiedName : "pkg.SourceC" , Kind : model .NodeKindFunction , Name : "SourceC" , FilePath : "src.go" , StartLine : 1 , EndLine : 2 , Language : "go" }
184+ target := model.Node {QualifiedName : "pkg.TargetC" , Kind : model .NodeKindFunction , Name : "TargetC" , FilePath : "target.go" , StartLine : 1 , EndLine : 2 , Language : "go" }
185+ if err := store .UpsertNodes (ctx , []model.Node {source , target }); err != nil {
186+ t .Fatal (err )
187+ }
188+ sourceNode , _ := store .GetNode (ctx , source .QualifiedName )
189+ targetNode , _ := store .GetNode (ctx , target .QualifiedName )
190+ if err := store .UpsertEdges (ctx , []model.Edge {{FromNodeID : sourceNode .ID , ToNodeID : targetNode .ID , Kind : model .EdgeKindFallbackCalls , Fingerprint : "fallback-c" }}); err != nil {
191+ t .Fatal (err )
192+ }
193+ if err := store .UpsertAnnotation (ctx , & model.Annotation {NodeID : sourceNode .ID , Tags : []model.DocTag {{Kind : model .TagIntent , Value : "source auth c" , Ordinal : 0 }}}); err != nil {
194+ t .Fatal (err )
195+ }
196+ if err := store .UpsertAnnotation (ctx , & model.Annotation {NodeID : targetNode .ID , Tags : []model.DocTag {{Kind : model .TagIntent , Value : "invoice render c" , Ordinal : 0 }}}); err != nil {
197+ t .Fatal (err )
198+ }
199+ }
200+
201+ got , err := New (db , store ).FindSuspectsPage (ctx , Options {Page : paging.Request {Limit : 1 , Offset : 0 }})
202+ if err != nil {
203+ t .Fatal (err )
204+ }
205+ if len (got .Items ) != 1 {
206+ t .Fatalf ("expected 1 suspect from the first scanned fallback edge, got %d" , len (got .Items ))
207+ }
208+ if got .Items [0 ].Edge .Fingerprint != "fallback-a" {
209+ t .Fatalf ("unexpected suspect edge: %s" , got .Items [0 ].Edge .Fingerprint )
210+ }
211+ if got .Pagination .Limit != 1 || got .Pagination .Offset != 0 || got .Pagination .Returned != 1 || ! got .Pagination .HasMore {
212+ t .Fatalf ("unexpected pagination: %+v" , got .Pagination )
213+ }
214+
215+ got , err = New (db , store ).FindSuspectsPage (ctx , Options {Page : paging.Request {Limit : 1 , Offset : 1 }})
216+ if err != nil {
217+ t .Fatal (err )
218+ }
219+ if len (got .Items ) != 0 {
220+ t .Fatalf ("expected 0 suspects from a page containing only suppressed fallback edges, got %d" , len (got .Items ))
221+ }
222+ if got .Pagination .Limit != 1 || got .Pagination .Offset != 1 || got .Pagination .Returned != 0 || ! got .Pagination .HasMore {
223+ t .Fatalf ("unexpected pagination for empty suspect page: %+v" , got .Pagination )
224+ }
225+ }
0 commit comments