@@ -5,7 +5,8 @@ vi.mock("../config", () => ({
55 } ,
66} ) ) ;
77
8- import { generateHighlights } from "./highlight-model" ;
8+ import { generateHighlightsBatch } from "./highlight-model" ;
9+ import { config } from "../config" ;
910
1011const logger = {
1112 info : vi . fn ( ) ,
@@ -30,34 +31,81 @@ afterEach(() => {
3031 vi . clearAllMocks ( ) ;
3132} ) ;
3233
33- describe ( "generateHighlights " , ( ) => {
34- it ( "posts the query and full markdown to /highlight with the bearer token" , async ( ) => {
35- const fetchMock = mockFetchOnce ( { highlights : [ ] , markdown : "" } ) ;
34+ describe ( "generateHighlightsBatch " , ( ) => {
35+ it ( "posts every page to one /batch_highlight call with the bearer token" , async ( ) => {
36+ const fetchMock = mockFetchOnce ( { pages : [ ] } ) ;
3637
37- await generateHighlights ( "q1" , "# Page\n\nbody text" , { logger } ) ;
38+ await generateHighlightsBatch (
39+ "q1" ,
40+ [
41+ { id : "0" , markdown : "# First\n\nbody text" } ,
42+ { id : "1" , markdown : "# Second\n\nmore text" } ,
43+ ] ,
44+ { logger } ,
45+ ) ;
3846
3947 expect ( fetchMock ) . toHaveBeenCalledTimes ( 1 ) ;
4048 const [ url , init ] = fetchMock . mock . calls [ 0 ] ;
41- expect ( url ) . toBe ( "https://highlight.test/highlight " ) ;
49+ expect ( url ) . toBe ( "https://highlight.test/batch_highlight " ) ;
4250 expect ( init . method ) . toBe ( "POST" ) ;
4351 expect ( init . headers . Authorization ) . toBe ( "Bearer secret-token" ) ;
4452
4553 const sent = JSON . parse ( init . body ) ;
46- expect ( sent ) . toEqual ( { query : "q1" , markdown : "# Page\n\nbody text" } ) ;
54+ expect ( sent ) . toEqual ( {
55+ query : "q1" ,
56+ pages : [
57+ { id : "0" , markdown : "# First\n\nbody text" } ,
58+ { id : "1" , markdown : "# Second\n\nmore text" } ,
59+ ] ,
60+ } ) ;
4761 } ) ;
4862
49- it ( "returns the highlights and reassembled markdown from the service" , async ( ) => {
63+ it ( "omits bearer auth for the in-cluster service when no token is configured" , async ( ) => {
64+ const token = config . HIGHLIGHT_MODEL_TOKEN ;
65+ config . HIGHLIGHT_MODEL_TOKEN = undefined ;
66+ const fetchMock = mockFetchOnce ( { pages : [ ] } ) ;
67+
68+ try {
69+ await generateHighlightsBatch ( "q1" , [ { id : "0" , markdown : "markdown" } ] , {
70+ logger,
71+ } ) ;
72+ } finally {
73+ config . HIGHLIGHT_MODEL_TOKEN = token ;
74+ }
75+
76+ const [ , init ] = fetchMock . mock . calls [ 0 ] ;
77+ expect ( init . headers ) . toEqual ( { "Content-Type" : "application/json" } ) ;
78+ } ) ;
79+
80+ it ( "returns successful pages keyed by ID and leaves missing pages absent" , async ( ) => {
5081 mockFetchOnce ( {
51- highlights : [
52- { block_index : 0 , score : 0.9 } ,
53- { block_index : 2 , score : 0.5 } ,
82+ pages : [
83+ {
84+ id : "1" ,
85+ cache : "hit" ,
86+ output : {
87+ highlights : [
88+ { block_index : 0 , score : 0.9 } ,
89+ { block_index : 2 , score : 0.5 } ,
90+ ] ,
91+ markdown : "reassembled answer" ,
92+ } ,
93+ } ,
94+ { id : "invalid-without-output" } ,
5495 ] ,
55- markdown : "reassembled answer" ,
5696 } ) ;
5797
58- const out = await generateHighlights ( "q" , "some markdown" , { logger } ) ;
98+ const out = await generateHighlightsBatch (
99+ "q" ,
100+ [
101+ { id : "0" , markdown : "missing" } ,
102+ { id : "1" , markdown : "some markdown" } ,
103+ ] ,
104+ { logger } ,
105+ ) ;
59106
60- expect ( out ) . toEqual ( {
107+ expect ( out ?. get ( "0" ) ) . toBeUndefined ( ) ;
108+ expect ( out ?. get ( "1" ) ) . toEqual ( {
61109 highlights : [
62110 { block_index : 0 , score : 0.9 } ,
63111 { block_index : 2 , score : 0.5 } ,
@@ -66,37 +114,117 @@ describe("generateHighlights", () => {
66114 } ) ;
67115 } ) ;
68116
69- it ( "debug-logs the highlights array " , async ( ) => {
117+ it ( "debug-logs batch coverage and highlights " , async ( ) => {
70118 mockFetchOnce ( {
71- highlights : [ { block_index : 1 , score : 0.8 } ] ,
72- markdown : "x" ,
119+ pages : [
120+ {
121+ id : "page-1" ,
122+ output : {
123+ highlights : [ { block_index : 1 , score : 0.8 } ] ,
124+ markdown : "x" ,
125+ } ,
126+ } ,
127+ ] ,
73128 } ) ;
74129
75- await generateHighlights ( "q" , "md" , { logger } ) ;
130+ await generateHighlightsBatch ( "q" , [ { id : "page-1" , markdown : "md" } ] , {
131+ logger,
132+ } ) ;
76133
77134 expect ( logger . debug ) . toHaveBeenCalledWith (
78- "query highlights" ,
135+ "query highlights batch " ,
79136 expect . objectContaining ( {
80137 canonicalLog : "search/highlights" ,
81- highlights : [ { block_index : 1 , score : 0.8 } ] ,
138+ requestedPages : 1 ,
139+ returnedPages : 1 ,
140+ pages : [
141+ {
142+ id : "page-1" ,
143+ highlights : [ { block_index : 1 , score : 0.8 } ] ,
144+ } ,
145+ ] ,
82146 } ) ,
83147 ) ;
84148 } ) ;
85149
86- it ( "defaults missing fields to empty highlights and markdown " , async ( ) => {
150+ it ( "defaults missing pages to an empty result map " , async ( ) => {
87151 mockFetchOnce ( { } ) ;
88152
89- const out = await generateHighlights ( "q" , "md" , { logger } ) ;
153+ const out = await generateHighlightsBatch (
154+ "q" ,
155+ [ { id : "0" , markdown : "md" } ] ,
156+ { logger } ,
157+ ) ;
90158
91- expect ( out ) . toEqual ( { highlights : [ ] , markdown : "" } ) ;
159+ expect ( out ) . toEqual ( new Map ( ) ) ;
160+ } ) ;
161+
162+ it ( "does not call the service for an empty batch" , async ( ) => {
163+ const fetchMock = vi . fn ( ) ;
164+ vi . stubGlobal ( "fetch" , fetchMock ) ;
165+
166+ const out = await generateHighlightsBatch ( "q" , [ ] , { logger } ) ;
167+
168+ expect ( fetchMock ) . not . toHaveBeenCalled ( ) ;
169+ expect ( out ) . toEqual ( new Map ( ) ) ;
92170 } ) ;
93171
94172 it ( "returns null when the service errors" , async ( ) => {
95173 mockFetchOnce ( { error : "boom" } , false , 500 ) ;
96174
97- const out = await generateHighlights ( "q" , "md" , { logger } ) ;
175+ const out = await generateHighlightsBatch (
176+ "q" ,
177+ [ { id : "0" , markdown : "md" } ] ,
178+ { logger } ,
179+ ) ;
98180
99181 expect ( out ) . toBeNull ( ) ;
100182 expect ( logger . warn ) . toHaveBeenCalled ( ) ;
101183 } ) ;
184+
185+ it ( "falls back to legacy per-page calls while the old service URL is configured" , async ( ) => {
186+ const fetchMock = vi
187+ . fn ( )
188+ . mockResolvedValueOnce ( {
189+ ok : false ,
190+ status : 400 ,
191+ text : async ( ) => "legacy batch contract" ,
192+ } )
193+ . mockResolvedValueOnce ( {
194+ ok : true ,
195+ status : 200 ,
196+ json : async ( ) => ( { markdown : "first legacy highlight" } ) ,
197+ text : async ( ) => "" ,
198+ } )
199+ . mockResolvedValueOnce ( {
200+ ok : false ,
201+ status : 500 ,
202+ json : async ( ) => ( { } ) ,
203+ text : async ( ) => "page failed" ,
204+ } ) ;
205+ vi . stubGlobal ( "fetch" , fetchMock ) ;
206+
207+ const out = await generateHighlightsBatch (
208+ "q" ,
209+ [
210+ { id : "0" , markdown : "first" } ,
211+ { id : "1" , markdown : "second" } ,
212+ ] ,
213+ { logger } ,
214+ ) ;
215+
216+ expect ( fetchMock ) . toHaveBeenCalledTimes ( 3 ) ;
217+ expect ( fetchMock . mock . calls . map ( call => call [ 0 ] ) ) . toEqual ( [
218+ "https://highlight.test/batch_highlight" ,
219+ "https://highlight.test/highlight" ,
220+ "https://highlight.test/highlight" ,
221+ ] ) ;
222+ expect ( out ) . toEqual (
223+ new Map ( [ [ "0" , { highlights : [ ] , markdown : "first legacy highlight" } ] ] ) ,
224+ ) ;
225+ expect ( logger . warn ) . toHaveBeenCalledWith (
226+ "legacy query highlight failed" ,
227+ expect . objectContaining ( { pageId : "1" } ) ,
228+ ) ;
229+ } ) ;
102230} ) ;
0 commit comments