first commit

This commit is contained in:
Zakaria
2026-09-22 16:13:21 -04:00
commit 7e49be27f1
297 changed files with 63162 additions and 0 deletions
@@ -0,0 +1,129 @@
import {describe, expect, test} from 'bun:test'
import type {CloudErrorCode, CloudErrorStatus} from '../cloud-spi/errors'
import {isAwsSdkError, mapAwsSdkError} from './awsErrors'
/** Build an error shaped like an AWS SDK v3 failure. */
function sdkError(name: string, extra: Record<string, unknown> = {}): Error {
const err = new Error(`${name} from the runtime`)
err.name = name
return Object.assign(err, {$fault: 'client', $metadata: {httpStatusCode: 500}, ...extra})
}
describe('isAwsSdkError', () => {
test('recognises the SDK envelope', () => {
expect(isAwsSdkError(sdkError('NoSuchBucket'))).toBe(true)
expect(isAwsSdkError(Object.assign(new Error('x'), {$retryable: {}}))).toBe(true)
})
test('rejects anything else so the generic mapper can handle it', () => {
expect(isAwsSdkError(new Error('plain'))).toBe(false)
expect(isAwsSdkError(null)).toBe(false)
expect(isAwsSdkError('string')).toBe(false)
})
})
describe('mapAwsSdkError', () => {
test('returns null for a non-SDK error', () => {
expect(mapAwsSdkError(new Error('plain'))).toBeNull()
})
const cases: Array<[string, CloudErrorStatus, CloudErrorCode]> = [
// throttling — must win over everything, including the 500 metadata status
['ThrottlingException', 429, 'rate_limited'],
['ProvisionedThroughputExceededException', 429, 'rate_limited'],
['SlowDown', 429, 'rate_limited'],
// conflicts — previously all 502
['BucketAlreadyExists', 409, 'resource_conflict'],
['BucketAlreadyOwnedByYou', 409, 'resource_conflict'],
['EntityAlreadyExists', 409, 'resource_conflict'],
['ResourceExistsException', 409, 'resource_conflict'],
['StateMachineAlreadyExists', 409, 'resource_conflict'],
['ResourceInUseException', 409, 'resource_conflict'],
['QueueNameExists', 409, 'resource_conflict'],
// access
['AccessDenied', 403, 'access_denied'],
['UnauthorizedOperation', 403, 'access_denied'],
['SignatureDoesNotMatch', 403, 'access_denied'],
// validation
['ValidationException', 400, 'invalid_request'],
['InvalidParameterValue', 400, 'invalid_request'],
['MalformedPolicyDocument', 400, 'invalid_request'],
// A bad parameter set, not an existing-resource clash.
['InvalidParameterCombination', 400, 'invalid_request'],
// not found
['NoSuchBucket', 404, 'resource_not_found'],
['NoSuchKey', 404, 'resource_not_found'],
['ResourceNotFoundException', 404, 'resource_not_found'],
['QueueDoesNotExist', 404, 'resource_not_found'],
// EC2 uses a dotted suffix convention
['InvalidVpcID.NotFound', 404, 'resource_not_found'],
['InvalidInstanceID.NotFound', 404, 'resource_not_found'],
// runtime gaps
['NotImplemented', 501, 'operation_not_implemented'],
['UnsupportedOperation', 501, 'operation_not_implemented'],
['ServiceUnavailable', 503, 'runtime_unavailable'],
]
for (const [name, status, code] of cases) {
test(`${name} maps to ${status}`, () => {
const mapped = mapAwsSdkError(sdkError(name))
expect(mapped?.status).toBe(status)
expect(mapped?.code).toBe(code)
expect(mapped?.message).toContain(name)
})
}
test('matches the AlreadyExists suffix convention for unlisted names', () => {
expect(mapAwsSdkError(sdkError('WidgetAlreadyExistsException'))?.status).toBe(409)
})
test('honours the $retryable throttling flag even for an unknown name', () => {
const mapped = mapAwsSdkError(sdkError('SomeNewException', {$retryable: {throttling: true}}))
expect(mapped?.status).toBe(429)
})
test('falls back to the metadata status only when the name is unknown', () => {
expect(mapAwsSdkError(sdkError('MysteryFailure', {$metadata: {httpStatusCode: 404}}))?.status).toBe(404)
expect(mapAwsSdkError(sdkError('MysteryFailure', {$metadata: {httpStatusCode: 409}}))?.status).toBe(409)
})
test('prefers the error name over a misleading metadata status', () => {
// Local emulators frequently answer 500 for what is really a conflict.
const mapped = mapAwsSdkError(sdkError('BucketAlreadyExists', {$metadata: {httpStatusCode: 500}}))
expect(mapped?.status).toBe(409)
})
test('defaults to 502 when neither name nor status is informative', () => {
expect(mapAwsSdkError(sdkError('MysteryFailure'))?.status).toBe(502)
})
})
/**
* CloudWatch Logs returns both of these with HTTP 400, verified against the
* runtime on 2026-07-28:
* DescribeLogStreams on a missing group -> 400 ResourceNotFoundException
* duplicate CreateLogGroup -> 400 ResourceAlreadyExistsException
*
* Both already map correctly, because the name tables run ahead of the
* `$metadata.httpStatusCode` floor. Nothing pinned that ordering against a
* 400-carrying not-found, and an unpinned ordering is how #168 shipped a green
* suite over a 400-instead-of-404 bug.
*/
describe('logs errors carry HTTP 400 and must not fall through to it', () => {
test('a not-found keeps its 404 despite the 400 status', () => {
const mapped = mapAwsSdkError(
sdkError('ResourceNotFoundException', {$metadata: {httpStatusCode: 400}}),
)
expect(mapped?.status).toBe(404)
expect(mapped?.code).toBe('resource_not_found')
})
test('an already-exists keeps its 409 despite the 400 status', () => {
const mapped = mapAwsSdkError(
sdkError('ResourceAlreadyExistsException', {$metadata: {httpStatusCode: 400}}),
)
expect(mapped?.status).toBe(409)
})
})