Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -657,7 +657,7 @@ Microsoft Entra ID (Azure AD) v2.0 OAuth 2.0 and OpenID Connect emulation with a

## AWS

S3, SQS, IAM, and STS emulation with AWS SDK-compatible S3 paths and query-style SQS/IAM/STS endpoints. All responses use AWS-compatible XML.
S3, SQS, IAM, and STS emulation with AWS SDK-compatible S3 paths, SQS AwsJson1.0 and query protocol support, and query-style IAM/STS endpoints.

### S3

Expand All @@ -675,7 +675,8 @@ S3 routes use root paths matching the real AWS S3 wire format, so the official A
- `DELETE /:bucket/:key` - delete object

### SQS
All operations via `POST /sqs/` with `Action` parameter:
All operations via `POST /sqs/`. SQS accepts modern AwsJson1.0 requests from current AWS SDK clients using `Content-Type: application/x-amz-json-1.0` and `X-Amz-Target: AmazonSQS.<Action>`. Legacy form-urlencoded query requests with an `Action` parameter remain supported.

- `CreateQueue`, `ListQueues`, `GetQueueUrl`, `GetQueueAttributes`
- `SendMessage`, `ReceiveMessage`, `DeleteMessage`
- `PurgeQueue`, `DeleteQueue`
Expand Down
4 changes: 2 additions & 2 deletions apps/web/app/docs/aws/page.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AWS

S3, SQS, IAM, and STS emulation with AWS SDK-compatible S3 paths and query-style SQS/IAM/STS endpoints. All state is in-memory, and responses use AWS-compatible XML.
S3, SQS, IAM, and STS emulation with AWS SDK-compatible S3 paths, SQS AwsJson1.0 and query protocol support, and query-style IAM/STS endpoints. All state is in-memory.

## S3

Expand All @@ -20,7 +20,7 @@ S3 routes use root paths matching the real AWS S3 wire format, so the official A

## SQS

All SQS operations use `POST /sqs/` with an `Action` form parameter.
All SQS operations use `POST /sqs/`. SQS accepts modern AwsJson1.0 requests from current AWS SDK clients using `Content-Type: application/x-amz-json-1.0` and `X-Amz-Target: AmazonSQS.<Action>`. Legacy form-urlencoded query requests with an `Action` parameter remain supported.

- `CreateQueue` - create queue (with optional attributes like `VisibilityTimeout`)
- `ListQueues` - list queues (supports `QueueNamePrefix` filter)
Expand Down
5 changes: 3 additions & 2 deletions packages/@emulators/aws/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# @emulators/aws

S3, SQS, IAM, and STS emulation with AWS SDK-compatible S3 paths and query-style SQS/IAM/STS endpoints. All responses use AWS-compatible XML.
S3, SQS, IAM, and STS emulation with AWS SDK-compatible S3 paths, SQS AwsJson1.0 and query protocol support, and query-style IAM/STS endpoints.

Part of [emulate](https://github.com/vercel-labs/emulate) — local drop-in replacement services for CI and no-network sandboxes.

Expand Down Expand Up @@ -28,7 +28,8 @@ S3 routes use root paths matching the real AWS S3 wire format, so the official A
- `DELETE /:bucket/:key` — delete object

### SQS
All operations via `POST /sqs/` with `Action` parameter:
All operations via `POST /sqs/`. SQS accepts modern AwsJson1.0 requests from current AWS SDK clients using `Content-Type: application/x-amz-json-1.0` and `X-Amz-Target: AmazonSQS.<Action>`. Legacy form-urlencoded query requests with an `Action` parameter remain supported.

- `CreateQueue`, `ListQueues`, `GetQueueUrl`, `GetQueueAttributes`
- `SendMessage`, `ReceiveMessage`, `DeleteMessage`
- `PurgeQueue`, `DeleteQueue`
Expand Down
106 changes: 106 additions & 0 deletions packages/@emulators/aws/src/__tests__/aws.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,112 @@ describe("AWS plugin - SQS", () => {
expect(text).toContain("emulate-default-queue");
});

it("handles AwsJson1.0 ListQueues requests", async () => {
const res = await app.request(`${base}/sqs/`, {
method: "POST",
headers: {
...authHeaders(),
"Content-Type": "application/x-amz-json-1.0",
"X-Amz-Target": "AmazonSQS.ListQueues",
},
body: "{}",
});
expect(res.status).toBe(200);
expect(res.headers.get("Content-Type")).toContain("application/x-amz-json-1.0");
const json = (await res.json()) as { QueueUrls: string[] };
expect(json.QueueUrls).toContain(`${base}/sqs/123456789012/emulate-default-queue`);
});

it("handles AwsJson1.0 queue and message operations", async () => {
const createRes = await app.request(`${base}/sqs/`, {
method: "POST",
headers: {
...authHeaders(),
"Content-Type": "application/x-amz-json-1.0",
"X-Amz-Target": "AmazonSQS.CreateQueue",
},
body: JSON.stringify({
QueueName: "json-queue",
Attributes: { VisibilityTimeout: "45" },
}),
});
expect(createRes.status).toBe(200);
const createJson = (await createRes.json()) as { QueueUrl: string };
expect(createJson.QueueUrl).toBe(`${base}/sqs/123456789012/json-queue`);

const sendRes = await app.request(`${base}/sqs/`, {
method: "POST",
headers: {
...authHeaders(),
"Content-Type": "application/x-amz-json-1.0",
"X-Amz-Target": "AmazonSQS.SendMessage",
},
body: JSON.stringify({
QueueUrl: createJson.QueueUrl,
MessageBody: "json message",
MessageAttributes: {
type: { DataType: "String", StringValue: "greeting" },
},
}),
});
expect(sendRes.status).toBe(200);
const sendJson = (await sendRes.json()) as { MD5OfMessageBody: string; MessageId: string };
expect(sendJson.MessageId).toBeTruthy();
expect(sendJson.MD5OfMessageBody).toBe("4e27a546974106f6e06b305041f8ab6d");

const receiveRes = await app.request(`${base}/sqs/`, {
method: "POST",
headers: {
...authHeaders(),
"Content-Type": "application/x-amz-json-1.0",
"X-Amz-Target": "AmazonSQS.ReceiveMessage",
},
body: JSON.stringify({ QueueUrl: createJson.QueueUrl, MaxNumberOfMessages: 1 }),
});
expect(receiveRes.status).toBe(200);
const receiveJson = (await receiveRes.json()) as {
Messages: Array<{
Body: string;
MessageAttributes: Record<string, { DataType: string; StringValue?: string }>;
ReceiptHandle: string;
}>;
};
expect(receiveJson.Messages).toHaveLength(1);
expect(receiveJson.Messages[0]?.Body).toBe("json message");
expect(receiveJson.Messages[0]?.MessageAttributes.type?.StringValue).toBe("greeting");

const attrRes = await app.request(`${base}/sqs/`, {
method: "POST",
headers: {
...authHeaders(),
"Content-Type": "application/x-amz-json-1.0",
"X-Amz-Target": "AmazonSQS.GetQueueAttributes",
},
body: JSON.stringify({ QueueUrl: createJson.QueueUrl, AttributeNames: ["All"] }),
});
expect(attrRes.status).toBe(200);
const attrJson = (await attrRes.json()) as { Attributes: Record<string, string> };
expect(attrJson.Attributes.VisibilityTimeout).toBe("45");
expect(attrJson.Attributes.ApproximateNumberOfMessagesNotVisible).toBe("1");
});

it("returns AwsJson1.0 errors for JSON protocol requests", async () => {
const res = await app.request(`${base}/sqs/`, {
method: "POST",
headers: {
...authHeaders(),
"Content-Type": "application/x-amz-json-1.0",
"X-Amz-Target": "AmazonSQS.GetQueueUrl",
},
body: JSON.stringify({ QueueName: "missing-queue" }),
});
expect(res.status).toBe(400);
expect(res.headers.get("Content-Type")).toContain("application/x-amz-json-1.0");
const json = (await res.json()) as { __type: string; message: string };
expect(json.__type).toBe("AWS.SimpleQueueService.NonExistentQueue");
expect(json.message).toContain("does not exist");
});

it("sends and receives a message", async () => {
// Get queue URL
const urlRes = await app.request(`${base}/sqs/`, {
Expand Down
Loading