2026-05-28 01:36:52 +03:00
package core
import (
"encoding/json"
"strings"
"testing"
"time"
2026-06-29 10:36:46 +03:00
"github.com/PuerkitoBio/goquery"
2026-05-28 01:36:52 +03:00
)
func TestEnvelopeAlwaysIncludesSerpFeatures ( t * testing . T ) {
env := NewEnvelope ( Query { Text : "golang" } , "req-1" , time . Unix ( 0 , 0 ) , [ ] string { "google" } )
data , err := json . Marshal ( env )
if err != nil {
t . Fatalf ( "marshal envelope: %v" , err )
}
if ! strings . Contains ( string ( data ) , ` "serp_features":[] ` ) {
t . Fatalf ( "expected empty serp_features array in JSON, got %s" , data )
}
}
func TestAppendEnrichedSearchResultAddsFeatureSurface ( t * testing . T ) {
env := NewEnvelope ( Query { Text : "weather" } , "req-1" , time . Unix ( 0 , 0 ) , [ ] string { "google" } )
raw := SearchResult {
Rank : 1 ,
URL : "https://example.com/weather" ,
Title : "Weather result" ,
Description : "Organic snippet" ,
Features : [ ] SerpFeature { {
Type : ResultTypeAnswerBox ,
Text : "72 F and sunny" ,
Links : [ ] FeatureLink { {
Title : "Weather source" ,
URL : "https://example.com/weather" ,
} } ,
Position : & Position { Absolute : 1 } ,
Confidence : 0.9 ,
} } ,
}
AppendEnrichedSearchResult ( env , raw , EnrichContext { Engine : "google" , Query : Query { } } , time . Unix ( 0 , 0 ) )
if len ( env . Results ) != 1 {
t . Fatalf ( "expected 1 result, got %d" , len ( env . Results ) )
}
if len ( env . SerpFeatures ) != 1 {
t . Fatalf ( "expected 1 feature, got %d" , len ( env . SerpFeatures ) )
}
feature := env . SerpFeatures [ 0 ]
if feature . ID == "" || ! strings . HasPrefix ( feature . ID , "f_" ) {
t . Fatalf ( "expected stable feature ID, got %q" , feature . ID )
}
if feature . Engine != "google" {
t . Fatalf ( "feature engine = %q, want google" , feature . Engine )
}
if feature . ExtractedAt != "1970-01-01T00:00:00Z" {
t . Fatalf ( "feature extracted_at = %q" , feature . ExtractedAt )
}
if len ( feature . SourceResultIDs ) != 1 || feature . SourceResultIDs [ 0 ] != env . Results [ 0 ] . ID {
t . Fatalf ( "expected feature to reference result ID %q, got %#v" , env . Results [ 0 ] . ID , feature . SourceResultIDs )
}
}
func TestAppendEnrichedSearchResultMirrorsExistingAnswerResultAsFeature ( t * testing . T ) {
env := NewEnvelope ( Query { Text : "weather" } , "req-1" , time . Unix ( 0 , 0 ) , [ ] string { "google" } )
raw := SearchResult {
Rank : - 1 ,
Type : ResultTypeAnswerBox ,
URL : "https://example.com/weather" ,
Title : "Weather" ,
Description : "72 F and sunny" ,
}
AppendEnrichedSearchResult ( env , raw , EnrichContext { Engine : "google" , Query : Query { } } , time . Unix ( 0 , 0 ) )
if len ( env . Results ) != 1 {
t . Fatalf ( "expected existing result to be preserved, got %d results" , len ( env . Results ) )
}
if len ( env . SerpFeatures ) != 1 {
t . Fatalf ( "expected mirrored feature, got %d" , len ( env . SerpFeatures ) )
}
if env . SerpFeatures [ 0 ] . Type != ResultTypeAnswerBox {
t . Fatalf ( "mirrored feature type = %q" , env . SerpFeatures [ 0 ] . Type )
}
if env . SerpFeatures [ 0 ] . Text != "72 F and sunny" {
t . Fatalf ( "mirrored feature text = %q" , env . SerpFeatures [ 0 ] . Text )
}
if len ( env . SerpFeatures [ 0 ] . SourceResultIDs ) != 1 || env . SerpFeatures [ 0 ] . SourceResultIDs [ 0 ] != env . Results [ 0 ] . ID {
t . Fatalf ( "mirrored feature did not reference result: %#v" , env . SerpFeatures [ 0 ] . SourceResultIDs )
}
}
2026-06-29 10:36:46 +03:00
func TestStripResultFeatures ( t * testing . T ) {
results := [ ] SearchResult {
{
Rank : 1 ,
URL : "https://example.com" ,
Title : "Example" ,
Features : [ ] SerpFeature { {
Type : ResultTypeRelatedSearches ,
Items : [ ] FeatureItem {
{ Text : "example search" } ,
} ,
} } ,
} ,
{
Rank : 2 ,
URL : "https://example.org" ,
Title : "Example Org" ,
} ,
}
if kept := StripResultFeatures ( results , true ) ; len ( kept [ 0 ] . Features ) != 1 {
t . Fatalf ( "keep=true must preserve features, got %#v" , kept [ 0 ] . Features )
}
stripped := StripResultFeatures ( results , false )
if len ( stripped ) != 2 {
t . Fatalf ( "expected result count to be preserved, got %d" , len ( stripped ) )
}
for i , result := range stripped {
if len ( result . Features ) != 0 {
t . Fatalf ( "result %d kept features: %#v" , i , result . Features )
}
}
if stripped [ 0 ] . URL != "https://example.com" || stripped [ 1 ] . Rank != 2 {
t . Fatalf ( "non-feature fields changed: %#v" , stripped )
}
}
func TestFeatureItemsUseTextAsTitleAndStripInvisibleCharacters ( t * testing . T ) {
doc , err := goquery . NewDocumentFromReader ( strings . NewReader ( `
< div class = "related" >
< a href = "?q=python+coding" > python coding & # 8203 ; < / a >
< / div > ` ) )
if err != nil {
t . Fatalf ( "parse fixture: %v" , err )
}
features := ExtractSerpFeaturesBySelectors ( doc , [ ] SerpFeatureSelector { {
Type : ResultTypeRelatedSearches ,
Title : "Related searches" ,
Container : [ ] string { ".related" } ,
ItemSelector : [ ] string { "a" } ,
LinkSelector : [ ] string { "a" } ,
} } )
if len ( features ) != 1 || len ( features [ 0 ] . Items ) != 1 {
t . Fatalf ( "expected one feature item, got %#v" , features )
}
item := features [ 0 ] . Items [ 0 ]
if item . Title != "python coding" || item . Text != "python coding" {
t . Fatalf ( "unexpected item text/title: %#v" , item )
}
}
func TestEnrichSerpFeatureNormalizesRelativeLinks ( t * testing . T ) {
feature := EnrichSerpFeature ( SerpFeature {
Type : ResultTypeRelatedSearches ,
Items : [ ] FeatureItem { {
Text : "python coding\u200b" ,
Link : "?q=python+coding&t=h" ,
} } ,
Links : [ ] FeatureLink { {
Title : "baidu related" ,
URL : "/s?wd=python" ,
} } ,
} , "duckduckgo" , "" , time . Unix ( 0 , 0 ) )
if feature . Items [ 0 ] . Title != "python coding" || feature . Items [ 0 ] . Text != "python coding" {
t . Fatalf ( "unexpected enriched item: %#v" , feature . Items [ 0 ] )
}
if got , want := feature . Items [ 0 ] . Link , "https://duckduckgo.com/?q=python+coding&t=h" ; got != want {
t . Fatalf ( "item link = %q, want %q" , got , want )
}
if got , want := feature . Links [ 0 ] . URL , "https://duckduckgo.com/s?wd=python" ; got != want {
t . Fatalf ( "feature link = %q, want %q" , got , want )
}
}
2026-05-28 01:36:52 +03:00
func TestRenderersIncludeSerpFeatures ( t * testing . T ) {
env := NewEnvelope ( Query { Text : "openserp" } , "req-1" , time . Unix ( 0 , 0 ) , [ ] string { "google" } )
AppendEnrichedSearchResult ( env , SearchResult {
Rank : 1 ,
URL : "https://example.com/result" ,
Title : "Organic result" ,
Description : "Snippet" ,
Features : [ ] SerpFeature {
{
Type : ResultTypeAISummary ,
Text : "OpenSERP is a search API." ,
Links : [ ] FeatureLink { {
Title : "Example" ,
URL : "https://example.com/source" ,
} } ,
Position : & Position { Absolute : 1 } ,
Confidence : 0.95 ,
} ,
{
Type : ResultTypeRelatedSearches ,
Items : [ ] FeatureItem {
{ Text : "openserp cloud" } ,
{ Text : "serp api" } ,
} ,
} ,
} ,
} , EnrichContext { Engine : "google" , Query : Query { } } , time . Unix ( 0 , 0 ) )
env . Finalize ( time . Unix ( 0 , 0 ) , Query { Text : "openserp" , Limit : 10 } )
markdown := string ( RenderMarkdown ( env ) )
if ! strings . Contains ( markdown , "## AI summary" ) || ! strings . Contains ( markdown , "Sources:" ) {
t . Fatalf ( "markdown missing AI summary feature section:\n%s" , markdown )
}
if ! strings . Contains ( markdown , "## Results" ) {
t . Fatalf ( "markdown missing Results section:\n%s" , markdown )
}
if ! strings . Contains ( markdown , "## Related searches" ) {
t . Fatalf ( "markdown missing related searches section:\n%s" , markdown )
}
// Spec order: AI summary -> ... -> Results -> Related searches.
if aiIdx , resIdx , relIdx := strings . Index ( markdown , "## AI summary" ) , strings . Index ( markdown , "## Results" ) , strings . Index ( markdown , "## Related searches" ) ; ! ( aiIdx < resIdx && resIdx < relIdx ) {
t . Fatalf ( "markdown section order wrong: ai=%d results=%d related=%d\n%s" , aiIdx , resIdx , relIdx , markdown )
}
text := string ( RenderText ( env ) )
if ! strings . Contains ( text , "AI summary\n" ) || ! strings . Contains ( text , "Related searches\n" ) {
t . Fatalf ( "text missing feature sections:\n%s" , text )
}
lines := strings . Split ( strings . TrimSpace ( string ( RenderNDJSON ( env ) ) ) , "\n" )
if len ( lines ) != 3 {
t . Fatalf ( "expected 3 ndjson lines, got %d: %v" , len ( lines ) , lines )
}
if ! strings . Contains ( lines [ 0 ] , ` "kind":"result" ` ) {
t . Fatalf ( "first ndjson line should be a result, got %s" , lines [ 0 ] )
}
if ! strings . Contains ( lines [ 1 ] , ` "kind":"feature" ` ) || ! strings . Contains ( lines [ 2 ] , ` "kind":"feature" ` ) {
t . Fatalf ( "feature ndjson lines missing kind tag: %v" , lines )
}
}
2026-06-02 04:21:56 +03:00
// TestRenderFeaturesUnorderedTypeStillRenders guards against silently dropping a
// feature whose Type is in neither render-order section (e.g. a newly added
// enum value not yet placed). It must still appear in text and markdown output.
func TestRenderFeaturesUnorderedTypeStillRenders ( t * testing . T ) {
const unplaced ResultType = "experimental_module"
env := NewEnvelope ( Query { Text : "openserp" } , "req-1" , time . Unix ( 0 , 0 ) , [ ] string { "google" } )
env . SerpFeatures = append ( env . SerpFeatures , SerpFeature {
Type : unplaced ,
Text : "experimental feature body" ,
} )
text := string ( RenderText ( env ) )
if ! strings . Contains ( text , "experimental feature body" ) {
t . Fatalf ( "text output dropped an unordered feature type:\n%s" , text )
}
markdown := string ( RenderMarkdown ( env ) )
if ! strings . Contains ( markdown , "experimental feature body" ) {
t . Fatalf ( "markdown output dropped an unordered feature type:\n%s" , markdown )
}
}