feat: Cloudformation + RDS

This commit is contained in:
Zakaria
2026-09-30 17:43:42 -04:00
parent 548a15d7ec
commit 7764ea0dc1
411 changed files with 10182 additions and 1063 deletions
+137 -16
View File
@@ -35,7 +35,6 @@ import type {
CreateResourceInput,
DatabaseSnapshot,
LogsInsightsQueryResult,
NoSqlItem,
} from '../cloud-spi/types'
import {CloudAdapterRegistry} from '../registry/CloudAdapterRegistry'
import {CloudProxyService} from '../service/CloudProxyService'
@@ -197,6 +196,44 @@ describe('API Gateway method test routes', () => {
})
})
describe('API Gateway resource routes', () => {
test('dispatches parent-scoped nested route creation and returns the resource tree', async () => {
const calls: unknown[] = []
const tree = {
resources: [
{resourceId: 'root-123', parentId: null, path: '/', pathPart: null, methods: []},
{resourceId: 'orders-123', parentId: 'root-123', path: '/orders', pathPart: 'orders', methods: []},
],
routes: [],
stages: ['dev'],
}
const app = appWithRoutes([mockAdapter('aws', {
service: 'apigateway',
createApiGatewayLambdaRoute: async (apiId, input) => {
calls.push({apiId, input})
return {resourceId: 'order-123', deploymentId: 'deployment-123', invokeUrl: '/orders/{id}'}
},
listApiGatewayRoutes: async () => tree,
})])
const create = await app.request('/api/clouds/aws/services/apigateway/resources/api-123/routes', {
method: 'POST', headers: {'content-type': 'application/json'},
body: JSON.stringify({
path: '/orders/{id}', parentResourceId: 'orders-123', pathPart: '{id}',
method: 'PATCH', stage: 'dev', functionName: 'orders-handler',
}),
})
const list = await app.request('/api/clouds/aws/services/apigateway/resources/api-123/routes')
expect(create.status).toBe(201)
expect(await list.json()).toEqual(tree)
expect(calls).toEqual([{apiId: 'api-123', input: {
path: '/orders/{id}', parentResourceId: 'orders-123', pathPart: '{id}',
method: 'PATCH', stage: 'dev', functionName: 'orders-handler',
}}])
})
})
describe('S3 website routes', () => {
test('routes normalized website configuration lifecycle requests through the storage adapter', async () => {
const calls: Array<{operation: string; args: unknown[]}> = []
@@ -579,46 +616,61 @@ describe('cloud schema routes', () => {
expect(body[0].name).toBe('items')
})
test('lists DynamoDB records through the nosql adapter', async () => {
test('lists DynamoDB records through the generic item adapter', async () => {
const app = appWithRoutes([mockAdapter('aws', {
service: 'nosql',
schema: awsDynamoDbSchema,
listNoSqlItems: async (resourceId: string): Promise<NoSqlItem[]> => [{
id: '{"pk":"item-1"}',
key: {pk: 'item-1'},
document: {pk: 'item-1', name: 'First item', table: resourceId},
}],
items: {
listItems: async (resourceId) => ({items: [{
id: '{"pk":"item-1"}', collectionId: null, timestamp: null,
body: {pk: 'item-1', name: 'First item', table: resourceId}, metadata: {key: {pk: 'item-1'}},
}], nextCursor: null}),
},
})])
const res = await app.request('/api/clouds/aws/services/nosql/resources/orders/items')
const body = await res.json()
expect(res.status).toBe(200)
expect(body[0].key).toEqual({pk: 'item-1'})
expect(body[0].document.table).toBe('orders')
expect(body.items[0].metadata.key).toEqual({pk: 'item-1'})
expect(body.items[0].body.table).toBe('orders')
})
test('puts a DynamoDB record through the nosql adapter', async () => {
test('puts and deletes DynamoDB records through the generic item adapter with the complete key', async () => {
const calls: Array<{resourceId: string; document: Record<string, unknown>}> = []
const app = appWithRoutes([mockAdapter('aws', {
service: 'nosql',
schema: awsDynamoDbSchema,
putNoSqlItem: async (resourceId, document): Promise<NoSqlItem> => {
calls.push({resourceId, document})
return {id: JSON.stringify({pk: document.pk}), key: {pk: document.pk}, document}
items: {
listItems: async () => ({items: [], nextCursor: null}),
putItem: async (resourceId, document) => {
calls.push({resourceId, document})
return {id: JSON.stringify({pk: document.pk, sk: document.sk}), collectionId: null, timestamp: null, body: document, metadata: {key: {pk: document.pk, sk: document.sk}}}
},
deleteItem: async (resourceId, itemId) => {
calls.push({resourceId, document: {itemId}})
},
},
})])
const res = await app.request('/api/clouds/aws/services/nosql/resources/orders/items', {
method: 'POST',
headers: {'content-type': 'application/json'},
body: JSON.stringify({pk: 'item-1', name: 'First item'}),
body: JSON.stringify({pk: 'item-1', sk: '2026-09-29', name: 'First item'}),
})
const body = await res.json()
expect(res.status).toBe(201)
expect(body.key).toEqual({pk: 'item-1'})
expect(calls).toEqual([{resourceId: 'orders', document: {pk: 'item-1', name: 'First item'}}])
expect(body.metadata.key).toEqual({pk: 'item-1', sk: '2026-09-29'})
expect(calls).toEqual([{resourceId: 'orders', document: {pk: 'item-1', sk: '2026-09-29', name: 'First item'}}])
const deleteRes = await app.request('/api/clouds/aws/services/nosql/resources/orders/items/%7B%22pk%22%3A%22item-1%22%2C%22sk%22%3A%222026-09-29%22%7D', {method: 'DELETE'})
expect(deleteRes.status).toBe(200)
expect(calls).toEqual([
{resourceId: 'orders', document: {pk: 'item-1', sk: '2026-09-29', name: 'First item'}},
{resourceId: 'orders', document: {itemId: '{"pk":"item-1","sk":"2026-09-29"}'}},
])
})
test('clears the SES mailbox through the email adapter', async () => {
@@ -804,6 +856,51 @@ describe('cloud schema routes', () => {
expect(res.status).toBe(400)
})
test('filters log events with pagination through the route', async () => {
let received: Record<string, unknown> = {}
const app = appWithRoutes([mockAdapter('aws', {
service: 'logs',
schema: awsLogsSchema,
filterLogEvents: async (logGroupName, input, page) => {
received = {logGroupName, input, page}
return {items: [{id: 'event-1', collectionId: 'stream-a', timestamp: null, body: {message: 'ERROR'}, metadata: {}}], nextCursor: 'next'}
},
})])
const res = await app.request('/api/clouds/aws/services/logs/resources/%2Ffloci%2Fprobe/filter?cursor=previous&limit=25', {
method: 'POST',
body: JSON.stringify({filterPattern: 'ERROR', logStreamNames: ['stream-a'], startTime: 1000, endTime: 2000}),
})
expect(res.status).toBe(200)
expect(await res.json()).toMatchObject({nextCursor: 'next'})
expect(received).toEqual({
logGroupName: '/floci/probe',
input: {filterPattern: 'ERROR', logStreamNames: ['stream-a'], startTime: 1000, endTime: 2000},
page: {cursor: 'previous', limit: 25},
})
})
test('rejects invalid log event filters before calling the adapter', async () => {
let called = false
const app = appWithRoutes([mockAdapter('aws', {
service: 'logs',
schema: awsLogsSchema,
filterLogEvents: async () => {
called = true
return {items: [], nextCursor: null}
},
})])
const res = await app.request('/api/clouds/aws/services/logs/resources/%2Ffloci%2Fprobe/filter', {
method: 'POST',
body: JSON.stringify({logStreamNames: 'stream-a'}),
})
expect(res.status).toBe(400)
expect(called).toBe(false)
})
test('normalizes runtime unavailable errors', async () => {
const app = appWithRoutes([
mockAdapter('aws', {
@@ -1621,6 +1718,30 @@ describe('child collection routes', () => {
expect(await res.json()).toEqual({items: stubItems.items, nextCursor: null})
})
test('passes a structured flat-item query and pagination through the existing query route', async () => {
let seen: unknown
const app = appWithRoutes([mockAdapter('aws', {items: {
...itemsStub,
queryItems: async (resourceId, query, page) => {
seen = {resourceId, query, page}
return {...stubItems, count: 1, scannedCount: 4, consumedCapacity: 0.5}
},
}})])
const res = await app.request('/api/clouds/aws/services/storage/resources/table-1/query?cursor=opaque&limit=25', {
method: 'POST',
headers: {'content-type': 'application/json'},
body: JSON.stringify({query: {operation: 'scan', filter: {attribute: 'status', operator: 'EQ', type: 'S', value: 'OPEN'}}}),
})
expect(res.status).toBe(200)
expect(seen).toEqual({
resourceId: 'table-1',
query: {operation: 'scan', filter: {attribute: 'status', operator: 'EQ', type: 'S', value: 'OPEN'}},
page: {cursor: 'opaque', limit: 25},
})
expect(await res.json()).toMatchObject({count: 1, scannedCount: 4, consumedCapacity: 0.5})
})
// A documents-only adapter has no flat shape, and vice versa.
test('501s when the adapter has the other shape', async () => {
const app = appWithRoutes([mockAdapter('aws', {documents: documentsStub})])