UNPKG

@zendesk/retrace

Version:

define and capture Product Operation Traces along with computed metrics with an optional friendly React beacon API

1,025 lines (918 loc) 31.9 kB
import { describe, expect, it } from 'vitest' import { findMatchingSpan, fromDefinition, type SpanAndAnnotationForMatching, } from './matchSpan' import type { SpanAndAnnotation, SpanAnnotation } from './spanAnnotationTypes' import type { PerformanceEntrySpan, Span } from './spanTypes' import type { TicketIdRelationSchemasFixture } from './testUtility/fixtures/relationSchemas' import type { MapSchemaToTypes, TraceContext } from './types' // Mock data setup const mockRelations: MapSchemaToTypes< TicketIdRelationSchemasFixture['ticket'] > = { ticketId: '123', } const createMockSpan = ( overrides: Partial<PerformanceEntrySpan<TicketIdRelationSchemasFixture>> = {}, ): PerformanceEntrySpan<TicketIdRelationSchemasFixture> => ({ type: 'measure', id: '123', name: 'testEntry', startTime: { now: Date.now(), epoch: Date.now(), }, relatedTo: mockRelations, attributes: { attr1: 'value1', attr2: 2, }, duration: 100, status: 'ok', getParentSpan: () => undefined, ...overrides, }) const createMockAnnotation = ( overrides: Partial<SpanAnnotation> = {}, ): SpanAnnotation => ({ id: '', occurrence: 1, operationRelativeEndTime: 0, operationRelativeStartTime: 0, recordedInState: 'active', labels: [], ...overrides, }) const createMockSpanAndAnnotation = ( spanOverrides: Partial< PerformanceEntrySpan<TicketIdRelationSchemasFixture> > = {}, annotationOverrides: Partial<SpanAnnotation> = {}, ): SpanAndAnnotation<TicketIdRelationSchemasFixture> => ({ span: createMockSpan(spanOverrides), annotation: createMockAnnotation(annotationOverrides), }) const mockContext: TraceContext< 'ticket', TicketIdRelationSchemasFixture, 'origin' > = { input: { id: '123', relatedTo: mockRelations, startTime: { now: Date.now(), epoch: Date.now(), }, variant: 'origin', }, definition: { name: 'test', type: 'operation', relationSchemaName: 'ticket', relationSchema: { ticketId: String }, requiredSpans: [() => true], computedSpanDefinitions: {}, computedValueDefinitions: {}, variants: { origin: { timeout: 10_000 }, }, }, recordedItemsByLabel: {}, recordedItems: new Map(), } as const describe('findMatchingSpan', () => { describe('basic functionality', () => { it('should return undefined for empty array', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const result = findMatchingSpan( matcher, [] as SpanAndAnnotation<TicketIdRelationSchemasFixture>[], mockContext, ) expect(result).toBeUndefined() }) it('should return the first matching span', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = [ createMockSpanAndAnnotation({ name: 'nonMatch' }), createMockSpanAndAnnotation({ name: 'testEntry' }), createMockSpanAndAnnotation({ name: 'testEntry' }), ] const result = findMatchingSpan(matcher, spanAndAnnotations, mockContext) expect(result).toBe(spanAndAnnotations[1]) }) it('should return undefined when no match is found', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'nonExistent' }) const spanAndAnnotations = [ createMockSpanAndAnnotation({ name: 'testEntry1' }), createMockSpanAndAnnotation({ name: 'testEntry2' }), ] const result = findMatchingSpan(matcher, spanAndAnnotations, mockContext) expect(result).toBeUndefined() }) it('should work with complex matchers', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry', type: 'element', status: 'ok', }) const spanAndAnnotations = [ createMockSpanAndAnnotation({ name: 'testEntry', type: 'element', status: 'error', }), createMockSpanAndAnnotation({ name: 'testEntry', type: 'mark', status: 'ok', }), createMockSpanAndAnnotation({ name: 'testEntry', type: 'element', status: 'ok', }), ] const result = findMatchingSpan(matcher, spanAndAnnotations, mockContext) expect(result).toBe(spanAndAnnotations[2]) }) }) describe('nthMatch functionality', () => { const createTestData = () => [ createMockSpanAndAnnotation({ name: 'testEntry', id: 'span1' }), createMockSpanAndAnnotation({ name: 'nonMatch', id: 'span2' }), createMockSpanAndAnnotation({ name: 'testEntry', id: 'span3' }), createMockSpanAndAnnotation({ name: 'testEntry', id: 'span4' }), createMockSpanAndAnnotation({ name: 'nonMatch', id: 'span5' }), ] it('should return first match when nthMatch is undefined', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = createTestData() const result = findMatchingSpan(matcher, spanAndAnnotations, mockContext) expect(result?.span.id).toBe('span1') }) it('should return first match when nthMatch is 0', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = createTestData() const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { nthMatch: 0 }, ) expect(result?.span.id).toBe('span1') }) it('should return second match when nthMatch is 1', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = createTestData() const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { nthMatch: 1 }, ) expect(result?.span.id).toBe('span3') }) it('should return third match when nthMatch is 2', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = createTestData() const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { nthMatch: 2 }, ) expect(result?.span.id).toBe('span4') }) it('should return undefined when nthMatch exceeds available matches', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = createTestData() const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { nthMatch: 5 }, ) expect(result).toBeUndefined() }) it('should return last match when nthMatch is -1', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = createTestData() const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { nthMatch: -1 }, ) expect(result?.span.id).toBe('span4') }) it('should return second-to-last match when nthMatch is -2', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = createTestData() const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { nthMatch: -2 }, ) expect(result?.span.id).toBe('span3') }) it('should return first match when nthMatch is -3', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = createTestData() const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { nthMatch: -3 }, ) expect(result?.span.id).toBe('span1') }) it('should return undefined when negative nthMatch exceeds available matches', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = createTestData() const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { nthMatch: -5 }, ) expect(result).toBeUndefined() }) it('should override matcher nthMatch with config nthMatch', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) matcher.nthMatch = 0 // Set matcher to return first match const spanAndAnnotations = createTestData() // Override with config to return second match const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { nthMatch: 1 }, ) expect(result?.span.id).toBe('span3') }) }) describe('lowestIndexToConsider functionality', () => { const createTestData = () => [ createMockSpanAndAnnotation({ name: 'testEntry', id: 'span1' }), createMockSpanAndAnnotation({ name: 'testEntry', id: 'span2' }), createMockSpanAndAnnotation({ name: 'testEntry', id: 'span3' }), createMockSpanAndAnnotation({ name: 'testEntry', id: 'span4' }), ] it('should start from index 0 by default', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = createTestData() const result = findMatchingSpan(matcher, spanAndAnnotations, mockContext) expect(result?.span.id).toBe('span1') }) it('should start from specified lowestIndexToConsider', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = createTestData() const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { lowestIndexToConsider: 2 }, ) expect(result?.span.id).toBe('span3') }) it('should return undefined when lowestIndexToConsider is beyond array length', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = createTestData() const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { lowestIndexToConsider: 10 }, ) expect(result).toBeUndefined() }) it('should work with lowestIndexToConsider and nthMatch together', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = createTestData() // Start from index 1, get second match (which would be span4) const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { lowestIndexToConsider: 1, nthMatch: 1, }, ) expect(result?.span.id).toBe('span3') }) it('should override matcher lowestIndexToConsider with config lowestIndexToConsider', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) matcher.lowestIndexToConsider = 0 const spanAndAnnotations = createTestData() const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { lowestIndexToConsider: 2 }, ) expect(result?.span.id).toBe('span3') }) }) describe('highestIndexToConsider functionality', () => { const createTestData = () => [ createMockSpanAndAnnotation({ name: 'testEntry', id: 'span1' }), createMockSpanAndAnnotation({ name: 'testEntry', id: 'span2' }), createMockSpanAndAnnotation({ name: 'testEntry', id: 'span3' }), createMockSpanAndAnnotation({ name: 'testEntry', id: 'span4' }), ] it('should search through entire array by default', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = createTestData() const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { nthMatch: -1 }, ) expect(result?.span.id).toBe('span4') // Last match in entire array }) it('should limit search to highestIndexToConsider', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = createTestData() // Only search through first 2 elements (indices 0-1) const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { highestIndexToConsider: 1, nthMatch: -1, }, ) expect(result?.span.id).toBe('span2') // Last match in limited range }) it('should handle highestIndexToConsider beyond array length', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = createTestData() const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { highestIndexToConsider: 100, nthMatch: -1, }, ) expect(result?.span.id).toBe('span4') // Should not exceed actual array length }) it('should work with lowestIndexToConsider and highestIndexToConsider together', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = createTestData() // Search only middle elements (indices 1-2) const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { lowestIndexToConsider: 1, highestIndexToConsider: 2, }, ) expect(result?.span.id).toBe('span2') // First match in range }) it('should return undefined when lowestIndexToConsider > highestIndexToConsider', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = createTestData() const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { lowestIndexToConsider: 3, highestIndexToConsider: 1, }, ) expect(result).toBeUndefined() }) it('should override matcher highestIndexToConsider with config highestIndexToConsider', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) matcher.highestIndexToConsider = 3 const spanAndAnnotations = createTestData() const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { highestIndexToConsider: 1, nthMatch: -1, }, ) expect(result?.span.id).toBe('span2') }) }) describe('complex scenarios', () => { it('should handle all config options together with positive nthMatch', () => { const spanAndAnnotations = [ createMockSpanAndAnnotation({ name: 'testEntry', id: 'span1' }), // index 0 createMockSpanAndAnnotation({ name: 'nonMatch', id: 'span2' }), // index 1 createMockSpanAndAnnotation({ name: 'testEntry', id: 'span3' }), // index 2 createMockSpanAndAnnotation({ name: 'testEntry', id: 'span4' }), // index 3 createMockSpanAndAnnotation({ name: 'testEntry', id: 'span5' }), // index 4 createMockSpanAndAnnotation({ name: 'nonMatch', id: 'span6' }), // index 5 ] const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) // Search from index 2 to 4, get second match (should be span5) const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { lowestIndexToConsider: 2, highestIndexToConsider: 4, nthMatch: 1, }, ) expect(result?.span.id).toBe('span4') }) it('should handle all config options together with negative nthMatch', () => { const spanAndAnnotations = [ createMockSpanAndAnnotation({ name: 'testEntry', id: 'span1' }), // index 0 createMockSpanAndAnnotation({ name: 'nonMatch', id: 'span2' }), // index 1 createMockSpanAndAnnotation({ name: 'testEntry', id: 'span3' }), // index 2 createMockSpanAndAnnotation({ name: 'testEntry', id: 'span4' }), // index 3 createMockSpanAndAnnotation({ name: 'testEntry', id: 'span5' }), // index 4 createMockSpanAndAnnotation({ name: 'nonMatch', id: 'span6' }), // index 5 ] const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) // Search from index 2 to 4, get last match (should be span5) const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { lowestIndexToConsider: 2, highestIndexToConsider: 4, nthMatch: -1, }, ) expect(result?.span.id).toBe('span5') }) it('should handle single element arrays correctly', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = [ createMockSpanAndAnnotation({ name: 'testEntry', id: 'span1' }), ] // Test various nthMatch values expect( findMatchingSpan(matcher, spanAndAnnotations, mockContext, { nthMatch: 0, })?.span.id, ).toBe('span1') expect( findMatchingSpan(matcher, spanAndAnnotations, mockContext, { nthMatch: -1, })?.span.id, ).toBe('span1') expect( findMatchingSpan(matcher, spanAndAnnotations, mockContext, { nthMatch: 1, }), ).toBeUndefined() expect( findMatchingSpan(matcher, spanAndAnnotations, mockContext, { nthMatch: -2, }), ).toBeUndefined() }) it('should handle edge case where lowestIndexToConsider equals highestIndexToConsider', () => { const spanAndAnnotations = [ createMockSpanAndAnnotation({ name: 'nonMatch', id: 'span1' }), createMockSpanAndAnnotation({ name: 'testEntry', id: 'span2' }), createMockSpanAndAnnotation({ name: 'nonMatch', id: 'span3' }), ] const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { lowestIndexToConsider: 1, highestIndexToConsider: 1, }, ) expect(result?.span.id).toBe('span2') }) it('should handle matcher with existing tags', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) matcher.nthMatch = 1 matcher.lowestIndexToConsider = 1 matcher.highestIndexToConsider = 3 const spanAndAnnotations = [ createMockSpanAndAnnotation({ name: 'testEntry', id: 'span1' }), // index 0 - excluded by lowestIndexToConsider createMockSpanAndAnnotation({ name: 'testEntry', id: 'span2' }), // index 1 - first match in range createMockSpanAndAnnotation({ name: 'testEntry', id: 'span3' }), // index 2 - second match in range createMockSpanAndAnnotation({ name: 'testEntry', id: 'span4' }), // index 3 - third match in range createMockSpanAndAnnotation({ name: 'testEntry', id: 'span5' }), // index 4 - excluded by highestIndexToConsider ] // Should use matcher's settings (second match in range 1-3 = span3) const result = findMatchingSpan(matcher, spanAndAnnotations, mockContext) expect(result?.span.id).toBe('span3') }) }) describe('edge cases and error conditions', () => { it('should handle matcher that always returns false', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'nonExistent' }) const spanAndAnnotations = [ createMockSpanAndAnnotation({ name: 'testEntry', id: 'span1' }), createMockSpanAndAnnotation({ name: 'testEntry', id: 'span2' }), ] const result = findMatchingSpan(matcher, spanAndAnnotations, mockContext) expect(result).toBeUndefined() }) it('should handle matcher that always returns true', () => { const matcher = () => true const spanAndAnnotations = [ createMockSpanAndAnnotation({ name: 'testEntry1', id: 'span1' }), createMockSpanAndAnnotation({ name: 'testEntry2', id: 'span2' }), ] const result = findMatchingSpan(matcher, spanAndAnnotations, mockContext) expect(result?.span.id).toBe('span1') }) it('should handle zero-based indices correctly', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = [ createMockSpanAndAnnotation({ name: 'testEntry', id: 'span1' }), ] // Test boundary conditions expect( findMatchingSpan(matcher, spanAndAnnotations, mockContext, { lowestIndexToConsider: 0, })?.span.id, ).toBe('span1') expect( findMatchingSpan(matcher, spanAndAnnotations, mockContext, { highestIndexToConsider: 0, })?.span.id, ).toBe('span1') expect( findMatchingSpan(matcher, spanAndAnnotations, mockContext, { lowestIndexToConsider: 0, highestIndexToConsider: 0, })?.span.id, ).toBe('span1') }) it('should handle array bounds correctly', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry' }) const spanAndAnnotations = [ createMockSpanAndAnnotation({ name: 'testEntry', id: 'span1' }), createMockSpanAndAnnotation({ name: 'testEntry', id: 'span2' }), ] // Test with highestIndexToConsider beyond array length const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { highestIndexToConsider: 100, nthMatch: -1, }, ) expect(result?.span.id).toBe('span2') }) }) describe('performance and iteration behavior', () => { it('should iterate from start to end for positive indices', () => { const callOrder: string[] = [] const matcher = ( spanAndAnnotation: SpanAndAnnotationForMatching<TicketIdRelationSchemasFixture>, ) => { callOrder.push(spanAndAnnotation.span.id) return spanAndAnnotation.span.name === 'target' } const spanAndAnnotations = [ createMockSpanAndAnnotation({ name: 'span1', id: 'span1' }), createMockSpanAndAnnotation({ name: 'span2', id: 'span2' }), createMockSpanAndAnnotation({ name: 'target', id: 'span3' }), createMockSpanAndAnnotation({ name: 'span4', id: 'span4' }), ] findMatchingSpan(matcher, spanAndAnnotations, mockContext) expect(callOrder).toEqual(['span1', 'span2', 'span3']) }) it('should iterate from end to start for negative indices', () => { const callOrder: string[] = [] const matcher = ( spanAndAnnotation: SpanAndAnnotationForMatching<TicketIdRelationSchemasFixture>, ) => { callOrder.push(spanAndAnnotation.span.id) return spanAndAnnotation.span.name === 'target' } const spanAndAnnotations = [ createMockSpanAndAnnotation({ name: 'span1', id: 'span1' }), createMockSpanAndAnnotation({ name: 'target', id: 'span2' }), createMockSpanAndAnnotation({ name: 'span3', id: 'span3' }), createMockSpanAndAnnotation({ name: 'span4', id: 'span4' }), ] findMatchingSpan(matcher, spanAndAnnotations, mockContext, { nthMatch: -1, }) expect(callOrder).toEqual(['span4', 'span3', 'span2']) }) it('should stop early when target nthMatch is found for positive indices', () => { const callOrder: string[] = [] const matcher = ( spanAndAnnotation: SpanAndAnnotationForMatching<TicketIdRelationSchemasFixture>, ) => { callOrder.push(spanAndAnnotation.span.id) return spanAndAnnotation.span.name === 'target' } const spanAndAnnotations = [ createMockSpanAndAnnotation({ name: 'target', id: 'span1' }), createMockSpanAndAnnotation({ name: 'target', id: 'span2' }), createMockSpanAndAnnotation({ name: 'target', id: 'span3' }), createMockSpanAndAnnotation({ name: 'target', id: 'span4' }), ] // Should stop after finding the second match (index 1) findMatchingSpan(matcher, spanAndAnnotations, mockContext, { nthMatch: 1, }) expect(callOrder).toEqual(['span1', 'span2']) }) }) describe('matcher tags from fromDefinition', () => { const createTestData = () => [ createMockSpanAndAnnotation({ name: 'testEntry', id: 'span1' }), createMockSpanAndAnnotation({ name: 'nonMatch', id: 'span2' }), createMockSpanAndAnnotation({ name: 'testEntry', id: 'span3' }), createMockSpanAndAnnotation({ name: 'testEntry', id: 'span4' }), createMockSpanAndAnnotation({ name: 'nonMatch', id: 'span5' }), ] it('should use nthMatch from fromDefinition', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry', nthMatch: 1, }) const spanAndAnnotations = createTestData() const result = findMatchingSpan(matcher, spanAndAnnotations, mockContext) expect(result?.span.id).toBe('span3') }) it('should use negative nthMatch from fromDefinition', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry', nthMatch: -1, }) const spanAndAnnotations = createTestData() const result = findMatchingSpan(matcher, spanAndAnnotations, mockContext) expect(result?.span.id).toBe('span4') }) it('should use lowestIndexToConsider from fromDefinition', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry', lowestIndexToConsider: 2, }) const spanAndAnnotations = createTestData() const result = findMatchingSpan(matcher, spanAndAnnotations, mockContext) expect(result?.span.id).toBe('span3') }) it('should use highestIndexToConsider from fromDefinition and nthMatch from config', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry', highestIndexToConsider: 2, }) const spanAndAnnotations = createTestData() const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { nthMatch: -1, }, ) expect(result?.span.id).toBe('span3') // Last match within highestIndexToConsider range (indices 0-2) }) it('should use multiple tags from fromDefinition together', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry', lowestIndexToConsider: 1, highestIndexToConsider: 3, nthMatch: 1, }) const spanAndAnnotations = [ createMockSpanAndAnnotation({ name: 'testEntry', id: 'span1' }), // index 0 - excluded by lowestIndexToConsider createMockSpanAndAnnotation({ name: 'nonMatch', id: 'span2' }), // index 1 createMockSpanAndAnnotation({ name: 'testEntry', id: 'span3' }), // index 2 - first match in range createMockSpanAndAnnotation({ name: 'testEntry', id: 'span4' }), // index 3 - second match in range createMockSpanAndAnnotation({ name: 'testEntry', id: 'span5' }), // index 4 - excluded by highestIndexToConsider ] const result = findMatchingSpan(matcher, spanAndAnnotations, mockContext) expect(result?.span.id).toBe('span4') // Second match in the range }) it('should override fromDefinition tags with config parameter', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry', nthMatch: 0, lowestIndexToConsider: 0, highestIndexToConsider: 4, }) const spanAndAnnotations = createTestData() // Override with different config const result = findMatchingSpan( matcher, spanAndAnnotations, mockContext, { nthMatch: 1, lowestIndexToConsider: 2, highestIndexToConsider: 3, }, ) expect(result?.span.id).toBe('span4') // Should use config override, not fromDefinition }) it('should handle negative nthMatch with range from fromDefinition', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry', lowestIndexToConsider: 0, highestIndexToConsider: 2, nthMatch: -1, }) const spanAndAnnotations = createTestData() const result = findMatchingSpan(matcher, spanAndAnnotations, mockContext) expect(result?.span.id).toBe('span3') // Last match within range [0, 2] }) it('should return undefined when fromDefinition nthMatch exceeds matches in range', () => { const matcher = fromDefinition< 'ticket', TicketIdRelationSchemasFixture, 'origin' >({ name: 'testEntry', lowestIndexToConsider: 2, highestIndexToConsider: 3, nthMatch: 5, // Only 2 matches in range, requesting 6th match }) const spanAndAnnotations = createTestData() const result = findMatchingSpan(matcher, spanAndAnnotations, mockContext) expect(result).toBeUndefined() }) }) })