|
| 1 | +import { afterEach, describe, expect, test, vi } from 'vite-plus/test' |
| 2 | + |
| 3 | +import { NoopScrubber } from './AttributeScrubber' |
| 4 | +import { logfireFormatWithExtras } from './formatter' |
| 5 | + |
| 6 | +function format(template: string, record: Record<string, unknown>): string { |
| 7 | + return logfireFormatWithExtras(template, record, NoopScrubber).formattedMessage |
| 8 | +} |
| 9 | + |
| 10 | +describe('message template nested field access', () => { |
| 11 | + afterEach(() => { |
| 12 | + vi.restoreAllMocks() |
| 13 | + }) |
| 14 | + |
| 15 | + test('resolves a nested field', () => { |
| 16 | + expect(format('hello {user.name}', { user: { name: 'Alice' } })).toBe('hello Alice') |
| 17 | + }) |
| 18 | + |
| 19 | + test('resolves a deeply nested field', () => { |
| 20 | + expect(format('request by {req.user.id}', { req: { user: { id: 1 } } })).toBe('request by 1') |
| 21 | + }) |
| 22 | + |
| 23 | + test('nested traversal ignores unrelated top-level keys with the same trailing name', () => { |
| 24 | + expect(format('value is {a.b}', { a: { b: 'nested' }, b: 'top' })).toBe('value is nested') |
| 25 | + }) |
| 26 | + |
| 27 | + test('a literal dotted attribute key wins over nested traversal', () => { |
| 28 | + expect(format('{http.method} request', { http: { method: 'nested' }, 'http.method': 'GET' })).toBe('GET request') |
| 29 | + }) |
| 30 | + |
| 31 | + test('supports the debug format with nested fields', () => { |
| 32 | + expect(format('{user.name=}', { user: { name: 'Alice' } })).toBe('user.name=Alice') |
| 33 | + }) |
| 34 | + |
| 35 | + test('falls back to the raw template when the nested field is missing', () => { |
| 36 | + const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined) |
| 37 | + expect(format('hello {user.name}', { user: {} })).toBe('hello {user.name}') |
| 38 | + expect(warn).toHaveBeenCalledWith('Formatting error: The field user.name is not defined.') |
| 39 | + }) |
| 40 | + |
| 41 | + test('falls back to the raw template when the root of a nested field is missing', () => { |
| 42 | + const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined) |
| 43 | + expect(format('hello {user.name}', { other: 1 })).toBe('hello {user.name}') |
| 44 | + expect(warn).toHaveBeenCalledWith('Formatting error: The field user.name is not defined.') |
| 45 | + }) |
| 46 | + |
| 47 | + test('falls back to the raw template when an intermediate value is not an object', () => { |
| 48 | + const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined) |
| 49 | + expect(format('{a.b}', { a: 5 })).toBe('{a.b}') |
| 50 | + expect(warn).toHaveBeenCalledWith('Formatting error: The field a.b is not defined.') |
| 51 | + }) |
| 52 | + |
| 53 | + test('plain top-level fields keep working', () => { |
| 54 | + expect(format('hello {name}', { name: 'Bob' })).toBe('hello Bob') |
| 55 | + }) |
| 56 | + |
| 57 | + test('nested traversal does not resolve prototype members', () => { |
| 58 | + const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined) |
| 59 | + expect(format('{user.toString}', { user: {} })).toBe('{user.toString}') |
| 60 | + expect(warn).toHaveBeenCalledWith('Formatting error: The field user.toString is not defined.') |
| 61 | + }) |
| 62 | + |
| 63 | + test('top-level lookup does not resolve prototype members', () => { |
| 64 | + const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined) |
| 65 | + expect(format('{toString}', { name: 'Bob' })).toBe('{toString}') |
| 66 | + expect(warn).toHaveBeenCalledWith('Formatting error: The field toString is not defined.') |
| 67 | + }) |
| 68 | + |
| 69 | + test('bracket syntax with no matching literal key falls back to the raw template', () => { |
| 70 | + const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined) |
| 71 | + expect(format('first item is {a[0]}', { a: ['zero'] })).toBe('first item is {a[0]}') |
| 72 | + expect(warn).toHaveBeenCalledWith('Formatting error: The field a[0] is not defined.') |
| 73 | + }) |
| 74 | + |
| 75 | + test('a literal attribute key containing brackets keeps resolving', () => { |
| 76 | + expect(format('first item is {a[0]}', { 'a[0]': 'zero' })).toBe('first item is zero') |
| 77 | + }) |
| 78 | +}) |
0 commit comments