chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
import { assert, test, describe } from 'vitest'
|
||||
import { Code, ConnectError } from '@connectrpc/connect'
|
||||
import {
|
||||
handleRpcError,
|
||||
handleRpcErrorWithHealthCheck,
|
||||
} from '../../src/envd/rpc'
|
||||
import {
|
||||
AuthenticationError,
|
||||
InvalidArgumentError,
|
||||
NotFoundError,
|
||||
RateLimitError,
|
||||
SandboxError,
|
||||
TimeoutError,
|
||||
} from '../../src/errors'
|
||||
|
||||
describe('handleRpcError', () => {
|
||||
test('returns InvalidArgumentError for InvalidArgument', () => {
|
||||
const err = handleRpcError(new ConnectError('bad', Code.InvalidArgument))
|
||||
assert.instanceOf(err, InvalidArgumentError)
|
||||
})
|
||||
|
||||
test('returns AuthenticationError for Unauthenticated', () => {
|
||||
const err = handleRpcError(new ConnectError('nope', Code.Unauthenticated))
|
||||
assert.instanceOf(err, AuthenticationError)
|
||||
})
|
||||
|
||||
test('returns NotFoundError for NotFound', () => {
|
||||
const err = handleRpcError(new ConnectError('missing', Code.NotFound))
|
||||
assert.instanceOf(err, NotFoundError)
|
||||
})
|
||||
|
||||
test('returns RateLimitError for ResourceExhausted', () => {
|
||||
const err = handleRpcError(
|
||||
new ConnectError('too many', Code.ResourceExhausted)
|
||||
)
|
||||
assert.instanceOf(err, RateLimitError)
|
||||
assert.include(err.message, 'Rate limit')
|
||||
})
|
||||
|
||||
test('returns TimeoutError for Unavailable', () => {
|
||||
const err = handleRpcError(new ConnectError('gone', Code.Unavailable))
|
||||
assert.instanceOf(err, TimeoutError)
|
||||
})
|
||||
|
||||
test('falls back to SandboxError for unmapped code', () => {
|
||||
const err = handleRpcError(new ConnectError('boom', Code.Internal))
|
||||
assert.instanceOf(err, SandboxError)
|
||||
})
|
||||
|
||||
test('falls back to generic SandboxError for Unknown "terminated"', () => {
|
||||
const err = handleRpcError(new ConnectError('terminated', Code.Unknown))
|
||||
assert.instanceOf(err, SandboxError)
|
||||
assert.include(err.message, 'terminated')
|
||||
assert.notInclude(err.message, 'killed')
|
||||
})
|
||||
|
||||
test('returns the original error when not a ConnectError', () => {
|
||||
const original = new Error('not connect')
|
||||
const err = handleRpcError(original)
|
||||
assert.strictEqual(err, original)
|
||||
})
|
||||
})
|
||||
|
||||
describe('handleRpcErrorWithHealthCheck', () => {
|
||||
const terminated = () => new ConnectError('terminated', Code.Unknown)
|
||||
|
||||
// Each JS runtime surfaces a dropped connection with different wording
|
||||
const runtimeTerminatedMessages = {
|
||||
Node: 'terminated',
|
||||
Bun: 'The socket connection was closed unexpectedly',
|
||||
Deno: 'error reading a body from connection',
|
||||
}
|
||||
|
||||
test('returns a TimeoutError when the health check says the sandbox is not running', async () => {
|
||||
const err = await handleRpcErrorWithHealthCheck(
|
||||
terminated(),
|
||||
async () => false
|
||||
)
|
||||
assert.instanceOf(err, TimeoutError)
|
||||
assert.include(err.message, 'sandbox was killed or reached its end of life')
|
||||
})
|
||||
|
||||
for (const [runtime, message] of Object.entries(runtimeTerminatedMessages)) {
|
||||
test(`treats the ${runtime} dropped-connection message as terminated`, async () => {
|
||||
const err = await handleRpcErrorWithHealthCheck(
|
||||
new ConnectError(message, Code.Unknown),
|
||||
async () => false
|
||||
)
|
||||
assert.instanceOf(err, TimeoutError)
|
||||
assert.include(
|
||||
err.message,
|
||||
'sandbox was killed or reached its end of life'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
test('falls back to the generic mapping when the health check says the sandbox is running', async () => {
|
||||
const err = await handleRpcErrorWithHealthCheck(
|
||||
terminated(),
|
||||
async () => true
|
||||
)
|
||||
assert.instanceOf(err, SandboxError)
|
||||
assert.notInstanceOf(err, TimeoutError)
|
||||
assert.notInclude(err.message, 'killed')
|
||||
})
|
||||
|
||||
test('falls back to the generic mapping when the sandbox state is unknown', async () => {
|
||||
const err = await handleRpcErrorWithHealthCheck(
|
||||
terminated(),
|
||||
async () => undefined
|
||||
)
|
||||
assert.instanceOf(err, SandboxError)
|
||||
assert.notInstanceOf(err, TimeoutError)
|
||||
assert.notInclude(err.message, 'killed')
|
||||
})
|
||||
|
||||
test('falls back to the generic mapping when the health check itself fails', async () => {
|
||||
const err = await handleRpcErrorWithHealthCheck(terminated(), async () => {
|
||||
throw new Error('health check failed')
|
||||
})
|
||||
assert.instanceOf(err, SandboxError)
|
||||
assert.notInstanceOf(err, TimeoutError)
|
||||
assert.notInclude(err.message, 'killed')
|
||||
})
|
||||
|
||||
test('does not run the health check for other errors', async () => {
|
||||
let called = false
|
||||
const err = await handleRpcErrorWithHealthCheck(
|
||||
new ConnectError('missing', Code.NotFound),
|
||||
async () => {
|
||||
called = true
|
||||
return false
|
||||
}
|
||||
)
|
||||
assert.instanceOf(err, NotFoundError)
|
||||
assert.isFalse(called)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user