Skip to content

Commit 55ead1d

Browse files
committed
add important tests
1 parent 0e23255 commit 55ead1d

File tree

4 files changed

+163
-6
lines changed

4 files changed

+163
-6
lines changed

app/routes/recipients+/$recipientId.index.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { formatSendTime, getSendTime } from '#app/utils/cron.server.js'
2020
import { prisma } from '#app/utils/db.server.ts'
2121
import { useDoubleCheck } from '#app/utils/misc.js'
2222
import { sendTextToRecipient } from '#app/utils/text.server.js'
23+
import { createToastHeaders } from '#app/utils/toast.server.js'
2324

2425
type FutureMessage = SerializeFrom<typeof loader>['futureMessages'][number]
2526

@@ -181,7 +182,17 @@ async function sendMessageAction({ formData, userId }: MessageActionArgs) {
181182
data: { sentAt: new Date() },
182183
})
183184

184-
return json({ result: submission.reply() }, { status: 200 })
185+
return json(
186+
{ result: submission.reply() },
187+
{
188+
status: 200,
189+
headers: await createToastHeaders({
190+
type: 'success',
191+
title: 'Message sent',
192+
description: 'Your message has been sent',
193+
}),
194+
},
195+
)
185196
}
186197

187198
const DeleteMessageSchema = z.object({ id: z.string() })

tests/e2e/onboarding.test.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,13 @@ test('onboarding with link', async ({ page, getOnboardingData }) => {
5959
).toBeVisible()
6060
await expect(page.getByText(/check your texts/i)).toBeVisible()
6161

62+
const sourceNumber = await prisma.sourceNumber.findFirstOrThrow({
63+
select: { phoneNumber: true },
64+
})
6265
const textMessage = await readText(onboardingData.phoneNumber)
6366
invariant(textMessage, 'Text message not found')
6467
expect(textMessage.To).toBe(onboardingData.phoneNumber.toLowerCase())
65-
expect(textMessage.From).toBe('555-555-5555')
68+
expect(textMessage.From).toBe(sourceNumber.phoneNumber)
6669
expect(textMessage.Body).toMatch(/welcome/i)
6770
const onboardingUrl = extractUrl(textMessage.Body)
6871
invariant(onboardingUrl, 'Onboarding URL not found')
@@ -121,10 +124,13 @@ test('onboarding with a short code', async ({ page, getOnboardingData }) => {
121124
).toBeVisible()
122125
await expect(page.getByText(/Check your texts/i)).toBeVisible()
123126

127+
const sourceNumber = await prisma.sourceNumber.findFirstOrThrow({
128+
select: { phoneNumber: true },
129+
})
124130
const textMessage = await readText(onboardingData.phoneNumber)
125131
invariant(textMessage, 'Text message not found')
126132
expect(textMessage.To).toBe(onboardingData.phoneNumber)
127-
expect(textMessage.From).toBe('555-555-5555')
133+
expect(textMessage.From).toBe(sourceNumber.phoneNumber)
128134
expect(textMessage.Body).toMatch(/welcome/i)
129135
const codeMatch = textMessage.Body.match(CODE_REGEX)
130136
const code = codeMatch?.groups?.code
@@ -167,11 +173,14 @@ test('reset password with a link', async ({ page, insertNewUser }) => {
167173
).toBeVisible()
168174
await expect(page.getByText(/check your texts/i)).toBeVisible()
169175

176+
const sourceNumber = await prisma.sourceNumber.findFirstOrThrow({
177+
select: { phoneNumber: true },
178+
})
170179
const textMessage = await readText(user.phoneNumber)
171180
invariant(textMessage, 'Text message not found')
172181
expect(textMessage.Body).toMatch(/password reset/i)
173182
expect(textMessage.To).toBe(user.phoneNumber)
174-
expect(textMessage.From).toBe('555-555-5555')
183+
expect(textMessage.From).toBe(sourceNumber.phoneNumber)
175184
const resetPasswordUrl = extractUrl(textMessage.Body)
176185
invariant(resetPasswordUrl, 'Reset password URL not found')
177186
await page.goto(resetPasswordUrl)
@@ -225,11 +234,14 @@ test('reset password with a short code', async ({ page, insertNewUser }) => {
225234
).toBeVisible()
226235
await expect(page.getByText(/Check your texts/i)).toBeVisible()
227236

237+
const sourceNumber = await prisma.sourceNumber.findFirstOrThrow({
238+
select: { phoneNumber: true },
239+
})
228240
const textMessage = await readText(user.phoneNumber)
229241
invariant(textMessage, 'Text message not found')
230242
expect(textMessage.Body).toMatch(/password reset/i)
231243
expect(textMessage.To).toBe(user.phoneNumber)
232-
expect(textMessage.From).toBe('555-555-5555')
244+
expect(textMessage.From).toBe(sourceNumber.phoneNumber)
233245
const codeMatch = textMessage.Body.match(CODE_REGEX)
234246
const code = codeMatch?.groups?.code
235247
invariant(code, 'Reset Password code not found')

tests/e2e/send.test.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { invariant } from '@epic-web/invariant'
2+
import { faker } from '@faker-js/faker'
3+
import { prisma } from '#app/utils/db.server.ts'
4+
import { readText } from '#tests/mocks/utils.ts'
5+
import {
6+
expect,
7+
test,
8+
createRecipient,
9+
createMessage,
10+
waitFor,
11+
} from '#tests/playwright-utils.ts'
12+
13+
test('Users can write and send a message immediately', async ({
14+
page,
15+
login,
16+
}) => {
17+
const user = await login({ stripeId: faker.string.uuid() })
18+
const recipient = await prisma.recipient.create({
19+
select: { id: true, phoneNumber: true },
20+
data: {
21+
...createRecipient(),
22+
verified: true,
23+
userId: user.id,
24+
// TODO: make it more certain that the specified cron will never trigger during the test
25+
scheduleCron: '0 0 1 1 1',
26+
},
27+
})
28+
await page.goto(`/recipients/${recipient.id}`)
29+
await page
30+
.getByRole('main')
31+
.getByRole('link', { name: /new message/i })
32+
.click()
33+
34+
const { content: textMessageContent } = createMessage()
35+
await page
36+
.getByRole('main')
37+
.getByRole('textbox', { name: /message/i })
38+
.fill(textMessageContent)
39+
40+
await page.getByRole('main').getByRole('button', { name: /save/i }).click()
41+
42+
await expect(page.getByText(/message created/i)).toBeVisible()
43+
await page.getByRole('button', { name: /close toast/i }).click()
44+
45+
await page.getByRole('button', { name: /send now/i }).click()
46+
await expect(page.getByText(/message sent/i)).toBeVisible()
47+
48+
const sourceNumber = await prisma.sourceNumber.findFirstOrThrow({
49+
select: { phoneNumber: true },
50+
})
51+
const textMessage = await readText(recipient.phoneNumber)
52+
invariant(textMessage, 'Text message not found')
53+
expect(textMessage.To).toBe(recipient.phoneNumber)
54+
expect(textMessage.From).toBe(sourceNumber.phoneNumber)
55+
56+
expect(textMessage.Body).toBe(textMessageContent)
57+
})
58+
59+
test('Scheduled messages go out on schedule', async ({ page, login }) => {
60+
const user = await login({ stripeId: faker.string.uuid() })
61+
const recipient = await prisma.recipient.create({
62+
select: { id: true, phoneNumber: true },
63+
data: {
64+
...createRecipient(),
65+
verified: true,
66+
userId: user.id,
67+
scheduleCron: '0 0 1 1 1',
68+
},
69+
})
70+
71+
const message = await prisma.message.create({
72+
select: { id: true, content: true },
73+
data: {
74+
...createMessage(),
75+
sentAt: null,
76+
recipientId: recipient.id,
77+
},
78+
})
79+
80+
await page.goto(`/recipients/${recipient.id}/past`)
81+
await expect(page.getByText(/sent 0 messages/i)).toBeVisible()
82+
83+
await prisma.$transaction(async $prisma => {
84+
await $prisma.recipient.update({
85+
select: { id: true },
86+
where: { id: recipient.id },
87+
data: {
88+
scheduleCron: '* * * * *',
89+
},
90+
})
91+
await $prisma.message.update({
92+
where: { id: message.id },
93+
select: { id: true },
94+
data: {
95+
// it needs to appear as though it was prepared before the time it was due to be sent.
96+
updatedAt: new Date(new Date().getTime() - 1000 * 60 * 2),
97+
},
98+
})
99+
})
100+
101+
test.setTimeout(15000)
102+
103+
await waitFor(async () => {
104+
const messageToSend = await prisma.message.findUnique({
105+
select: { sentAt: true },
106+
where: { id: message.id },
107+
})
108+
if (messageToSend?.sentAt) return messageToSend
109+
})
110+
111+
await prisma.recipient.update({
112+
where: { id: recipient.id },
113+
data: { scheduleCron: '0 0 1 1 1' },
114+
})
115+
116+
const sourceNumber = await prisma.sourceNumber.findFirstOrThrow({
117+
select: { phoneNumber: true },
118+
})
119+
const textMessage = await readText(recipient.phoneNumber)
120+
invariant(textMessage, 'Text message not found')
121+
expect(textMessage.To).toBe(recipient.phoneNumber)
122+
expect(textMessage.From).toBe(sourceNumber.phoneNumber)
123+
124+
expect(textMessage.Body).toBe(message.content)
125+
126+
// TODO: real-time updates here would be cool.
127+
await page.reload()
128+
129+
await expect(page.getByText(/sent 1 message/i)).toBeVisible()
130+
await expect(page.getByText(message.content)).toBeVisible()
131+
})

tests/playwright-utils.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type GetOrInsertUserOptions = {
1717
username?: UserModel['username']
1818
password?: string
1919
phoneNumber?: UserModel['phoneNumber']
20+
stripeId?: string
2021
}
2122

2223
type User = {
@@ -31,6 +32,7 @@ async function getOrInsertUser({
3132
username,
3233
password,
3334
phoneNumber,
35+
stripeId,
3436
}: GetOrInsertUserOptions = {}): Promise<User> {
3537
const select = { id: true, phoneNumber: true, username: true, name: true }
3638
if (id) {
@@ -49,6 +51,7 @@ async function getOrInsertUser({
4951
...userData,
5052
phoneNumber,
5153
username,
54+
stripeId,
5255
roles: { connect: { name: 'user' } },
5356
password: { create: { hash: await getPasswordHash(password) } },
5457
},
@@ -107,7 +110,7 @@ export const { expect } = test
107110
export async function waitFor<ReturnValue>(
108111
cb: () => ReturnValue | Promise<ReturnValue>,
109112
{
110-
errorMessage,
113+
errorMessage = 'waitFor call timed out',
111114
timeout = 5000,
112115
}: { errorMessage?: string; timeout?: number } = {},
113116
) {

0 commit comments

Comments
 (0)