@@ -6,7 +6,6 @@ import { propagation, trace } from '@opentelemetry/api'
66import { W3CTraceContextPropagator } from '@opentelemetry/core'
77import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'
88import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
9- import { RequestTraceV1Outcome } from '@/lib/copilot/generated/request-trace-v1'
109import type { OrchestratorResult } from '@/lib/copilot/request/types'
1110
1211const { runCopilotLifecycle } = vi . hoisted ( ( ) => ( {
@@ -34,22 +33,13 @@ describe('runHeadlessCopilotLifecycle', () => {
3433 beforeEach ( ( ) => {
3534 trace . setGlobalTracerProvider ( new BasicTracerProvider ( ) )
3635 propagation . setGlobalPropagator ( new W3CTraceContextPropagator ( ) )
37- vi . stubGlobal (
38- 'fetch' ,
39- vi . fn ( ) . mockResolvedValue (
40- new Response ( null , {
41- status : 200 ,
42- } )
43- )
44- )
4536 } )
4637
4738 afterEach ( ( ) => {
4839 vi . clearAllMocks ( )
49- vi . unstubAllGlobals ( )
5040 } )
5141
52- it ( 'reports a successful headless trace ' , async ( ) => {
42+ it ( 'runs the lifecycle and returns its result ' , async ( ) => {
5343 runCopilotLifecycle . mockResolvedValueOnce (
5444 createLifecycleResult ( {
5545 usage : { prompt : 10 , completion : 5 } ,
@@ -77,32 +67,13 @@ describe('runHeadlessCopilotLifecycle', () => {
7767 expect . objectContaining ( {
7868 simRequestId : 'req-1' ,
7969 trace : expect . any ( Object ) ,
70+ otelContext : expect . any ( Object ) ,
8071 chatId : 'chat-1' ,
8172 } )
8273 )
83-
84- expect ( fetch ) . toHaveBeenCalledTimes ( 1 )
85- const [ url , init ] = vi . mocked ( fetch ) . mock . calls [ 0 ] as [ string , RequestInit ]
86- expect ( url ) . toContain ( '/api/traces' )
87- const body = JSON . parse ( String ( init . body ) )
88- expect ( body ) . toEqual (
89- expect . objectContaining ( {
90- simRequestId : 'req-1' ,
91- outcome : RequestTraceV1Outcome . success ,
92- chatId : 'chat-1' ,
93- usage : expect . objectContaining ( {
94- inputTokens : 10 ,
95- outputTokens : 5 ,
96- } ) ,
97- cost : {
98- rawTotalCost : 3 ,
99- billedTotalCost : 3 ,
100- } ,
101- } )
102- )
10374 } )
10475
105- it ( 'reports an error trace when the lifecycle result is unsuccessful ' , async ( ) => {
76+ it ( 'returns an unsuccessful result from the lifecycle' , async ( ) => {
10677 runCopilotLifecycle . mockResolvedValueOnce (
10778 createLifecycleResult ( {
10879 success : false ,
@@ -125,9 +96,6 @@ describe('runHeadlessCopilotLifecycle', () => {
12596 )
12697
12798 expect ( result . success ) . toBe ( false )
128- const [ , init ] = vi . mocked ( fetch ) . mock . calls [ 0 ] as [ string , RequestInit ]
129- const body = JSON . parse ( String ( init . body ) )
130- expect ( body . outcome ) . toBe ( RequestTraceV1Outcome . error )
13199 } )
132100
133101 it ( 'prefers an explicit simRequestId over the payload messageId' , async ( ) => {
@@ -154,13 +122,9 @@ describe('runHeadlessCopilotLifecycle', () => {
154122 simRequestId : 'workflow-request-id' ,
155123 } )
156124 )
157-
158- const [ , init ] = vi . mocked ( fetch ) . mock . calls [ 0 ] as [ string , RequestInit ]
159- const body = JSON . parse ( String ( init . body ) )
160- expect ( body . simRequestId ) . toBe ( 'workflow-request-id' )
161125 } )
162126
163- it ( 'passes an OTel context to the lifecycle and trace report ' , async ( ) => {
127+ it ( 'threads a valid OTel context into the lifecycle' , async ( ) => {
164128 let lifecycleTraceparent = ''
165129 runCopilotLifecycle . mockImplementationOnce ( async ( _payload , options ) => {
166130 const { traceHeaders } = await import ( '@/lib/copilot/request/go/propagation' )
@@ -183,18 +147,9 @@ describe('runHeadlessCopilotLifecycle', () => {
183147 )
184148
185149 expect ( lifecycleTraceparent ) . toMatch ( / ^ 0 0 - [ 0 - 9 a - f ] { 32 } - [ 0 - 9 a - f ] { 16 } - 0 [ 0 - 9 a - f ] $ / )
186- const [ , init ] = vi . mocked ( fetch ) . mock . calls [ 0 ] as [ string , RequestInit ]
187- const headers = init . headers as Record < string , string >
188- // The outbound trace report now runs inside its own OTel child span, so
189- // traceparent has the same trace-id as the lifecycle but a different
190- // span-id. Both must stay on the same trace.
191- const lifecycleTraceId = lifecycleTraceparent . split ( '-' ) [ 1 ]
192- expect ( headers . traceparent ) . toMatch ( / ^ 0 0 - [ 0 - 9 a - f ] { 32 } - [ 0 - 9 a - f ] { 16 } - 0 [ 0 - 9 a - f ] $ / )
193- expect ( headers . traceparent . split ( '-' ) [ 1 ] ) . toBe ( lifecycleTraceId )
194- expect ( headers . traceparent . split ( '-' ) [ 2 ] ) . not . toBe ( lifecycleTraceparent . split ( '-' ) [ 2 ] )
195150 } )
196151
197- it ( 'reports an error trace when the lifecycle throws' , async ( ) => {
152+ it ( 'rethrows when the lifecycle throws' , async ( ) => {
198153 runCopilotLifecycle . mockRejectedValueOnce ( new Error ( 'kaboom' ) )
199154
200155 await expect (
@@ -212,9 +167,5 @@ describe('runHeadlessCopilotLifecycle', () => {
212167 }
213168 )
214169 ) . rejects . toThrow ( 'kaboom' )
215-
216- const [ , init ] = vi . mocked ( fetch ) . mock . calls [ 0 ] as [ string , RequestInit ]
217- const body = JSON . parse ( String ( init . body ) )
218- expect ( body . outcome ) . toBe ( RequestTraceV1Outcome . error )
219170 } )
220171} )
0 commit comments