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
8 changes: 6 additions & 2 deletions platform/src/components/aws/apigatewayv2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,9 @@ export interface ApiGatewayV2Args {
* Customize the CORS (Cross-origin resource sharing) settings for your HTTP API.
* @default `true`
* @example
* Disable CORS.
* Disable CORS. This omits CORS configuration entirely (`CorsConfiguration` is
* unset). An empty config object is not the same — AWS treats that as CORS
* enabled with no allowed origins or headers.
* ```js
* {
* cors: false
Expand Down Expand Up @@ -775,7 +777,8 @@ export class ApiGatewayV2 extends Component implements Link.Linkable {

function normalizeCors() {
return output(args.cors).apply((cors) => {
if (cors === false) return {};
// AWS treats `{}` as an empty CORS config; omit to fully disable.
if (cors === false) return undefined;

const defaultCors: types.input.apigatewayv2.ApiCorsConfiguration = {
allowHeaders: ["*"],
Expand Down Expand Up @@ -831,6 +834,7 @@ export class ApiGatewayV2 extends Component implements Link.Linkable {
`${name}Api`,
{
protocolType: "HTTP",
// @ts-ignore Output includes undefined when cors is false
corsConfiguration: cors,
},
{ parent },
Expand Down
119 changes: 119 additions & 0 deletions platform/test/components/apigatewayv2.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { describe, beforeAll, beforeEach, it, expect } from "vitest";
import * as pulumi from "@pulumi/pulumi";

// Suppress Pulumi "Trace events are unavailable" errors in test environment
process.on("unhandledRejection", (err: any) => {
if (err?.code === "ERR_TRACE_EVENTS_UNAVAILABLE") return;
throw err;
});

// @ts-ignore
global.$app = {
name: "app",
stage: "test",
};
global.$util = pulumi;

interface CreatedResource {
type: string;
name: string;
inputs: any;
}

let createdResources: CreatedResource[] = [];

pulumi.runtime.setMocks(
{
newResource: function (args: pulumi.runtime.MockResourceArgs): {
id: string;
state: any;
} {
createdResources.push({
type: args.type,
name: args.name,
inputs: args.inputs,
});
return {
id: `${args.name}_id`,
state: {
...args.inputs,
id: `${args.name}_id`,
arn: `arn:aws:apigateway:us-east-1::/apis/${args.name}`,
apiEndpoint: `https://${args.name}.execute-api.us-east-1.amazonaws.com`,
executionArn: `arn:aws:execute-api:us-east-1:123456789012:${args.name}`,
},
};
},
call: function (args: pulumi.runtime.MockCallArgs) {
return args.inputs;
},
},
"project",
"stack",
false,
);

const API_TYPE = "aws:apigatewayv2/api:Api";

function findApis() {
return createdResources.filter((r) => r.type === API_TYPE);
}

async function settle() {
for (let i = 0; i < 50; i++) {
await new Promise((resolve) => setImmediate(resolve));
}
}

describe("ApiGatewayV2 CORS", function () {
let ApiGatewayV2: typeof import("./../../src/components/aws/apigatewayv2").ApiGatewayV2;

beforeAll(async function () {
ApiGatewayV2 = (await import("./../../src/components/aws/apigatewayv2"))
.ApiGatewayV2;
});

beforeEach(function () {
createdResources = [];
});

it("enables wildcard CORS by default", async () => {
new ApiGatewayV2("DefaultCors");
await settle();

const apis = findApis();
expect(apis).toHaveLength(1);
expect(apis[0].inputs.corsConfiguration).toEqual({
allowHeaders: ["*"],
allowMethods: ["*"],
allowOrigins: ["*"],
});
});

it("omits corsConfiguration when cors is false", async () => {
new ApiGatewayV2("CorsDisabled", { cors: false });
await settle();

const apis = findApis();
expect(apis).toHaveLength(1);
expect(apis[0].inputs.corsConfiguration).toBeUndefined();
});

it("applies custom CORS settings", async () => {
new ApiGatewayV2("CustomCors", {
cors: {
allowMethods: ["GET", "POST"],
allowOrigins: ["https://example.com"],
},
});
await settle();

const apis = findApis();
expect(apis).toHaveLength(1);
expect(apis[0].inputs.corsConfiguration).toEqual({
allowHeaders: ["*"],
allowMethods: ["GET", "POST"],
allowOrigins: ["https://example.com"],
});
});
});