Skip to content

test fixes#893

Merged
BilalG1 merged 3 commits intodevfrom
test-email-fixes
Sep 12, 2025
Merged

test fixes#893
BilalG1 merged 3 commits intodevfrom
test-email-fixes

Conversation

@BilalG1
Copy link
Collaborator

@BilalG1 BilalG1 commented Sep 12, 2025

High-level PR Summary

This PR improves email testing in the end-to-end test suite by adding a new waitForMessagesWithSubject helper method to the Mailbox class. This method replaces the previous pattern of using arbitrary wait times (e.g., wait(2000)) followed by fetching and finding messages, which could lead to flaky tests. The new approach implements a polling mechanism that waits until messages with the specified subject appear, with a reasonable timeout. The PR updates all email-related tests to use this new helper method, making the tests more reliable and less dependent on timing assumptions.

⏱️ Estimated Review Time: 0h 15m

💡 Review Order Suggestion
Order File Path
1 apps/e2e/tests/helpers.ts
2 apps/e2e/tests/backend/endpoints/api/v1/send-email.test.ts
3 apps/e2e/tests/backend/endpoints/api/v1/unsubscribe-link.test.ts

Important

Improves email test reliability by adding waitForMessagesWithSubject in Mailbox and updating tests to use it.

  • Behavior:
    • Introduces waitForMessagesWithSubject in Mailbox class in helpers.ts to replace arbitrary wait times with a polling mechanism.
    • Updates email-related tests in send-email.test.ts and unsubscribe-link.test.ts to use waitForMessagesWithSubject for more reliable email verification.
  • Tests:
    • Replaces wait() calls with waitForMessagesWithSubject() in send-email.test.ts and unsubscribe-link.test.ts.
    • Ensures emails are verified by subject, reducing flakiness in tests.
  • Misc:
    • Minor refactoring in helpers.ts to support new functionality.

This description was created by Ellipsis for 90b7bda. You can customize this summary. It will automatically update as commits are pushed.


Review by RecurseML

🔍 Review performed on d14317c..f42dfc5

Severity Location Issue Action
Medium apps/e2e/tests/helpers.ts:235 Async function call not wrapped with runAsynchronously Dismiss
✅ Files analyzed, no issues (2)

apps/e2e/tests/backend/endpoints/api/v1/send-email.test.ts
apps/e2e/tests/backend/endpoints/api/v1/unsubscribe-link.test.ts

Need help? Join our Discord

Summary by CodeRabbit

  • Tests

    • More reliable email end-to-end tests by waiting for messages by subject instead of fixed delays and manual polling.
    • Multi-recipient scenarios now independently confirm delivery for each user.
    • Switched to snapshot-style assertions capturing full message metadata for clearer, more stable verification.
    • Unsubscribe email tests now wait deterministically for delivery before validating links.
  • Chores

    • Mailbox handling improved to reduce flakiness and repeated network calls.

@vercel
Copy link

vercel bot commented Sep 12, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
stack-backend Ready Ready Preview Comment Sep 12, 2025 2:05am
stack-dashboard Ready Ready Preview Comment Sep 12, 2025 2:05am
stack-demo Ready Ready Preview Comment Sep 12, 2025 2:05am
stack-docs Ready Ready Preview Comment Sep 12, 2025 2:05am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 12, 2025

Walkthrough

Replaces fixed delays and manual mailbox polling in e2e email tests with a new Mailbox.waitForMessagesWithSubject(subject) helper; implements polling + caching in Mailbox.fetchMessages and updates tests to assert the returned MailboxMessage snapshots. All edits are test-only.

Changes

Cohort / File(s) Summary
Mailbox helper
apps/e2e/tests/helpers.ts
Added Mailbox.waitForMessagesWithSubject(subject: string): Promise<MailboxMessage[]>; implemented polling (up to 20 retries, wait(200) between attempts) and added fullMessageCache to fetchMessages to reuse fetched full message bodies.
Email tests (send-email)
apps/e2e/tests/backend/endpoints/api/v1/send-email.test.ts
Replaced fixed waits and manual fetch+filter with mailbox.waitForMessagesWithSubject(subject) calls; assertions now use toMatchInlineSnapshot on returned MailboxMessage[] snapshots (full message metadata). Multi-user tests now await each user's mailbox separately.
Email tests (unsubscribe link)
apps/e2e/tests/backend/endpoints/api/v1/unsubscribe-link.test.ts
Replaced sleep+fetchMessages polling with waitForMessagesWithSubject(subject) and used first returned message for existing unsubscribe-link assertions.
Imports / minor formatting
apps/e2e/tests/helpers.ts
Added wait import from stack-shared/dist/utils/promises; minor formatting change in NiceResponse constructor (no semantic change).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Test
  participant Mailbox as Mailbox (tests/helpers)
  participant Server as Email API / Server

  Note over Test,Mailbox: Test requests email delivery (triggered by app flow)
  Test->>Mailbox: waitForMessagesWithSubject(subject)
  activate Mailbox
  Mailbox->>Server: fetchMessages() [full list]
  Server-->>Mailbox: messages (headers + maybe bodies)
  Mailbox->>Mailbox: cache full bodies by message.id
  alt matches found
    Mailbox-->>Test: [MailboxMessage[] matching subject]
  else no match
    Mailbox->>Mailbox: wait(200) then retry (up to 20)
    Mailbox-->>Test: throw "Message with subject ... not found" (after retries)
  end
  deactivate Mailbox
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • N2D4

Poem

"I hopped through logs and snippets bright,
Found subjects hiding out of sight.
I waited, cached, then gave a cheer—
Emails arrived, the snapshots clear.
🐇📬"

Pre-merge checks (2 passed, 1 inconclusive)

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title Check ❓ Inconclusive The current title "test fixes" indicates the PR touches tests but is overly generic and does not describe the primary change (adding Mailbox.waitForMessagesWithSubject and updating e2e email tests), so it fails to convey the main intent to someone scanning history. Because it lacks specificity, it is not helpful for quick reviewer context or changelog clarity. Under the repository's title guidance this makes the title inconclusive. Rename the PR to a concise, descriptive title such as "e2e: add Mailbox.waitForMessagesWithSubject and update email tests to reduce flakiness" (or "tests: replace fixed waits with mailbox.waitForMessagesWithSubject in e2e email tests") so the primary change is clear at a glance.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed The PR description includes the repository template comment and a detailed high-level summary explaining the new Mailbox.waitForMessagesWithSubject helper, which tests were updated, the rationale to reduce flakiness, affected files, and a review-order suggestion, providing clear context for reviewers. It also notes behavior, test changes, a minor refactor, and includes the automated review finding about a medium-severity lint/async issue. Overall the description satisfies the repository template and contains sufficient information for a typical code review.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch test-email-fixes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile Summary

This PR refactors email testing infrastructure to improve test reliability and reduce flakiness. The changes introduce a new waitForMessagesWithSubject helper method that implements proper polling instead of using fixed delays when waiting for emails.

Key Changes:

  • New utility method: Adds waitForMessagesWithSubject to the Mailbox class in helpers.ts that polls for messages with a specific subject line (up to 20 retries with 200ms intervals)
  • Test refactoring: Updates email-related tests in send-email.test.ts and unsubscribe-link.test.ts to replace manual wait(2000)/wait(3000) calls followed by message filtering with the new helper method
  • Improved reliability: Eliminates race conditions where tests might fail if emails don't arrive within arbitrary timeout periods

The polling mechanism waits up to 4 seconds total (20 × 200ms) for expected emails to arrive, making tests more deterministic while still being reasonably fast when emails arrive quickly. This pattern follows testing best practices by using condition-based waiting instead of fixed delays, which is particularly important for CI/CD environments where timing can be unpredictable. The changes maintain identical test behavior but with better reliability guarantees, bringing consistency to how email testing is handled across the codebase.

Confidence score: 5/5

  • This PR is safe to merge with minimal risk as it only improves test infrastructure without changing production code
  • Score reflects that these are well-contained test improvements using established patterns (polling with reasonable timeouts)
  • No files require special attention as all changes are straightforward test reliability improvements

3 files reviewed, no comments

Edit Code Review Bot Settings | Greptile

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
apps/e2e/tests/backend/endpoints/api/v1/send-email.test.ts (1)

595-597: Minor: await mailbox waits in parallel to speed up test

-    await userA.mailbox.waitForMessagesWithSubject(subject);
-    await userB.mailbox.waitForMessagesWithSubject(subject);
-    await userC.mailbox.waitForMessagesWithSubject(subject);
+    await Promise.all([
+      userA.mailbox.waitForMessagesWithSubject(subject),
+      userB.mailbox.waitForMessagesWithSubject(subject),
+      userC.mailbox.waitForMessagesWithSubject(subject),
+    ]);
apps/e2e/tests/helpers.ts (2)

200-203: Consider options on waitForMessagesWithSubject (includeBody, retries, interval)

Proposed signature change (backward compatible defaults keep current behavior):

-  public readonly waitForMessagesWithSubject: (subject: string) => Promise<MailboxMessage[]>;
+  public readonly waitForMessagesWithSubject: (
+    subject: string,
+    options?: { includeBody?: boolean; maxRetries?: number; intervalMs?: number }
+  ) => Promise<MailboxMessage[]>;

227-238: Optional: allow returning messages with bodies (unblocks body assertions without a second fetch)

This keeps default noBody behavior for existing snapshots, but enables consumers to request bodies when needed.

-    this.waitForMessagesWithSubject = async (subject: string) => {
-      const maxRetries = 20;
-      for (let i = 0; i < maxRetries; i++) {
-        const messages = await this.fetchMessages({ noBody: true });
-        const withSubject = messages.filter(m => m.subject === subject);
-        if (withSubject.length > 0) {
-          return withSubject;
-        }
-        await wait(200);
-      }
-      throw new Error(`Message with subject ${subject} not found`);
-    };
+    this.waitForMessagesWithSubject = async (
+      subject: string,
+      options?: { includeBody?: boolean; maxRetries?: number; intervalMs?: number }
+    ) => {
+      const maxRetries = options?.maxRetries ?? 20;
+      const intervalMs = options?.intervalMs ?? 200;
+      for (let i = 0; i < maxRetries; i++) {
+        const messages = await this.fetchMessages({ noBody: true });
+        const withSubject = messages.filter(m => m.subject === subject);
+        if (withSubject.length > 0) {
+          if (options?.includeBody) {
+            const full = await this.fetchMessages(); // uses cache
+            return full.filter(m => m.subject === subject);
+          }
+          return withSubject;
+        }
+        await wait(intervalMs);
+      }
+      throw new Error(`Message with subject ${subject} not found after ${maxRetries} retries`);
+    };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d14317c and f42dfc5.

📒 Files selected for processing (3)
  • apps/e2e/tests/backend/endpoints/api/v1/send-email.test.ts (5 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/unsubscribe-link.test.ts (2 hunks)
  • apps/e2e/tests/helpers.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer ES6 Map over Record when representing key–value collections

Files:

  • apps/e2e/tests/helpers.ts
  • apps/e2e/tests/backend/endpoints/api/v1/unsubscribe-link.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/send-email.test.ts
**/*.test.{ts,tsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

In tests, prefer .toMatchInlineSnapshot where possible; refer to snapshot-serializer.ts for snapshot formatting and handling of non-deterministic values

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/unsubscribe-link.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/send-email.test.ts
🧬 Code graph analysis (1)
apps/e2e/tests/helpers.ts (1)
packages/stack-shared/src/utils/promises.tsx (1)
  • wait (260-268)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Security Check
  • GitHub Check: build (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: all-good
  • GitHub Check: setup-tests
  • GitHub Check: build (22.x)
  • GitHub Check: docker
  • GitHub Check: docker
🔇 Additional comments (7)
apps/e2e/tests/backend/endpoints/api/v1/send-email.test.ts (4)

297-307: LGTM: subject-based wait with stable inline snapshot

Good switch to mailbox.waitForMessagesWithSubject and snapshotting metadata (avoids brittle HTML assertions).


412-412: LGTM: waiting on subject is sufficient

This ensures the draft send completed before asserting draft state.


741-741: LGTM: transactional subject wait

Consistent with the new helper; avoids HTML coupling.


780-780: LGTM: default category subject wait

Consistent usage across tests.

apps/e2e/tests/helpers.ts (3)

4-4: LGTM: import wait for polling helper


133-133: LGTM: cosmetic NiceResponse constructor change


211-226: Nice: full-message cache avoids redundant network calls

Caching the full message while optionally omitting body for snapshots keeps polling cheap and snapshots stable.

Comment on lines +51 to 53
const messages = await user.mailbox.waitForMessagesWithSubject("Custom Test Email Subject");
const sentEmail = messages[0];
expect(sentEmail!.body?.html).toMatchInlineSnapshot(`"http://localhost:8102/api/v1/emails/unsubscribe-link?code=%3Cstripped+query+param%3E"`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix: waitForMessagesWithSubject returns messages without bodies; fetch full message before asserting body

waitForMessagesWithSubject uses fetchMessages({ noBody: true }), so sentEmail.body is undefined and the snapshot/assertions will fail. Fetch the full message after waiting.

-  const messages = await user.mailbox.waitForMessagesWithSubject("Custom Test Email Subject");
-  const sentEmail = messages[0];
+  await user.mailbox.waitForMessagesWithSubject("Custom Test Email Subject");
+  const sentEmail = (await user.mailbox.fetchMessages()).find(m => m.subject === "Custom Test Email Subject")!;

Optionally make the link extraction robust (handles either plain-URL body or an link):

-  const unsubscribeLinkMatch = sentEmail!.body?.html.match(/href="([^"]+)"/);
-  expect(unsubscribeLinkMatch).toBeDefined();
-  const unsubscribeUrl = unsubscribeLinkMatch![1];
+  const html = sentEmail!.body?.html ?? "";
+  const unsubscribeUrl = html.includes('href="')
+    ? (html.match(/href="([^"]+)"/)![1])
+    : html.trim();
+  expect(unsubscribeUrl).toMatch(/^https?:\/\//);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const messages = await user.mailbox.waitForMessagesWithSubject("Custom Test Email Subject");
const sentEmail = messages[0];
expect(sentEmail!.body?.html).toMatchInlineSnapshot(`"http://localhost:8102/api/v1/emails/unsubscribe-link?code=%3Cstripped+query+param%3E"`);
await user.mailbox.waitForMessagesWithSubject("Custom Test Email Subject");
const sentEmail = (await user.mailbox.fetchMessages()).find(m => m.subject === "Custom Test Email Subject")!;
expect(sentEmail!.body?.html).toMatchInlineSnapshot(`"http://localhost:8102/api/v1/emails/unsubscribe-link?code=%3Cstripped+query+param%3E"`);

Comment on lines +143 to 145
const messages = await user.mailbox.waitForMessagesWithSubject("Custom Test Email Subject");
const sentEmail = messages[0];
expect(sentEmail).toBeDefined();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Same issue here: fetch full message before using body

This block also reads body from a noBody result.

-  const messages = await user.mailbox.waitForMessagesWithSubject("Custom Test Email Subject");
-  const sentEmail = messages[0];
+  await user.mailbox.waitForMessagesWithSubject("Custom Test Email Subject");
+  const sentEmail = (await user.mailbox.fetchMessages()).find(m => m.subject === "Custom Test Email Subject")!;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const messages = await user.mailbox.waitForMessagesWithSubject("Custom Test Email Subject");
const sentEmail = messages[0];
expect(sentEmail).toBeDefined();
await user.mailbox.waitForMessagesWithSubject("Custom Test Email Subject");
const sentEmail = (await user.mailbox.fetchMessages()).find(m => m.subject === "Custom Test Email Subject")!;
expect(sentEmail).toBeDefined();
🤖 Prompt for AI Agents
In apps/e2e/tests/backend/endpoints/api/v1/unsubscribe-link.test.ts around lines
143 to 145, the test accesses the message body without first fetching the full
message; modify the code to retrieve the complete message (e.g., call the
mailbox API to fetch the full message by id or use a provided method like
getMessage or fetchFullMessage) before reading its body, then assert on the
body—ensure you replace usages of messages[0].body with the
fetchedFullMessage.body (or equivalent) so you never read from a noBody result.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
apps/e2e/tests/helpers.ts (1)

235-235: Re: automated comment about runAsynchronously — this is already awaited.
The pattern in Line 235 uses await wait(200), not fire-and-forget; the bot warning looks like a false positive.

🧹 Nitpick comments (2)
apps/e2e/tests/helpers.ts (2)

210-226: Avoid N+1 full-message fetches when noBody is true.
When callers only need headers/subject (e.g., polling), short-circuit using the list payload to skip per-message detail fetches. Keeps Map usage (preferred over Record) and reduces network/CPU.

Apply this diff:

 this.fetchMessages = async ({ noBody } = {}) => {
-  const res = await niceFetch(new URL(`/api/v1/mailbox/${encodeURIComponent(mailboxName)}`, INBUCKET_API_URL));
-  return await Promise.all((res.body as any[]).map(async (message) => {
-    let fullMessage: any;
-    if (fullMessageCache.has(message.id)) {
-      fullMessage = fullMessageCache.get(message.id);
-    } else {
-      const fullMessageRes = await niceFetch(new URL(`/api/v1/mailbox/${encodeURIComponent(mailboxName)}/${message.id}`, INBUCKET_API_URL));
-      fullMessage = fullMessageRes.body;
-      fullMessageCache.set(message.id, fullMessage);
-    }
-    const messagePart = noBody ? omit(fullMessage, ["body", "attachments"]) : fullMessage;
-    return new MailboxMessage(messagePart);
-  }));
+  const res = await niceFetch(new URL(`/api/v1/mailbox/${encodeURIComponent(mailboxName)}`, INBUCKET_API_URL));
+  const list = res.body as any[];
+  if (noBody) {
+    // Build from list payload to avoid N extra network calls
+    return list.map((message) => new MailboxMessage(omit(message, ["body", "attachments"])));
+  }
+  return await Promise.all(list.map(async (message) => {
+    let fullMessage: any = fullMessageCache.get(message.id);
+    if (!fullMessage) {
+      const fullMessageRes = await niceFetch(new URL(`/api/v1/mailbox/${encodeURIComponent(mailboxName)}/${message.id}`, INBUCKET_API_URL));
+      fullMessage = fullMessageRes.body;
+      fullMessageCache.set(message.id, fullMessage);
+    }
+    return new MailboxMessage(fullMessage);
+  }));
 };

If the list payload lacks some fields needed by MailboxMessage in your snapshots, keep the current path for those cases and only short-circuit when safe.


227-238: Make polling cheaper and error messages more actionable.
Use noBody during polling and include mailbox/elapsed time + last subjects to ease debugging timeouts.

 this.waitForMessagesWithSubject = async (subject: string) => {
-  const maxRetries = 20;
-  for (let i = 0; i < maxRetries; i++) {
-    const messages = await this.fetchMessages();
-    const withSubject = messages.filter(m => m.subject === subject);
+  const maxRetries = 20;
+  const intervalMs = 200;
+  let lastSubjects: string[] = [];
+  for (let i = 0; i < maxRetries; i++) {
+    const messages = await this.fetchMessages({ noBody: true });
+    lastSubjects = messages.map(m => m.subject);
+    const withSubject = messages.filter(m => m.subject === subject);
     if (withSubject.length > 0) {
       return withSubject;
     }
-    await wait(200);
+    await wait(intervalMs);
   }
-  throw new Error(`Message with subject ${subject} not found`);
+  throw new Error(`Message with subject "${subject}" not found for ${this.emailAddress} after ${maxRetries} retries (~${maxRetries * intervalMs}ms). Last subjects seen: ${lastSubjects.slice(0, 10).join(", ")}`);
 };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f42dfc5 and f35fcde.

📒 Files selected for processing (1)
  • apps/e2e/tests/helpers.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer ES6 Map over Record when representing key–value collections

Files:

  • apps/e2e/tests/helpers.ts
🧬 Code graph analysis (1)
apps/e2e/tests/helpers.ts (1)
packages/stack-shared/src/utils/promises.tsx (1)
  • wait (260-268)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: setup-tests
  • GitHub Check: docker
  • GitHub Check: docker
  • GitHub Check: all-good
  • GitHub Check: build (22.x)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: build (22.x)
  • GitHub Check: Security Check
🔇 Additional comments (3)
apps/e2e/tests/helpers.ts (3)

4-4: Import for wait is appropriate and used correctly.
No issues; aligns with the new polling helper.


133-133: Constructor formatting change is a no-op.
Safe cosmetic tweak; no semantic impact.


202-202: Good addition: typed polling helper on Mailbox.
Clear API; returning an array is flexible for multi-send scenarios.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
apps/e2e/tests/backend/endpoints/api/v1/send-email.test.ts (5)

297-312: Assert cardinality and consider snapshotting only stable fields to reduce flakiness

Add an explicit length assertion so the test fails clearly if multiple emails with the same subject arrive. Optionally, snapshot a stable projection instead of the full message (body can churn).

Minimal addition:

   const messages = await user.mailbox.waitForMessagesWithSubject("Custom Test Email Subject");
+  expect(messages).toHaveLength(1);
   expect(messages).toMatchInlineSnapshot(`

Optional alternative (snapshot stable fields):

-  const messages = await user.mailbox.waitForMessagesWithSubject("Custom Test Email Subject");
-  expect(messages).toMatchInlineSnapshot(`
-    [
-      MailboxMessage {
-        "attachments": [],
-        "body": {
-          "html": "http://localhost:8102/api/v1/emails/unsubscribe-link?code=%3Cstripped+query+param%3E",
-          "text": "http://localhost:8102/api/v1/emails/unsubscribe-link?code=%3Cstripped+query+param%3E",
-        },
-        "from": "Test Project <test@example.com>",
-        "subject": "Custom Test Email Subject",
-        "to": ["<unindexed-mailbox--<stripped UUID>@stack-generated.example.com>"],
-        <some fields may have been hidden>,
-      },
-    ]
-  `);
+  const messages = await user.mailbox.waitForMessagesWithSubject("Custom Test Email Subject");
+  expect(messages).toHaveLength(1);
+  expect(messages.map(m => ({
+    from: m.from,
+    to: m.to,
+    subject: m.subject,
+    attachments: m.attachments.length,
+  }))).toMatchInlineSnapshot(`
+    [
+      {
+        "from": "Test Project <test@example.com>",
+        "to": ["<unindexed-mailbox--<stripped UUID>@stack-generated.example.com>"],
+        "subject": "Custom Test Email Subject",
+        "attachments": 0,
+      },
+    ]
+  `);

417-417: Validate the awaited result, not just completion

Capture the returned array and assert at least one (ideally exactly one) message to tighten the check.

-  await user.mailbox.waitForMessagesWithSubject("Overridden Subject");
+  const msgs = await user.mailbox.waitForMessagesWithSubject("Overridden Subject");
+  expect(msgs).toHaveLength(1);

600-602: Parallelize mailbox waits to speed up the test

Waiting sequentially adds unnecessary latency; use Promise.all.

-    await userA.mailbox.waitForMessagesWithSubject(subject);
-    await userB.mailbox.waitForMessagesWithSubject(subject);
-    await userC.mailbox.waitForMessagesWithSubject(subject);
+    await Promise.all(
+      [userA, userB, userC].map(u => u.mailbox.waitForMessagesWithSubject(subject))
+    );

746-746: Assert cardinality for the transactional email

Ensure exactly one message arrived with the subject.

-    await user.mailbox.waitForMessagesWithSubject("Transactional Test Subject");
+    const msgs = await user.mailbox.waitForMessagesWithSubject("Transactional Test Subject");
+    expect(msgs).toHaveLength(1);

785-785: Assert cardinality for the default category email

Same rationale as above: make the expectation explicit.

-    await user.mailbox.waitForMessagesWithSubject("Default Category Test Subject");
+    const msgs = await user.mailbox.waitForMessagesWithSubject("Default Category Test Subject");
+    expect(msgs).toHaveLength(1);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f35fcde and 90b7bda.

📒 Files selected for processing (1)
  • apps/e2e/tests/backend/endpoints/api/v1/send-email.test.ts (5 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.test.{ts,tsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

In tests, prefer .toMatchInlineSnapshot where possible; refer to snapshot-serializer.ts for snapshot formatting and handling of non-deterministic values

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/send-email.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer ES6 Map over Record when representing key–value collections

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/send-email.test.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: docker
  • GitHub Check: build (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: build (22.x)
  • GitHub Check: docker
  • GitHub Check: all-good
  • GitHub Check: setup-tests
  • GitHub Check: Security Check

@BilalG1 BilalG1 merged commit edc68fe into dev Sep 12, 2025
22 of 24 checks passed
@BilalG1 BilalG1 deleted the test-email-fixes branch September 12, 2025 17:41
This was referenced Nov 19, 2025
@coderabbitai coderabbitai bot mentioned this pull request Feb 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants