Skip to content

Commit

Permalink
I think I have it working with failing tests
Browse files Browse the repository at this point in the history
  • Loading branch information
dac09 committed Dec 11, 2023
1 parent b275d9f commit 67c4ace
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 121 deletions.
97 changes: 67 additions & 30 deletions packages/api/src/__tests__/normalizeRequest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { APIGatewayProxyEvent } from 'aws-lambda'

import { normalizeRequest } from '../transforms'

export const createMockedEvent = (
export const createMockedLambdaEvent = (
httpMethod = 'POST',
body: any = undefined,
isBase64Encoded = false
Expand Down Expand Up @@ -53,41 +53,78 @@ export const createMockedEvent = (
}
}

test('Normalizes an aws event with base64', () => {
const corsEventB64 = createMockedEvent(
'POST',
Buffer.from(JSON.stringify({ bazinga: 'hello_world' }), 'utf8').toString(
'base64'
),
true
)
describe('Lambda Request', () => {
it('Normalizes an aws event with base64', async () => {
const corsEventB64 = createMockedLambdaEvent(
'POST',
Buffer.from(JSON.stringify({ bazinga: 'hello_world' }), 'utf8').toString(
'base64'
),
true
)

expect(normalizeRequest(corsEventB64)).toEqual({
headers: new Headers(corsEventB64.headers),
method: 'POST',
query: null,
body: {
bazinga: 'hello_world',
},
expect(await normalizeRequest(corsEventB64)).toEqual({
headers: new Headers(corsEventB64.headers as Record<string, string>),
method: 'POST',
query: null,
jsonBody: {
bazinga: 'hello_world',
},
})
})
})

test('Handles CORS requests with and without b64 encoded', () => {
const corsEventB64 = createMockedEvent('OPTIONS', undefined, true)
it('Handles CORS requests with and without b64 encoded', async () => {
const corsEventB64 = createMockedLambdaEvent('OPTIONS', undefined, true)

expect(await normalizeRequest(corsEventB64)).toEqual({
headers: new Headers(corsEventB64.headers as Record<string, string>), // headers returned as symbol
method: 'OPTIONS',
query: null,
jsonBody: undefined,
})

const corsEventWithoutB64 = createMockedLambdaEvent(
'OPTIONS',
undefined,
false
)

expect(normalizeRequest(corsEventB64)).toEqual({
headers: new Headers(corsEventB64.headers), // headers returned as symbol
method: 'OPTIONS',
query: null,
body: undefined,
expect(await normalizeRequest(corsEventWithoutB64)).toEqual({
headers: new Headers(corsEventB64.headers as Record<string, string>), // headers returned as symbol
method: 'OPTIONS',
query: null,
jsonBody: undefined,
})
})
})

describe('Fetch API Request', () => {
it('Normalizes a fetch event', async () => {
const fetchEvent = new Request(
'http://localhost:9210/graphql?whatsup=doc&its=bugs',
{
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({ bazinga: 'kittens_purr_purr' }),
}
)

const partial = await normalizeRequest(fetchEvent)

const corsEventWithoutB64 = createMockedEvent('OPTIONS', undefined, false)
expect(partial).toMatchObject({
// headers: fetchEvent.headers,
method: 'POST',
query: {
whatsup: 'doc',
its: 'bugs',
},
jsonBody: {
bazinga: 'kittens_purr_purr',
},
})

expect(normalizeRequest(corsEventWithoutB64)).toEqual({
headers: new Headers(corsEventB64.headers), // headers returned as symbol
method: 'OPTIONS',
query: null,
body: undefined,
expect(partial.headers.get('content-type')).toEqual('application/json')
})
})
6 changes: 3 additions & 3 deletions packages/api/src/cors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Headers } from '@whatwg-node/fetch'

import type { Request } from './transforms'
import type { PartialRequest } from './transforms'

export type CorsConfig = {
origin?: boolean | string | string[]
Expand Down Expand Up @@ -59,10 +59,10 @@ export function createCorsContext(cors: CorsConfig | undefined) {
}

return {
shouldHandleCors(request: Request) {
shouldHandleCors(request: PartialRequest) {
return request.method === 'OPTIONS'
},
getRequestHeaders(request: Request): CorsHeaders {
getRequestHeaders(request: PartialRequest): CorsHeaders {
const eventHeaders = new Headers(request.headers as HeadersInit)
const requestCorsHeaders = new Headers(corsHeaders)

Expand Down
52 changes: 43 additions & 9 deletions packages/api/src/transforms.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Headers } from '@whatwg-node/fetch'
import { Headers, Request as PonyFillRequest } from '@whatwg-node/fetch'
import type { APIGatewayProxyEvent } from 'aws-lambda'

// This is the same interface used by GraphQL Yoga
// But not importing here to avoid adding a dependency
export interface Request {
body?: any
// This is part of the request, dreived either from a LambdaEvent or FetchAPI Request
// We do this to keep the API consistent between the two
// When we support only the FetchAPI request, we should remove this
export interface PartialRequest<TBody = Record<string, any>> {
jsonBody?: TBody
headers: Headers
method: string
query: any
Expand All @@ -13,7 +14,7 @@ export interface Request {
/**
* Extracts and parses body payload from event with base64 encoding check
*/
export const parseEventBody = (event: APIGatewayProxyEvent) => {
export const parseLambdaEventBody = (event: APIGatewayProxyEvent) => {
if (!event.body) {
return
}
Expand All @@ -25,14 +26,47 @@ export const parseEventBody = (event: APIGatewayProxyEvent) => {
}
}

export function normalizeRequest(event: APIGatewayProxyEvent): Request {
const body = parseEventBody(event)
export const isFetchApiRequest = (event: any): event is Request => {
return event instanceof Request || event instanceof PonyFillRequest
}

function getQueryStringParams(reqUrl: string) {
const url = new URL(reqUrl)
const params = new URLSearchParams(url.search)

const paramObject: Record<string, string> = {}
for (const entry of params.entries()) {
paramObject[entry[0]] = entry[1] // each 'entry' is a [key, value] tuple
}
return paramObject
}

/**
*
* This function returns a an object that lets you access _some_ of the request properties in a consistent way
* You can give it either a LambdaEvent or a Fetch API Request
*
* NOTE: It does NOT return a full Request object!
*/
export async function normalizeRequest(
event: APIGatewayProxyEvent | Request
): Promise<PartialRequest> {
if (isFetchApiRequest(event)) {
return {
headers: event.headers,
method: event.method,
query: getQueryStringParams(event.url),
jsonBody: await event.json(),
}
}

const jsonBody = parseLambdaEventBody(event)

return {
headers: new Headers(event.headers as Record<string, string>),
method: event.httpMethod,
query: event.queryStringParameters,
body,
jsonBody,
}
}

Expand Down
Loading

0 comments on commit 67c4ace

Please sign in to comment.