mirror of
https://github.com/stack-auth/stack.git
synced 2026-07-03 21:02:05 +08:00
## Summary - replace the dashboard feedback form's Web3Forms submission with an authenticated internal backend endpoint - send support and feature-request notifications through Stack Auth's native internal email pipeline - share internal project auth headers in the dashboard and add backend E2E coverage for support feedback ## Testing - pnpm typecheck - pnpm lint -- "src/components/feedback-form.tsx" "src/components/stack-companion/feature-request-board.tsx" <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Internal feedback submission endpoint with automated internal email notifications * New internal email builder and sending utility; recipient list configurable via env * **Enhancements** * Feedback form requires sign-in, disables submit when unauthenticated, and tightens validation * Centralized header helper for authenticated internal requests * Feature request board gates actions for signed-out users and improves upvote/submit reliability * Runtime retrieval/validation of the feature-tracking API key and streamlined user handling * **Tests** * End-to-end tests covering internal feedback flows, validation, and email delivery <!-- end of auto-generated comment: release notes by coderabbit.ai -->
119 lines
3.7 KiB
TypeScript
119 lines
3.7 KiB
TypeScript
import { afterEach, describe } from "vitest";
|
|
import { it } from "../../../../../helpers";
|
|
import { Auth, backendContext, createMailbox, niceBackendFetch, waitForOutboxEmailWithStatus } from "../../../../backend-helpers";
|
|
|
|
afterEach(() => {
|
|
delete process.env.STACK_INTERNAL_FEEDBACK_RECIPIENTS;
|
|
});
|
|
|
|
describe("POST /api/v1/internal/feedback", () => {
|
|
it("should reject unauthenticated requests", async ({ expect }) => {
|
|
const response = await niceBackendFetch("/api/v1/internal/feedback", {
|
|
method: "POST",
|
|
accessType: "client",
|
|
body: {
|
|
email: "test@example.com",
|
|
message: "This should be rejected",
|
|
},
|
|
});
|
|
|
|
expect(response).toMatchInlineSnapshot(`
|
|
NiceResponse {
|
|
"status": 400,
|
|
"body": {
|
|
"code": "SCHEMA_ERROR",
|
|
"details": {
|
|
"message": deindent\`
|
|
Request validation failed on POST /api/v1/internal/feedback:
|
|
- auth.user must be defined
|
|
\`,
|
|
},
|
|
"error": deindent\`
|
|
Request validation failed on POST /api/v1/internal/feedback:
|
|
- auth.user must be defined
|
|
\`,
|
|
},
|
|
"headers": Headers {
|
|
"x-stack-known-error": "SCHEMA_ERROR",
|
|
<some fields may have been hidden>,
|
|
},
|
|
}
|
|
`);
|
|
});
|
|
|
|
it("should send support feedback to the configured internal inbox", async ({ expect }) => {
|
|
const senderEmail = backendContext.value.mailbox.emailAddress;
|
|
const signInResult = await Auth.Otp.signIn();
|
|
const recipientMailbox = createMailbox("team@stack-auth.com");
|
|
const subject = `[Support] ${senderEmail}`;
|
|
|
|
const response = await niceBackendFetch("/api/v1/internal/feedback", {
|
|
method: "POST",
|
|
accessType: "client",
|
|
body: {
|
|
name: "Support Tester",
|
|
email: senderEmail,
|
|
message: "Please replace Web3Forms with native email delivery.",
|
|
},
|
|
});
|
|
|
|
expect(response).toMatchInlineSnapshot(`
|
|
NiceResponse {
|
|
"status": 200,
|
|
"body": { "success": true },
|
|
"headers": Headers { <some fields may have been hidden> },
|
|
}
|
|
`);
|
|
|
|
const emails = await waitForOutboxEmailWithStatus(subject, "sent");
|
|
expect(emails[0]?.to).toMatchObject({
|
|
type: "custom-emails",
|
|
emails: ["team@stack-auth.com"],
|
|
});
|
|
|
|
const messages = await recipientMailbox.waitForMessagesWithSubject(subject);
|
|
expect(messages).toHaveLength(1);
|
|
expect(messages[0]?.subject).toBe(subject);
|
|
expect(messages[0]?.body?.text).toContain("Support Tester");
|
|
expect(messages[0]?.body?.text).toContain(senderEmail);
|
|
expect(messages[0]?.body?.text).toContain(signInResult.userId);
|
|
expect(messages[0]?.body?.text).toContain("Please replace Web3Forms with native email delivery.");
|
|
});
|
|
|
|
it("should reject invalid payloads", async ({ expect }) => {
|
|
await Auth.Otp.signIn();
|
|
|
|
const response = await niceBackendFetch("/api/v1/internal/feedback", {
|
|
method: "POST",
|
|
accessType: "client",
|
|
body: {
|
|
email: backendContext.value.mailbox.emailAddress,
|
|
message: "",
|
|
},
|
|
});
|
|
|
|
expect(response).toMatchInlineSnapshot(`
|
|
NiceResponse {
|
|
"status": 400,
|
|
"body": {
|
|
"code": "SCHEMA_ERROR",
|
|
"details": {
|
|
"message": deindent\`
|
|
Request validation failed on POST /api/v1/internal/feedback:
|
|
- body.message must not be empty
|
|
\`,
|
|
},
|
|
"error": deindent\`
|
|
Request validation failed on POST /api/v1/internal/feedback:
|
|
- body.message must not be empty
|
|
\`,
|
|
},
|
|
"headers": Headers {
|
|
"x-stack-known-error": "SCHEMA_ERROR",
|
|
<some fields may have been hidden>,
|
|
},
|
|
}
|
|
`);
|
|
});
|
|
});
|