Skip to content

Commit

Permalink
APEX-1637 - Manual testing (#21)
Browse files Browse the repository at this point in the history
* chore: update diagram

* chore: move test route logic to separate file

* feat: add manual testing endpoints

* fix: adding cron detail type in staging

* chore: use getFunctionProps in SchedulerStack

* feat: add manual testing for squadjoin

* fix: adding schedule detail in staging

* chore: separate function permissions and roles into multiple groups

* fix: binding db to functions

* chore: create lambda result utils

* fix: send back empty json if no body is provided
  • Loading branch information
BaDo2001 authored Nov 15, 2023
1 parent 348fd82 commit 216ae47
Show file tree
Hide file tree
Showing 22 changed files with 413 additions and 180 deletions.
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,43 @@ npx sst secrets set RANDOM_SLACK_CHANNEL_ID <your-test-channel>

Fill `.env.local` file with the secrets.

### Run all tests in watch mode

```bash
pnpm test
```

### Run unit tests

```bash
pnpm test:unit
```

### Run integration tests

```bash
pnpm test:integration
```

### Run all tests

```bash
pnpm test:ci
```

### Manual testing

All urls are displayed in the console output.

- Send out icebreaker question: open `<ApiEndpoint>/icebreaker`
- Ask birthday from everyone: open `<ApiEndpoint>/botJoined`
- Ask birthday from specific user: open `<ApiEndpoint>/userJoined?userId=<slack user id>`
- Send out birthday fill reminder: open `<ApiEndpoint>/daily`
- Only sends it out to users who have not filled in their birthday yet.
- Send out birthday present idea question: open `<ApiEndpoint>/daily`
- Sends it out related to users whose birthday is in exactly 2 months.
- Send out birthday present idea + squadjoin question: open `<ApiEndpoint>/squadJoin?userId=<slack user id>`

## Generate a new migration

```bash
Expand Down
159 changes: 102 additions & 57 deletions architecture/backend.drawio

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions packages/core/db/queries/deleteUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { and, eq } from "drizzle-orm";

import { db } from "@/db/index";
import { users } from "@/db/schema";

export const deleteUser = async (userId: string, teamId: string) => {
await db
.delete(users)
.where(and(eq(users.id, userId), eq(users.teamId, teamId)));
};
9 changes: 9 additions & 0 deletions packages/core/db/queries/getUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { eq } from "drizzle-orm";

import { db } from "@/db/index";
import { users } from "@/db/schema";

export const getUser = async (userId: string) =>
db.query.users.findFirst({
where: eq(users.id, userId),
});
9 changes: 2 additions & 7 deletions packages/functions/events/memberLeftChannel.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { and, eq } from "drizzle-orm";

import { db } from "@/db/index";
import { users } from "@/db/schema";
import { deleteUser } from "@/db/queries/deleteUser";
import { handleEvent } from "@/utils/eventBridge/handleEvent";

export const handler = handleEvent(
"memberLeftChannel",
async ({ user, team }) => {
try {
await db
.delete(users)
.where(and(eq(users.id, user), eq(users.teamId, team)));
await deleteUser(user, team);
} catch (error) {
console.error("Error processing memberLeftChannel event: ", error);
}
Expand Down
11 changes: 3 additions & 8 deletions packages/functions/lambdas/listen-for-test-payloads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { APIGatewayProxyHandlerV2 } from "aws-lambda";

import { db } from "@/db/index";
import { testItems } from "@/db/schema";
import { errorResult, okResult } from "@/utils/lambda/result";

export const handler: APIGatewayProxyHandlerV2 = async (request) => {
try {
Expand All @@ -20,15 +21,9 @@ export const handler: APIGatewayProxyHandlerV2 = async (request) => {
payload: request.body,
});

return {
statusCode: 200,
body: JSON.stringify({}),
};
return okResult();
} catch (error) {
console.error(`Error handling slack event: ${error}`);
return {
statusCode: 500,
body: JSON.stringify({}),
};
return errorResult(error);
}
};
19 changes: 19 additions & 0 deletions packages/functions/lambdas/manualBotJoinedEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { APIGatewayProxyHandlerV2 } from "aws-lambda";
import { Config } from "sst/node/config";

import { publishEvent } from "@/utils/eventBridge/publishEvent";
import { errorResult, okResult } from "@/utils/lambda/result";

export const handler: APIGatewayProxyHandlerV2 = async () => {
try {
await publishEvent("botJoined", {
channel: Config.CORE_SLACK_CHANNEL_ID,
});

return okResult("Event sent");
} catch (error) {
console.error(`Error sending manual botJoined event: ${error}`);

return errorResult(error);
}
};
30 changes: 30 additions & 0 deletions packages/functions/lambdas/manualSquadJoinEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { APIGatewayProxyHandlerV2 } from "aws-lambda";

import { getUser } from "@/db/queries/getUser";
import { publishEvent } from "@/utils/eventBridge/publishEvent";
import { errorResult, okResult } from "@/utils/lambda/result";

export const handler: APIGatewayProxyHandlerV2 = async (request) => {
try {
if (!request.queryStringParameters?.userId) {
throw new Error("No userId");
}

const user = await getUser(request.queryStringParameters.userId);

if (!user) {
throw new Error("User not found");
}

await publishEvent("askPresentAndSquadJoinFromTeam", {
birthdayPerson: user.id,
team: user.teamId,
});

return okResult("Event sent");
} catch (error) {
console.error(`Error sending manual botJoined event: ${error}`);

return errorResult(error);
}
};
24 changes: 24 additions & 0 deletions packages/functions/lambdas/manualUserJoinedEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { APIGatewayProxyHandlerV2 } from "aws-lambda";
import { Config } from "sst/node/config";

import { publishEvent } from "@/utils/eventBridge/publishEvent";
import { errorResult, okResult } from "@/utils/lambda/result";

export const handler: APIGatewayProxyHandlerV2 = async (request) => {
try {
if (!request.queryStringParameters?.userId) {
throw new Error("No userId");
}

await publishEvent("memberJoinedChannel", {
channel: Config.CORE_SLACK_CHANNEL_ID,
user: request.queryStringParameters.userId,
});

return okResult("Event sent");
} catch (error) {
console.error(`Error sending manual userJoined event: ${error}`);

return errorResult(error);
}
};
14 changes: 4 additions & 10 deletions packages/functions/lambdas/migrateDb.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { migrate } from "@/db/index";
import { errorResult, okResult } from "@/utils/lambda/result";

export const handler = async () => {
try {
Expand All @@ -8,17 +9,10 @@ export const handler = async () => {

console.log("Migration complete!");

return {
statusCode: 200,
body: JSON.stringify({}),
};
return okResult();
} catch (error) {
console.error(`Error migrating db: ${error}`);
return {
statusCode: 500,
body: JSON.stringify({
error,
}),
};

return errorResult(error);
}
};
21 changes: 7 additions & 14 deletions packages/functions/lambdas/slack-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {

import { SlackCallbackRequestSchema } from "@/types/SlackEventRequest";
import { publishEvent } from "@/utils/eventBridge/publishEvent";
import { errorResult, okResult } from "@/utils/lambda/result";

export const handler: APIGatewayProxyHandlerV2 = async (
request: APIGatewayProxyEventV2,
Expand All @@ -19,12 +20,9 @@ export const handler: APIGatewayProxyHandlerV2 = async (
);

if (validatedBody.type === "url_verification") {
return {
statusCode: 200,
body: JSON.stringify({
urlVerificationChallenge: validatedBody.challenge,
}),
};
return okResult({
urlVerificationChallenge: validatedBody.challenge,
});
}

switch (validatedBody.event.type) {
Expand All @@ -40,15 +38,10 @@ export const handler: APIGatewayProxyHandlerV2 = async (
break;
}

return {
statusCode: 200,
body: JSON.stringify({}),
};
return okResult();
} catch (error) {
console.error(`Error handling slack event: ${error}`);
return {
statusCode: 500,
body: JSON.stringify({}),
};

return errorResult(error);
}
};
12 changes: 4 additions & 8 deletions packages/functions/lambdas/slack-interaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from "@/types/SlackInteractionRequest";
import { publishEvent } from "@/utils/eventBridge/publishEvent";
import { parseRequest } from "@/utils/lambda/parseRequest";
import { errorResult, okResult } from "@/utils/lambda/result";

export const handler: APIGatewayProxyHandlerV2 = async (request) => {
try {
Expand Down Expand Up @@ -93,15 +94,10 @@ export const handler: APIGatewayProxyHandlerV2 = async (request) => {
}
}

return {
statusCode: 200,
body: JSON.stringify({}),
};
return okResult();
} catch (error) {
console.error(`Error handling slack interaction: ${error}`);
return {
statusCode: 500,
body: JSON.stringify({}),
};

return errorResult(error);
}
};
8 changes: 7 additions & 1 deletion packages/functions/utils/lambda/cronHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import type { APIGatewayProxyEventV2, EventBridgeEvent } from "aws-lambda";
import type { BaseEvent } from "@/types/BaseEvent";
import type { CronEventType } from "@/types/cron";

import { errorResult, okResult } from "./result";

type Event =
| APIGatewayProxyEventV2
| EventBridgeEvent<CronEventType, BaseEvent>;

const isApiGatewayProxyEventV2 = (
event: Event,
): event is APIGatewayProxyEventV2 => "queryStringParameters" in event;
): event is APIGatewayProxyEventV2 => "routeKey" in event;

export const cronHandler =
(handler: (eventId?: string) => Promise<unknown>) =>
Expand All @@ -20,7 +22,11 @@ export const cronHandler =
: request.detail.eventId;

await handler(eventId);

return okResult("Event sent");
} catch (error) {
console.error(error);

return errorResult(error);
}
};
15 changes: 15 additions & 0 deletions packages/functions/utils/lambda/result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const okResult = (body?: unknown) => ({
statusCode: 200,
body: JSON.stringify(body ?? {}, null, 2),
});

export const errorResult = (error: unknown) => ({
statusCode: 500,
body: JSON.stringify(
{
error,
},
null,
2,
),
});
37 changes: 22 additions & 15 deletions stacks/CronStack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,30 @@ import { Cron } from "sst/constructs";

import { daily, iceBreaker } from "@/types/cron";

import { getFunctionProps } from "./getFunctionProps";
import { getFunctionProps } from "./utils/getFunctionProps";
import { isStageTestable } from "./utils/isStageTestable";

export function CronStack({ stack }: StackContext) {
const functionProps = getFunctionProps();

const isTestable = isStageTestable(stack);

new Cron(stack, "IceBreakerCron", {
job: {
function: {
handler: "packages/functions/cron/iceBreakerQuestions.handler",
...functionProps,
},
},
cdk: {
rule: {
eventPattern: {
detailType: [iceBreaker],
},
},
},
cdk: isTestable
? {
rule: {
eventPattern: {
detailType: [iceBreaker],
},
},
}
: undefined,
// Every first Tuesday of the month at 11:00 UTC
schedule: "cron(0 11 ? * 3#1 *)",
});
Expand All @@ -33,13 +38,15 @@ export function CronStack({ stack }: StackContext) {
...functionProps,
},
},
cdk: {
rule: {
eventPattern: {
detailType: [daily],
},
},
},
cdk: isTestable
? {
rule: {
eventPattern: {
detailType: [daily],
},
},
}
: undefined,
// Every day at 11:00 UTC
schedule: "cron(0 11 ? * * *)",
});
Expand Down
Loading

0 comments on commit 216ae47

Please sign in to comment.