Skip to content

Commit 7eaf7aa

Browse files
committed
Improve error messages
1 parent c544346 commit 7eaf7aa

File tree

4 files changed

+451
-8
lines changed

4 files changed

+451
-8
lines changed

packages/cli-kit/src/private/node/analytics/error-categorizer.test.ts

Lines changed: 339 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {categorizeError, ErrorCategory} from './error-categorizer.js'
1+
import {categorizeError, formatErrorMessage, ErrorCategory} from './error-categorizer.js'
22
import {describe, test, expect} from 'vitest'
33

44
describe('categorizeError', () => {
@@ -185,3 +185,341 @@ describe('categorizeError', () => {
185185
expect(category).toBe(ErrorCategory.Unknown)
186186
})
187187
})
188+
189+
describe('formatErrorMessage', () => {
190+
describe('Network errors', () => {
191+
test('preserves HTTP status codes', () => {
192+
// Given
193+
const error = new Error('Request failed with status 404')
194+
const category = ErrorCategory.Network
195+
196+
// When
197+
const formatted = formatErrorMessage(error, category)
198+
199+
// Then
200+
expect(formatted).toBe('http-404-request-failed-with-status')
201+
})
202+
203+
test('preserves GraphQL error codes', () => {
204+
// Given
205+
const error = new Error('GraphQL Error (Code: 401): Unauthorized')
206+
const category = ErrorCategory.Network
207+
208+
// When
209+
const formatted = formatErrorMessage(error, category)
210+
211+
// Then
212+
expect(formatted).toBe('http-401-graphql-error-code-unauthorized')
213+
})
214+
215+
test('preserves connection error codes', () => {
216+
// Given
217+
const error = new Error('ECONNREFUSED: connection refused')
218+
const category = ErrorCategory.Network
219+
220+
// When
221+
const formatted = formatErrorMessage(error, category)
222+
223+
// Then
224+
expect(formatted).toBe('http-000-econnrefused-econnrefused-connection-refu')
225+
})
226+
227+
test('handles network errors without specific codes', () => {
228+
// Given
229+
const error = new Error('Network request failed')
230+
const category = ErrorCategory.Network
231+
232+
// When
233+
const formatted = formatErrorMessage(error, category)
234+
235+
// Then
236+
expect(formatted).toBe('http-000-network-request-failed')
237+
})
238+
})
239+
240+
describe('Authentication errors', () => {
241+
test('uses generic formatting', () => {
242+
// Given
243+
const error = new Error('Unauthorized access: 401')
244+
const category = ErrorCategory.Authentication
245+
246+
// When
247+
const formatted = formatErrorMessage(error, category)
248+
249+
// Then
250+
expect(formatted).toBe('unauthorized-access-401')
251+
})
252+
253+
test('handles auth errors without status codes', () => {
254+
// Given
255+
const error = new Error('Invalid credentials provided')
256+
const category = ErrorCategory.Authentication
257+
258+
// When
259+
const formatted = formatErrorMessage(error, category)
260+
261+
// Then
262+
expect(formatted).toBe('invalid-credentials-provided')
263+
})
264+
})
265+
266+
describe('File system errors', () => {
267+
test('uses generic formatting', () => {
268+
// Given
269+
const error = new Error('ENOENT: no such file or directory')
270+
const category = ErrorCategory.FileSystem
271+
272+
// When
273+
const formatted = formatErrorMessage(error, category)
274+
275+
// Then
276+
expect(formatted).toBe('enoent-no-such-file-or-directory')
277+
})
278+
279+
test('handles file system errors without error codes', () => {
280+
// Given
281+
const error = new Error('Cannot read directory')
282+
const category = ErrorCategory.FileSystem
283+
284+
// When
285+
const formatted = formatErrorMessage(error, category)
286+
287+
// Then
288+
expect(formatted).toBe('cannot-read-directory')
289+
})
290+
})
291+
292+
describe('Rate limit errors', () => {
293+
test('uses generic formatting', () => {
294+
// Given
295+
const error = new Error('Rate limit exceeded: 100 requests per minute')
296+
const category = ErrorCategory.RateLimit
297+
298+
// When
299+
const formatted = formatErrorMessage(error, category)
300+
301+
// Then
302+
expect(formatted).toBe('rate-limit-exceeded-100-requests-per-minute')
303+
})
304+
305+
test('handles rate limit errors without numbers', () => {
306+
// Given
307+
const error = new Error('Too many requests')
308+
const category = ErrorCategory.RateLimit
309+
310+
// When
311+
const formatted = formatErrorMessage(error, category)
312+
313+
// Then
314+
expect(formatted).toBe('too-many-requests')
315+
})
316+
})
317+
318+
describe('JSON errors', () => {
319+
test('uses generic formatting', () => {
320+
// Given
321+
const error = new Error('Syntax error at line 42: unexpected token')
322+
const category = ErrorCategory.Json
323+
324+
// When
325+
const formatted = formatErrorMessage(error, category)
326+
327+
// Then
328+
expect(formatted).toBe('syntax-error-at-line-42-unexpected-token')
329+
})
330+
331+
test('handles JSON errors without position info', () => {
332+
// Given
333+
const error = new Error('Invalid JSON received')
334+
const category = ErrorCategory.Json
335+
336+
// When
337+
const formatted = formatErrorMessage(error, category)
338+
339+
// Then
340+
expect(formatted).toBe('invalid-json-received')
341+
})
342+
})
343+
344+
describe('Validation errors', () => {
345+
test('uses generic formatting', () => {
346+
// Given
347+
const error = new Error('Validation failed for field: username')
348+
const category = ErrorCategory.Validation
349+
350+
// When
351+
const formatted = formatErrorMessage(error, category)
352+
353+
// Then
354+
expect(formatted).toBe('validation-failed-for-field-username')
355+
})
356+
357+
test('handles validation errors without field names', () => {
358+
// Given
359+
const error = new Error('Required field missing')
360+
const category = ErrorCategory.Validation
361+
362+
// When
363+
const formatted = formatErrorMessage(error, category)
364+
365+
// Then
366+
expect(formatted).toBe('required-field-missing')
367+
})
368+
})
369+
370+
describe('Permission errors', () => {
371+
test('uses generic formatting', () => {
372+
// Given
373+
const error = new Error('Permission denied to /etc/config')
374+
const category = ErrorCategory.Permission
375+
376+
// When
377+
const formatted = formatErrorMessage(error, category)
378+
379+
// Then
380+
expect(formatted).toBe('permission-denied-to-etc-config')
381+
})
382+
383+
test('handles permission errors without resource names', () => {
384+
// Given
385+
const error = new Error('Access denied')
386+
const category = ErrorCategory.Permission
387+
388+
// When
389+
const formatted = formatErrorMessage(error, category)
390+
391+
// Then
392+
expect(formatted).toBe('access-denied')
393+
})
394+
})
395+
396+
describe('Liquid errors', () => {
397+
test('uses generic formatting', () => {
398+
// Given
399+
const error = new Error('Liquid syntax error at line 15')
400+
const category = ErrorCategory.Liquid
401+
402+
// When
403+
const formatted = formatErrorMessage(error, category)
404+
405+
// Then
406+
expect(formatted).toBe('liquid-syntax-error-at-line-15')
407+
})
408+
409+
test('handles Liquid errors without line numbers', () => {
410+
// Given
411+
const error = new Error('Liquid template error')
412+
const category = ErrorCategory.Liquid
413+
414+
// When
415+
const formatted = formatErrorMessage(error, category)
416+
417+
// Then
418+
expect(formatted).toBe('liquid-template-error')
419+
})
420+
})
421+
422+
describe('Theme check errors', () => {
423+
test('uses generic formatting', () => {
424+
// Given
425+
const error = new Error('Theme check failed for rule: missing-alt-text')
426+
const category = ErrorCategory.ThemeCheck
427+
428+
// When
429+
const formatted = formatErrorMessage(error, category)
430+
431+
// Then
432+
expect(formatted).toBe('theme-check-failed-for-rule-missing-alt-text')
433+
})
434+
435+
test('handles theme check errors without rule names', () => {
436+
// Given
437+
const error = new Error('Theme validation failed')
438+
const category = ErrorCategory.ThemeCheck
439+
440+
// When
441+
const formatted = formatErrorMessage(error, category)
442+
443+
// Then
444+
expect(formatted).toBe('theme-validation-failed')
445+
})
446+
})
447+
448+
describe('Unknown errors', () => {
449+
test('uses generic formatting', () => {
450+
// Given
451+
const error = new Error('Something went wrong')
452+
const category = ErrorCategory.Unknown
453+
454+
// When
455+
const formatted = formatErrorMessage(error, category)
456+
457+
// Then
458+
expect(formatted).toBe('something-went-wrong')
459+
})
460+
})
461+
462+
describe('Edge cases', () => {
463+
test('handles very long error messages', () => {
464+
// Given
465+
const longMessage = 'A'.repeat(100)
466+
const error = new Error(longMessage)
467+
const category = ErrorCategory.Network
468+
469+
// When
470+
const formatted = formatErrorMessage(error, category)
471+
472+
// Then
473+
expect(formatted.length).toBeLessThanOrEqual(50)
474+
expect(formatted).toBe(`http-000-${'a'.repeat(41)}`)
475+
})
476+
477+
test('handles non-Error objects', () => {
478+
// Given
479+
const error = 'String error message'
480+
const category = ErrorCategory.Unknown
481+
482+
// When
483+
const formatted = formatErrorMessage(error, category)
484+
485+
// Then
486+
expect(formatted).toBe('string-error-message')
487+
})
488+
489+
test('handles errors with special characters', () => {
490+
// Given
491+
const error = new Error('Error: @#$%^&*()!')
492+
const category = ErrorCategory.Unknown
493+
494+
// When
495+
const formatted = formatErrorMessage(error, category)
496+
497+
// Then
498+
expect(formatted).toBe('error')
499+
})
500+
501+
test('removes consecutive dashes', () => {
502+
// Given
503+
const error = new Error('Error---with---many---dashes')
504+
const category = ErrorCategory.Unknown
505+
506+
// When
507+
const formatted = formatErrorMessage(error, category)
508+
509+
// Then
510+
expect(formatted).toBe('error-with-many-dashes')
511+
})
512+
513+
test('trims leading and trailing dashes', () => {
514+
// Given
515+
const error = new Error('---Error message---')
516+
const category = ErrorCategory.Unknown
517+
518+
// When
519+
const formatted = formatErrorMessage(error, category)
520+
521+
// Then
522+
expect(formatted).toBe('error-message')
523+
})
524+
})
525+
})

0 commit comments

Comments
 (0)