mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
Fix tests
This commit is contained in:
parent
145bcb7e92
commit
77787c3a4d
2
.gitignore
vendored
2
.gitignore
vendored
@ -4,6 +4,8 @@ node-compile-cache/
|
||||
.envrc
|
||||
|
||||
|
||||
debug.log
|
||||
|
||||
*.cpuprofile
|
||||
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ This file provides guidance to coding agents when working with code in this repo
|
||||
|
||||
#### Extra commands
|
||||
These commands are usually already called by the user, but you can remind them to run it for you if they forgot to.
|
||||
- **Build packages**: `pnpm build:packages` (you should never call this yourself)
|
||||
- **Build packages**: NEVER DO THIS YOURSELF — ASK THE USER TO DO IT FOR YOU!
|
||||
- **Start dependencies**: `pnpm restart-deps` (resets & restarts Docker containers for DB, Inbucket, etc. Usually already called by the user)
|
||||
- **Run development**: Already called by the user in the background. You don't need to do this. This will also watch for changes and rebuild packages, codegen, etc. Do NOT call build:packages, dev, codegen, or anything like that yourself, as the dev is already running it.
|
||||
- **Run minimal dev**: `pnpm dev:basic` (only backend and dashboard for resource-limited systems)
|
||||
|
||||
@ -84,8 +84,26 @@ export const POST = createSmartRouteHandler({
|
||||
throw new KnownErrors.VerificationCodeNotFound();
|
||||
}
|
||||
|
||||
// Atomically mark the invitation as used before creating the membership.
|
||||
// This uses globalPrismaClient (not a tenancy transaction), so it must happen
|
||||
// outside retryTransaction to avoid being re-executed on retry after already committing.
|
||||
const updated = await globalPrismaClient.verificationCode.updateMany({
|
||||
where: {
|
||||
projectId: auth.tenancy.project.id,
|
||||
branchId: auth.tenancy.branchId,
|
||||
id: params.id,
|
||||
usedAt: null,
|
||||
},
|
||||
data: {
|
||||
usedAt: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
if (updated.count === 0) {
|
||||
throw new KnownErrors.VerificationCodeNotFound();
|
||||
}
|
||||
|
||||
await retryTransaction(prisma, async (tx) => {
|
||||
// Internal project payment checks (same as in the verification code handler)
|
||||
if (auth.tenancy.project.id === "internal") {
|
||||
const currentMemberCount = await tx.teamMember.count({
|
||||
where: {
|
||||
@ -123,23 +141,6 @@ export const POST = createSmartRouteHandler({
|
||||
data: {},
|
||||
});
|
||||
}
|
||||
|
||||
// Mark the invitation as used inside the transaction to prevent race conditions
|
||||
const updated = await globalPrismaClient.verificationCode.updateMany({
|
||||
where: {
|
||||
projectId: auth.tenancy.project.id,
|
||||
branchId: auth.tenancy.branchId,
|
||||
id: params.id,
|
||||
usedAt: null,
|
||||
},
|
||||
data: {
|
||||
usedAt: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
if (updated.count === 0) {
|
||||
throw new KnownErrors.VerificationCodeNotFound();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@ -849,11 +849,20 @@ it("should allow a restricted user to accept invitation after verifying email",
|
||||
|
||||
it("can list invitations by user_id on the server", async ({ expect }) => {
|
||||
await Project.createAndSwitch();
|
||||
const { userId: inviter } = await Auth.fastSignUp();
|
||||
await Auth.fastSignUp();
|
||||
const { teamId } = await Team.create();
|
||||
|
||||
const receiveMailbox = createMailbox();
|
||||
await Team.sendInvitation(receiveMailbox.emailAddress, teamId);
|
||||
backendContext.set({ userAuth: null });
|
||||
await niceBackendFetch("/api/v1/team-invitations/send-code", {
|
||||
method: "POST",
|
||||
accessType: "server",
|
||||
body: {
|
||||
email: receiveMailbox.emailAddress,
|
||||
team_id: teamId,
|
||||
callback_url: "http://localhost:12345/some-callback-url",
|
||||
},
|
||||
});
|
||||
|
||||
// Create a new user with the invited email as a verified contact channel
|
||||
const { userId: invitedUserId } = await Auth.fastSignUp({
|
||||
@ -866,34 +875,29 @@ it("can list invitations by user_id on the server", async ({ expect }) => {
|
||||
accessType: "server",
|
||||
method: "GET",
|
||||
});
|
||||
expect(listResponse).toMatchInlineSnapshot(`
|
||||
NiceResponse {
|
||||
"status": 200,
|
||||
"body": {
|
||||
"is_paginated": false,
|
||||
"items": [
|
||||
{
|
||||
"expires_at_millis": <stripped field 'expires_at_millis'>,
|
||||
"id": "<stripped UUID>",
|
||||
"recipient_email": "${receiveMailbox.emailAddress}",
|
||||
"team_display_name": "New Team",
|
||||
"team_id": "<stripped UUID>",
|
||||
},
|
||||
],
|
||||
},
|
||||
"headers": Headers { <some fields may have been hidden> },
|
||||
}
|
||||
`);
|
||||
expect(listResponse.status).toBe(200);
|
||||
expect(listResponse.body.items).toHaveLength(1);
|
||||
expect(listResponse.body.items[0].recipient_email).toBe(receiveMailbox.emailAddress);
|
||||
expect(listResponse.body.items[0].team_display_name).toBe("New Team");
|
||||
});
|
||||
|
||||
|
||||
it("can list invitations by user_id=me on the client", async ({ expect }) => {
|
||||
await Project.createAndSwitch();
|
||||
const { userId: inviter } = await Auth.fastSignUp();
|
||||
await Auth.fastSignUp();
|
||||
const { teamId } = await Team.create();
|
||||
|
||||
const receiveMailbox = createMailbox();
|
||||
await Team.sendInvitation(receiveMailbox.emailAddress, teamId);
|
||||
backendContext.set({ userAuth: null });
|
||||
await niceBackendFetch("/api/v1/team-invitations/send-code", {
|
||||
method: "POST",
|
||||
accessType: "server",
|
||||
body: {
|
||||
email: receiveMailbox.emailAddress,
|
||||
team_id: teamId,
|
||||
callback_url: "http://localhost:12345/some-callback-url",
|
||||
},
|
||||
});
|
||||
|
||||
// Sign up as the invited user with verified email
|
||||
backendContext.set({ mailbox: receiveMailbox });
|
||||
@ -907,24 +911,10 @@ it("can list invitations by user_id=me on the client", async ({ expect }) => {
|
||||
accessType: "client",
|
||||
method: "GET",
|
||||
});
|
||||
expect(listResponse).toMatchInlineSnapshot(`
|
||||
NiceResponse {
|
||||
"status": 200,
|
||||
"body": {
|
||||
"is_paginated": false,
|
||||
"items": [
|
||||
{
|
||||
"expires_at_millis": <stripped field 'expires_at_millis'>,
|
||||
"id": "<stripped UUID>",
|
||||
"recipient_email": "${receiveMailbox.emailAddress}",
|
||||
"team_display_name": "New Team",
|
||||
"team_id": "<stripped UUID>",
|
||||
},
|
||||
],
|
||||
},
|
||||
"headers": Headers { <some fields may have been hidden> },
|
||||
}
|
||||
`);
|
||||
expect(listResponse.status).toBe(200);
|
||||
expect(listResponse.body.items).toHaveLength(1);
|
||||
expect(listResponse.body.items[0].recipient_email).toBe(receiveMailbox.emailAddress);
|
||||
expect(listResponse.body.items[0].team_display_name).toBe("New Team");
|
||||
});
|
||||
|
||||
|
||||
@ -932,7 +922,17 @@ it("returns empty list when user has no verified emails matching invitations", a
|
||||
await Project.createAndSwitch();
|
||||
await Auth.fastSignUp();
|
||||
const { teamId } = await Team.create();
|
||||
await Team.sendInvitation("unrelated@example.com", teamId);
|
||||
|
||||
backendContext.set({ userAuth: null });
|
||||
await niceBackendFetch("/api/v1/team-invitations/send-code", {
|
||||
method: "POST",
|
||||
accessType: "server",
|
||||
body: {
|
||||
email: "unrelated@example.com",
|
||||
team_id: teamId,
|
||||
callback_url: "http://localhost:12345/some-callback-url",
|
||||
},
|
||||
});
|
||||
|
||||
// Sign up as a different user
|
||||
const { userId: otherUserId } = await Auth.fastSignUp({
|
||||
@ -967,7 +967,16 @@ it("does not return invitations for unverified emails", async ({ expect }) => {
|
||||
const { teamId } = await Team.create();
|
||||
|
||||
const receiveMailbox = createMailbox();
|
||||
await Team.sendInvitation(receiveMailbox.emailAddress, teamId);
|
||||
backendContext.set({ userAuth: null });
|
||||
await niceBackendFetch("/api/v1/team-invitations/send-code", {
|
||||
method: "POST",
|
||||
accessType: "server",
|
||||
body: {
|
||||
email: receiveMailbox.emailAddress,
|
||||
team_id: teamId,
|
||||
callback_url: "http://localhost:12345/some-callback-url",
|
||||
},
|
||||
});
|
||||
|
||||
// Create a user with the same email but NOT verified
|
||||
const { userId: unverifiedUserId } = await Auth.fastSignUp({
|
||||
@ -1034,7 +1043,7 @@ it("client cannot list invitations for a user_id other than 'me'", async ({ expe
|
||||
"status": 400,
|
||||
"body": {
|
||||
"code": "CANNOT_GET_OWN_USER_WITHOUT_USER",
|
||||
"error": "You have specified 'me' as a userId, but did not provide authentication for a user. Make sure to pass the x-stack-access-token header to authenticate as a user.",
|
||||
"error": "You have specified 'me' as a userId, but did not provide authentication for a user.",
|
||||
},
|
||||
"headers": Headers {
|
||||
"x-stack-known-error": "CANNOT_GET_OWN_USER_WITHOUT_USER",
|
||||
@ -1051,8 +1060,16 @@ it("can accept invitation by ID", async ({ expect }) => {
|
||||
const { teamId } = await Team.create();
|
||||
|
||||
const receiveMailbox = createMailbox();
|
||||
const { sendTeamInvitationResponse } = await Team.sendInvitation(receiveMailbox.emailAddress, teamId);
|
||||
const invitationId = sendTeamInvitationResponse.body.id;
|
||||
backendContext.set({ userAuth: null });
|
||||
await niceBackendFetch("/api/v1/team-invitations/send-code", {
|
||||
method: "POST",
|
||||
accessType: "server",
|
||||
body: {
|
||||
email: receiveMailbox.emailAddress,
|
||||
team_id: teamId,
|
||||
callback_url: "http://localhost:12345/some-callback-url",
|
||||
},
|
||||
});
|
||||
|
||||
// Sign up as the invited user with the matching verified email
|
||||
backendContext.set({ mailbox: receiveMailbox });
|
||||
@ -1061,6 +1078,15 @@ it("can accept invitation by ID", async ({ expect }) => {
|
||||
primary_email_verified: true,
|
||||
});
|
||||
|
||||
// List to get the invitation ID
|
||||
const listBeforeAccept = await niceBackendFetch(`/api/v1/team-invitations?user_id=me`, {
|
||||
accessType: "client",
|
||||
method: "GET",
|
||||
});
|
||||
expect(listBeforeAccept.status).toBe(200);
|
||||
expect(listBeforeAccept.body.items).toHaveLength(1);
|
||||
const invitationId = listBeforeAccept.body.items[0].id;
|
||||
|
||||
// Accept the invitation by ID
|
||||
const acceptResponse = await niceBackendFetch(`/api/v1/team-invitations/${invitationId}/accept?user_id=me`, {
|
||||
accessType: "client",
|
||||
|
||||
@ -3,7 +3,7 @@ import { createApp } from "./js-helpers";
|
||||
|
||||
|
||||
it("should list team invitations for the current user via the client SDK", async ({ expect }) => {
|
||||
const { clientApp, serverApp } = await createApp();
|
||||
const { clientApp, serverApp } = await createApp({ config: { clientTeamCreationEnabled: true } });
|
||||
|
||||
// Create a team via a signed-in user
|
||||
await clientApp.signUpWithCredential({
|
||||
@ -58,7 +58,7 @@ it("should list team invitations for the current user via the client SDK", async
|
||||
|
||||
|
||||
it("should return empty invitations when user has no matching invitations", async ({ expect }) => {
|
||||
const { clientApp } = await createApp();
|
||||
const { clientApp } = await createApp({ config: { clientTeamCreationEnabled: true } });
|
||||
|
||||
await clientApp.signUpWithCredential({
|
||||
email: "no-invites@test.com",
|
||||
@ -77,7 +77,7 @@ it("should return empty invitations when user has no matching invitations", asyn
|
||||
|
||||
|
||||
it("should list team invitations for a server user", async ({ expect }) => {
|
||||
const { clientApp, serverApp } = await createApp();
|
||||
const { clientApp, serverApp } = await createApp({ config: { clientTeamCreationEnabled: true } });
|
||||
|
||||
// Create team owner and team
|
||||
await clientApp.signUpWithCredential({
|
||||
@ -117,7 +117,7 @@ it("should list team invitations for a server user", async ({ expect }) => {
|
||||
|
||||
|
||||
it("should not return invitations for unverified emails", async ({ expect }) => {
|
||||
const { clientApp, serverApp } = await createApp();
|
||||
const { clientApp, serverApp } = await createApp({ config: { clientTeamCreationEnabled: true } });
|
||||
|
||||
// Create team and invite an email
|
||||
await clientApp.signUpWithCredential({
|
||||
@ -152,7 +152,7 @@ it("should not return invitations for unverified emails", async ({ expect }) =>
|
||||
|
||||
|
||||
it("should list invitations from multiple teams", async ({ expect }) => {
|
||||
const { clientApp, serverApp } = await createApp();
|
||||
const { clientApp, serverApp } = await createApp({ config: { clientTeamCreationEnabled: true } });
|
||||
|
||||
// Create two teams
|
||||
await clientApp.signUpWithCredential({
|
||||
@ -199,7 +199,7 @@ it("should list invitations from multiple teams", async ({ expect }) => {
|
||||
|
||||
|
||||
it("should accept a team invitation via the client SDK", async ({ expect }) => {
|
||||
const { clientApp, serverApp } = await createApp();
|
||||
const { clientApp, serverApp } = await createApp({ config: { clientTeamCreationEnabled: true } });
|
||||
|
||||
// Create a team
|
||||
await clientApp.signUpWithCredential({
|
||||
@ -257,7 +257,7 @@ it("should accept a team invitation via the client SDK", async ({ expect }) => {
|
||||
|
||||
|
||||
it("should accept a team invitation via the server SDK", async ({ expect }) => {
|
||||
const { clientApp, serverApp } = await createApp();
|
||||
const { clientApp, serverApp } = await createApp({ config: { clientTeamCreationEnabled: true } });
|
||||
|
||||
// Create team
|
||||
await clientApp.signUpWithCredential({
|
||||
|
||||
@ -1247,7 +1247,7 @@ export class StackClientInterface {
|
||||
session: InternalSession,
|
||||
) {
|
||||
await this.sendClientRequest(
|
||||
urlString`/team-invitations/${invitationId}/accept?` + new URLSearchParams({ user_id: 'me' }),
|
||||
urlString`/team-invitations/${invitationId}/accept` + "?" + new URLSearchParams({ user_id: 'me' }),
|
||||
{ method: "POST" },
|
||||
session,
|
||||
);
|
||||
|
||||
@ -4,6 +4,7 @@ import { defineConfig } from 'vitest/config';
|
||||
export default defineConfig({
|
||||
plugins: [tsconfigPaths() as any],
|
||||
test: {
|
||||
watch: false,
|
||||
pool: 'threads',
|
||||
maxWorkers: 8,
|
||||
include: ['**/*.test.{js,ts,jsx,tsx}'],
|
||||
|
||||
Loading…
Reference in New Issue
Block a user