Skip to content

Commit 0714d53

Browse files
committed
Use exact assertions in fingerprint tests instead of fuzzy matching
Replace toContain() with toBe() for canonical form assertions. When output is deterministic, test the exact value. Control inputs by mocking error.stack to make outputs predictable. Add testing guidance to CLAUDE.md about preferring exact assertions.
1 parent 7fc83f1 commit 0714d53

2 files changed

Lines changed: 68 additions & 15 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ The SDK uses ULID (Universally Unique Lexicographically Sortable Identifier) for
175175
- Tests use Vitest
176176
- Some packages have minimal tests (`--passWithNoTests` flag in package.json)
177177
- Test files are located alongside source files with `.test.ts` extension
178+
- **Prefer exact assertions over fuzzy matching**: When testing functions that produce stable, canonical output, use exact equality (`toBe`, `toEqual`) rather than fuzzy matchers (`toContain`, `toMatch`). If a value should be stable, test the exact value. For example, instead of `expect(result).toContain('foo')`, use `expect(result).toBe('expected\nfull\nvalue')`. Control inputs (like mocking `error.stack`) to make outputs deterministic.
178179

179180
## Node Version
180181

packages/logfire-api/src/fingerprint.test.ts

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,18 @@ import { describe, expect, test } from 'vitest'
33
import { canonicalizeError, computeFingerprint } from './fingerprint'
44

55
describe('canonicalizeError', () => {
6-
test('includes error type', () => {
6+
test('produces stable canonical form with error type and stack frames', () => {
77
const error = new TypeError('test message')
8+
error.stack = `TypeError: test message
9+
at testFunction (test.js:10:5)
10+
at main (test.js:20:3)`
11+
812
const canonical = canonicalizeError(error)
913

10-
expect(canonical).toContain('TypeError')
11-
expect(canonical).toContain('----')
14+
expect(canonical).toBe(`TypeError
15+
----
16+
test:testFunction
17+
test:main`)
1218
})
1319

1420
test('includes function names from stack', () => {
@@ -32,34 +38,73 @@ describe('canonicalizeError', () => {
3238

3339
test('handles error.cause chain', () => {
3440
const cause = new Error('root cause')
41+
cause.stack = `Error: root cause
42+
at causeFunction (cause.js:5:1)`
43+
3544
const error = new Error('wrapper', { cause })
45+
error.stack = `Error: wrapper
46+
at wrapperFunction (wrapper.js:10:5)`
3647

3748
const canonical = canonicalizeError(error)
3849

39-
expect(canonical).toContain('----CAUSE----')
40-
expect(canonical.split('----CAUSE----').length).toBe(2)
50+
expect(canonical).toBe(`Error
51+
----
52+
wrapper:wrapperFunction
53+
----CAUSE----
54+
Error
55+
----
56+
cause:causeFunction`)
4157
})
4258

4359
test('handles AggregateError', () => {
44-
const errors = [new Error('first'), new TypeError('second')]
45-
const aggregate = new AggregateError(errors, 'multiple errors')
60+
const error1 = new Error('first')
61+
error1.stack = `Error: first
62+
at firstFn (first.js:1:1)`
63+
64+
const error2 = new TypeError('second')
65+
error2.stack = `TypeError: second
66+
at secondFn (second.js:2:2)`
67+
68+
const aggregate = new AggregateError([error1, error2], 'multiple errors')
69+
aggregate.stack = `AggregateError: multiple errors
70+
at aggregateFn (aggregate.js:10:5)`
4671

4772
const canonical = canonicalizeError(aggregate)
4873

49-
expect(canonical).toContain('AggregateError')
50-
expect(canonical).toContain('----AGGREGATE----')
51-
expect(canonical).toContain('Error')
52-
expect(canonical).toContain('TypeError')
74+
expect(canonical).toBe(`AggregateError
75+
----
76+
aggregate:aggregateFn
77+
----AGGREGATE----
78+
Error
79+
----
80+
first:firstFn
81+
----
82+
TypeError
83+
----
84+
second:secondFn`)
5385
})
5486

5587
test('handles circular cause references', () => {
5688
const error1 = new Error('error 1')
89+
error1.stack = `Error: error 1
90+
at fn1 (file1.js:1:1)`
91+
5792
const error2 = new Error('error 2', { cause: error1 })
93+
error2.stack = `Error: error 2
94+
at fn2 (file2.js:2:2)`
5895
;(error1 as Error & { cause: Error }).cause = error2
5996

6097
const canonical = canonicalizeError(error1)
6198

62-
expect(canonical).toContain('[circular]')
99+
expect(canonical).toBe(`Error
100+
----
101+
file1:fn1
102+
----CAUSE----
103+
Error
104+
----
105+
file2:fn2
106+
----CAUSE----
107+
[circular]`)
63108
})
64109

65110
test('deduplicates repeated frames (recursion)', () => {
@@ -83,9 +128,14 @@ describe('canonicalizeError', () => {
83128

84129
test('does not include error message in canonical form', () => {
85130
const error = new Error('this message should not appear')
131+
error.stack = `Error: this message should not appear
132+
at testFn (test.js:1:1)`
133+
86134
const canonical = canonicalizeError(error)
87135

88-
expect(canonical).not.toContain('this message should not appear')
136+
expect(canonical).toBe(`Error
137+
----
138+
test:testFn`)
89139
})
90140
})
91141

@@ -217,7 +267,9 @@ otherFunction@http://different.com/src/script.js:888:2`
217267
expect(fp1).toBe(fp2)
218268

219269
const canonical = canonicalizeError(error1)
220-
expect(canonical).toContain('myFunction')
221-
expect(canonical).toContain('otherFunction')
270+
expect(canonical).toBe(`Error
271+
----
272+
src/script:myFunction
273+
src/script:otherFunction`)
222274
})
223275
})

0 commit comments

Comments
 (0)