Skip to content

Commit 38f1859

Browse files
committed
chore: wip
chore: wip chore: wip chore: wip
1 parent 676bca2 commit 38f1859

File tree

6 files changed

+187
-64
lines changed

6 files changed

+187
-64
lines changed

resources/preloader/test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { enableBrowserFeatures } from '@stacksjs/testing'
1+
import { setupTestEnvironment } from '@stacksjs/testing'
22

33
/**
44
* A place to register logic that should run before the tests run.
55
* e.g. you may abstract your module mocks here, if you want
66
* to prevent the original module from being evaluated.
77
*/
88

9-
enableBrowserFeatures()
9+
setupTestEnvironment()

storage/framework/core/error-handling/src/handler.ts

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,24 @@ interface ErrorOptions {
77
silent?: boolean
88
}
99

10-
export const StacksError = Error
10+
type ErrorMessage = string
1111

1212
export class ErrorHandler {
13-
// static logFile = path.logsPath('errors.log')
14-
15-
static handle(err: ErrorDescription | Error | unknown, options?: ErrorOptions): Error {
16-
// let's only write to the console if we are not in silent mode
17-
if (options?.silent !== false) this.writeErrorToConsole(err)
18-
19-
if (typeof err === 'string') err = new StacksError(err)
20-
if (typeof err === 'object') err = err as Error
13+
static handle(err: Error | ErrorMessage | unknown, options?: ErrorOptions): Error {
14+
if (options?.silent !== true) this.writeErrorToConsole(err)
15+
16+
let error: Error
17+
if (err instanceof Error) {
18+
error = err
19+
} else if (typeof err === 'string') {
20+
error = new Error(err)
21+
} else {
22+
error = new Error(JSON.stringify(err))
23+
}
2124

22-
this.writeErrorToFile(err).catch((e) => console.error(e))
25+
this.writeErrorToFile(error).catch((e) => console.error(e))
2326

24-
return err as Error // TODO: improve this type
27+
return error
2528
}
2629

2730
static handleError(err: Error, options?: ErrorOptions): Error {
@@ -39,9 +42,7 @@ export class ErrorHandler {
3942
const logFilePath = path.logsPath('stacks.log') ?? path.logsPath('errors.log')
4043

4144
try {
42-
// Ensure the directory exists
4345
await fs.mkdir(path.dirname(logFilePath), { recursive: true })
44-
// Append the message to the log file
4546
await fs.appendFile(logFilePath, formattedError)
4647
} catch (error) {
4748
console.error('Failed to write to error file:', error)
@@ -53,13 +54,10 @@ export class ErrorHandler {
5354
err === `Failed to execute command: ${italic('bunx --bun biome check --fix')}` ||
5455
err === `Failed to execute command: ${italic('bun storage/framework/core/actions/src/lint/fix.ts')}`
5556
) {
56-
// To trigger this, run `buddy release` with a lint error in your codebase
5757
console.error(err)
58-
process.exit(ExitCode.FatalError) // TODO: abstract this by differently catching the error somewhere
58+
process.exit(ExitCode.FatalError)
5959
}
6060

61-
// when "undeploying," there currently is a chance that edge functions can't be destroyed yet, because of their distributed nature
62-
// this is a temporary fix until AWS improves this on their side
6361
if (
6462
typeof err === 'string' &&
6563
err.includes('Failed to execute command:') &&
@@ -70,15 +68,13 @@ export class ErrorHandler {
7068
'No need to worry. The edge function is currently being destroyed. Please run `buddy undeploy` shortly again, and continue doing so until it succeeds running.',
7169
)
7270
console.log('Hoping to see you back soon!')
73-
process.exit(ExitCode.FatalError) // TODO: abstract this by differently catching the error somewhere
71+
process.exit(ExitCode.FatalError)
7472
}
7573

7674
console.error(err)
7775
}
7876
}
7977

80-
type ErrorDescription = string
81-
82-
export function handleError(err: ErrorDescription | Error | unknown, options?: ErrorOptions): Error {
78+
export function handleError(err: ErrorMessage | Error | unknown, options?: ErrorOptions): Error {
8379
return ErrorHandler.handle(err, options)
8480
}
Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,25 @@
1-
export function rescue<T>(fn: () => T, fallback: T): T {
1+
import { ErrorHandler } from './handler'
2+
3+
export function rescue<T, F>(
4+
fn: () => T | Promise<T>,
5+
fallback: F,
6+
onError?: (error: Error) => void,
7+
): T | F | Promise<T | F> {
28
try {
3-
return fn()
4-
} catch {
9+
const result = fn()
10+
if (result instanceof Promise) {
11+
return result.catch((error) => {
12+
if (onError) {
13+
onError(ErrorHandler.handle(error))
14+
}
15+
return fallback
16+
})
17+
}
18+
return result
19+
} catch (error) {
20+
if (onError) {
21+
onError(ErrorHandler.handle(error))
22+
}
523
return fallback
624
}
725
}

storage/framework/core/error-handling/tests/error-handling.test.ts

Lines changed: 141 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,30 @@
1-
import { describe, expect, it, mock } from 'bun:test'
1+
import { afterAll, beforeAll, describe, expect, it, mock, spyOn } from 'bun:test'
22
import { italic } from '@stacksjs/cli'
3+
import * as path from '@stacksjs/path'
34
import { ExitCode } from '@stacksjs/types'
5+
import fs from 'fs-extra'
46
import { ErrorHandler, handleError } from '../src/handler'
57
import { rescue } from '../src/utils'
68

7-
// Tests for ErrorHandler class
89
describe('@stacksjs/error-handling', () => {
10+
let originalConsoleError: typeof console.error
11+
let originalProcessExit: typeof process.exit
12+
13+
beforeAll(() => {
14+
originalConsoleError = console.error
15+
console.error = () => {} // No-op function
16+
17+
originalProcessExit = process.exit
18+
process.exit = () => {
19+
throw new Error('process.exit() was called')
20+
}
21+
})
22+
23+
afterAll(() => {
24+
console.error = originalConsoleError
25+
process.exit = originalProcessExit
26+
})
27+
928
describe('ErrorHandler', () => {
1029
it('should handle string errors', () => {
1130
const error = 'Test error'
@@ -20,66 +39,151 @@ describe('@stacksjs/error-handling', () => {
2039
expect(handledError).toBe(error)
2140
})
2241

42+
it('should handle non-Error objects', () => {
43+
const nonError = { message: 'Not an Error' }
44+
const handledError = ErrorHandler.handle(nonError)
45+
expect(handledError).toBeInstanceOf(Error)
46+
expect(handledError.message).toBe(JSON.stringify(nonError))
47+
})
48+
2349
it('should write error to console when not silent', () => {
24-
const consoleErrorMock = mock(console, 'error')
50+
const writeErrorToConsoleSpy = spyOn(ErrorHandler, 'writeErrorToConsole')
2551
const error = new Error('Test error')
2652
ErrorHandler.handle(error, { silent: false })
27-
expect(consoleErrorMock).toHaveBeenCalledWith(error)
28-
consoleErrorMock.mockRestore(redisTest)
53+
expect(writeErrorToConsoleSpy).toHaveBeenCalledWith(error)
54+
writeErrorToConsoleSpy.mockRestore()
55+
})
56+
57+
it('should not write error to console when silent', () => {
58+
const writeErrorToConsoleSpy = spyOn(ErrorHandler, 'writeErrorToConsole')
59+
const error = new Error('Test error')
60+
ErrorHandler.handle(error, { silent: true })
61+
expect(writeErrorToConsoleSpy).not.toHaveBeenCalled()
62+
writeErrorToConsoleSpy.mockRestore()
2963
})
3064

3165
it('should write error to file', async () => {
66+
const writeErrorToFileSpy = spyOn(ErrorHandler, 'writeErrorToFile')
3267
const error = new Error('Test error')
33-
await ErrorHandler.writeErrorToFile(error)
34-
// You can add more checks here to verify the file writing logic
68+
await ErrorHandler.handle(error)
69+
expect(writeErrorToFileSpy).toHaveBeenCalledWith(error)
70+
writeErrorToFileSpy.mockRestore()
3571
})
3672

3773
it('should handle specific command errors', () => {
38-
const consoleErrorMock = mock(console, 'error')
39-
const processExitMock = mock(process, 'exit')
74+
const consoleErrorSpy = spyOn(console, 'error')
75+
const processExitSpy = spyOn(process, 'exit')
4076

4177
const error = `Failed to execute command: ${italic('bunx --bun biome check --fix')}`
42-
ErrorHandler.writeErrorToConsole(error)
43-
expect(consoleErrorMock).toHaveBeenCalledWith(error)
44-
expect(processExitMock).toHaveBeenCalledWith(ExitCode.FatalError)
78+
expect(() => ErrorHandler.writeErrorToConsole(error)).toThrow('process.exit() was called')
79+
expect(consoleErrorSpy).toHaveBeenCalledWith(error)
80+
expect(processExitSpy).toHaveBeenCalledWith(ExitCode.FatalError)
4581

46-
consoleErrorMock.mockRestore()
47-
processExitMock.mockRestore()
82+
consoleErrorSpy.mockRestore()
83+
processExitSpy.mockRestore()
4884
})
49-
})
5085

51-
// Tests for utility functions
52-
describe('Utils', () => {
53-
it('should return the result of the function if no error occurs', () => {
54-
const result = rescue(() => 42, 0)
55-
expect(result).toBe(42)
86+
it('should format error message correctly when writing to file', async () => {
87+
const appendFileSpy = spyOn(fs, 'appendFile')
88+
const error = new Error('Test error')
89+
await ErrorHandler.writeErrorToFile(error)
90+
expect(appendFileSpy).toHaveBeenCalledWith(
91+
expect.any(String),
92+
expect.stringMatching(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] Error: Test error\n$/),
93+
)
94+
appendFileSpy.mockRestore()
5695
})
5796

58-
it('should return the fallback value if an error occurs', () => {
59-
const result = rescue(() => {
60-
throw new Error('Test error')
61-
}, 0)
62-
expect(result).toBe(0)
97+
it('should handle CDK destroy command errors', () => {
98+
const consoleErrorSpy = spyOn(console, 'error')
99+
const consoleLogSpy = spyOn(console, 'log')
100+
const processExitSpy = spyOn(process, 'exit')
101+
102+
const error = 'Failed to execute command: bunx --bun cdk destroy'
103+
expect(() => ErrorHandler.writeErrorToConsole(error)).toThrow('process.exit() was called')
104+
expect(consoleErrorSpy).toHaveBeenCalledWith(error)
105+
expect(consoleLogSpy).toHaveBeenCalledTimes(2)
106+
expect(processExitSpy).toHaveBeenCalledWith(ExitCode.FatalError)
107+
108+
consoleErrorSpy.mockRestore()
109+
consoleLogSpy.mockRestore()
110+
processExitSpy.mockRestore()
111+
})
112+
113+
it('should use correct log file path', async () => {
114+
const mkdirSpy = spyOn(fs, 'mkdir')
115+
const appendFileSpy = spyOn(fs, 'appendFile')
116+
const error = new Error('Test error')
117+
await ErrorHandler.writeErrorToFile(error)
118+
expect(mkdirSpy).toHaveBeenCalledWith(expect.any(String), expect.anything())
119+
expect(appendFileSpy.mock.calls[0][0]).toMatch(/stacks\.log$/)
120+
mkdirSpy.mockRestore()
121+
appendFileSpy.mockRestore()
63122
})
64123
})
65124

66-
// Tests for handleError function
67-
describe('handleError', () => {
68-
it('should handle string errors', () => {
69-
const error = 'Test error'
70-
const handledError = handleError(error)
71-
expect(handledError).toBeInstanceOf(Error)
72-
expect(handledError.message).toBe(error)
125+
describe('Utils', () => {
126+
describe('handleError function', () => {
127+
it('should handle string errors', () => {
128+
const error = 'Test error'
129+
const handledError = handleError(error)
130+
expect(handledError).toBeInstanceOf(Error)
131+
expect(handledError.message).toBe(error)
132+
})
133+
134+
it('should handle Error objects', () => {
135+
const error = new Error('Test error')
136+
const handledError = handleError(error)
137+
expect(handledError).toBe(error)
138+
})
139+
140+
it('should handle unknown objects', () => {
141+
const unknown = { foo: 'bar' }
142+
const handledError = handleError(unknown)
143+
expect(handledError).toBeInstanceOf(Error)
144+
expect(handledError.message).toBe(JSON.stringify(unknown))
145+
})
73146
})
74147

75-
it('should handle Error objects', () => {
76-
const error = new Error('Test error')
77-
const handledError = handleError(error)
78-
expect(handledError).toBe(error)
148+
describe('rescue function', () => {
149+
it('should return the result of the function if no error occurs', () => {
150+
const result = rescue(() => 42, 0)
151+
expect(result).toBe(42)
152+
})
153+
154+
it('should return the fallback value if an error occurs', () => {
155+
const result = rescue(() => {
156+
throw new Error('Test error')
157+
}, 0)
158+
expect(result).toBe(0)
159+
})
160+
161+
it('should handle async functions', async () => {
162+
const result = await rescue(async () => {
163+
throw new Error('Async error')
164+
}, 'fallback')
165+
expect(result).toBe('fallback')
166+
})
167+
168+
it('should pass error to onError callback if provided', () => {
169+
const onErrorMock = mock(() => {})
170+
rescue(
171+
() => {
172+
throw new Error('Test error')
173+
},
174+
0,
175+
onErrorMock,
176+
)
177+
expect(onErrorMock).toHaveBeenCalledWith(expect.any(Error))
178+
})
179+
180+
it('should handle successful async operations', async () => {
181+
const result = await rescue(async () => 'async result', 'fallback')
182+
expect(result).toBe('async result')
183+
})
79184
})
80185
})
81186

82-
// Tests for index.ts exports
83187
describe('ErrorHandling Index', () => {
84188
it('should export ErrorHandler', () => {
85189
expect(ErrorHandler).toBeDefined()

storage/framework/core/testing/src/feature.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,9 @@ export { GlobalRegistrator } from '@happy-dom/global-registrator'
33
export function enableBrowserFeatures() {
44
return GlobalRegistrator.register()
55
}
6+
7+
export function setupTestEnvironment() {
8+
process.env.NODE_ENV = 'test'
9+
process.env.APP_ENV = 'test'
10+
return GlobalRegistrator.register()
11+
}

storage/framework/core/types/src/errors.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
// toString(): string
88
// }
99

10-
// export type StacksError = Error
1110
export type CommandError = Error
1211

1312
export interface ErrorOptions {

0 commit comments

Comments
 (0)