Skip to content

Commit

Permalink
Create fewer projects in unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
N2D4 committed Dec 23, 2024
1 parent 330ddd4 commit e704408
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 119 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"typecheck",
"typehack",
"Uncapitalize",
"unindexed",
"unsubscribers",
"upsert",
"webapi",
Expand Down
53 changes: 29 additions & 24 deletions apps/e2e/tests/backend/backend-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import { InternalProjectsCrud } from "@stackframe/stack-shared/dist/interface/cr
import { encodeBase64 } from "@stackframe/stack-shared/dist/utils/bytes";
import { generateSecureRandomString } from "@stackframe/stack-shared/dist/utils/crypto";
import { StackAssertionError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { filterUndefined, omit } from "@stackframe/stack-shared/dist/utils/objects";
import { filterUndefined } from "@stackframe/stack-shared/dist/utils/objects";
import { nicify } from "@stackframe/stack-shared/dist/utils/strings";
import * as jose from "jose";
import { randomUUID } from "node:crypto";
import { expect } from "vitest";
import { Context, INBUCKET_API_URL, Mailbox, MailboxMessage, NiceRequestInit, NiceResponse, STACK_BACKEND_BASE_URL, STACK_INTERNAL_PROJECT_ADMIN_KEY, STACK_INTERNAL_PROJECT_CLIENT_KEY, STACK_INTERNAL_PROJECT_ID, STACK_INTERNAL_PROJECT_SERVER_KEY, generatedEmailSuffix, localRedirectUrl, niceFetch, updateCookiesFromResponse } from "../helpers";
import { Context, Mailbox, NiceRequestInit, NiceResponse, STACK_BACKEND_BASE_URL, STACK_INTERNAL_PROJECT_ADMIN_KEY, STACK_INTERNAL_PROJECT_CLIENT_KEY, STACK_INTERNAL_PROJECT_ID, STACK_INTERNAL_PROJECT_SERVER_KEY, generatedEmailSuffix, localRedirectUrl, niceFetch, updateCookiesFromResponse } from "../helpers";

type BackendContext = {
readonly projectKeys: ProjectKeys,
readonly defaultProjectKeys: ProjectKeys,
readonly mailbox: Mailbox,
readonly userAuth: {
readonly refreshToken?: string,
Expand All @@ -30,13 +31,20 @@ type BackendContext = {

export const backendContext = new Context<BackendContext, Partial<BackendContext>>(
() => ({
defaultProjectKeys: InternalProjectKeys,
projectKeys: InternalProjectKeys,
mailbox: createMailbox(`default-mailbox--${randomUUID()}${generatedEmailSuffix}`),
generatedMailboxNamesCount: 0,
userAuth: null,
ipData: undefined,
}),
(acc, update) => {
if ("defaultProjectKeys" in update) {
throw new StackAssertionError("Cannot set defaultProjectKeys");
}
if ("mailbox" in update && !(update.mailbox instanceof Mailbox)) {
throw new StackAssertionError("Must create a mailbox with createMailbox()!");
}
return {
...acc,
...update,
Expand All @@ -50,26 +58,7 @@ export function createMailbox(email?: string): Mailbox {
email = `mailbox-${backendContext.value.generatedMailboxNamesCount}--${randomUUID()}${generatedEmailSuffix}`;
}
if (!email.includes("@")) throw new StackAssertionError(`Invalid mailbox email: ${email}`);
const mailboxName = email.split("@")[0];
const fullMessageCache = new Map<string, any>();
return {
emailAddress: email,
async fetchMessages({ 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);
}));
},
};
return new Mailbox("(we can ignore the disclaimer here)" as any, email);
}

export type ProjectKeys = "no-project" | {
Expand Down Expand Up @@ -169,6 +158,19 @@ export async function niceBackendFetch(url: string | URL, options?: Omit<NiceReq
}


/**
* Creates a new mailbox with a different email address, and sets it as the current mailbox.
*/
export async function bumpEmailAddress(options: { unindexed?: boolean } = {}) {
let emailAddress = undefined;
if (options.unindexed) {
emailAddress = `unindexed-mailbox--${randomUUID()}${generatedEmailSuffix}`;
}
const mailbox = createMailbox(emailAddress);
backendContext.set({ mailbox });
return mailbox;
}

export namespace Auth {
export async function ensureParsableAccessToken() {
const accessToken = backendContext.value.userAuth?.accessToken;
Expand Down Expand Up @@ -484,7 +486,6 @@ export namespace Auth {
}

export async function initiateRegistration(): Promise<{ code: string }> {

const response = await niceBackendFetch("/api/v1/auth/passkey/initiate-passkey-registration", {
method: "POST",
accessType: "client",
Expand Down Expand Up @@ -925,7 +926,9 @@ export namespace Project {
backendContext.set({
projectKeys: InternalProjectKeys,
});
await Auth.Otp.signIn();
const oldMailbox = backendContext.value.mailbox;
await bumpEmailAddress({ unindexed: true });
const { userId } = await Auth.Otp.signIn();
const adminAccessToken = backendContext.value.userAuth?.accessToken;
expect(adminAccessToken).toBeDefined();
const { projectId, createProjectResponse } = await Project.create(body);
Expand All @@ -935,9 +938,11 @@ export namespace Project {
projectId,
},
userAuth: null,
mailbox: oldMailbox,
});

return {
creatorUserId: userId,
projectId,
adminAccessToken: adminAccessToken!,
createProjectResponse,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { it } from "../../../../../helpers";
import { Auth, ContactChannels, Project, backendContext, createMailbox, niceBackendFetch } from "../../../../backend-helpers";
import { Auth, ContactChannels, backendContext, bumpEmailAddress, createMailbox, niceBackendFetch } from "../../../../backend-helpers";

it("create contact channel on the client", async ({ expect }) => {
await Project.createAndSwitch({ config: { magic_link_enabled: true } });
await Auth.Otp.signIn();
const mailbox = createMailbox();
const response = await niceBackendFetch("/api/v1/contact-channels", {
accessType: "client",
method: "POST",
body: {
value: "[email protected]",
value: mailbox.emailAddress,
type: "email",
used_for_auth: true,
user_id: "me",
Expand All @@ -24,7 +24,7 @@ it("create contact channel on the client", async ({ expect }) => {
"type": "email",
"used_for_auth": true,
"user_id": "<stripped UUID>",
"value": "test@example.com",
"value": "mailbox-1--<stripped UUID>@stack-generated.example.com",
},
"headers": Headers { <some fields may have been hidden> },
}
Expand Down Expand Up @@ -56,7 +56,7 @@ it("create contact channel on the client", async ({ expect }) => {
"type": "email",
"used_for_auth": true,
"user_id": "<stripped UUID>",
"value": "test@example.com",
"value": "mailbox-1--<stripped UUID>@stack-generated.example.com",
},
],
},
Expand All @@ -66,14 +66,14 @@ it("create contact channel on the client", async ({ expect }) => {
});

it("cannot create duplicate contact channels", async ({ expect }) => {
await Project.createAndSwitch({ config: { magic_link_enabled: true } });
await Auth.Otp.signIn();
const mailbox = createMailbox();

await niceBackendFetch("/api/v1/contact-channels", {
accessType: "client",
method: "POST",
body: {
value: "[email protected]",
value: mailbox.emailAddress,
type: "email",
used_for_auth: true,
user_id: "me",
Expand All @@ -84,7 +84,7 @@ it("cannot create duplicate contact channels", async ({ expect }) => {
accessType: "client",
method: "POST",
body: {
value: "[email protected]",
value: mailbox.emailAddress,
type: "email",
used_for_auth: true,
user_id: "me",
Expand All @@ -100,14 +100,13 @@ it("cannot create duplicate contact channels", async ({ expect }) => {
});

it("create contact channel on the server", async ({ expect }) => {
await Project.createAndSwitch({ config: { magic_link_enabled: true } });
const { userId } = await Auth.Otp.signIn();

const mailbox = createMailbox();
const response = await niceBackendFetch(`/api/v1/contact-channels`, {
accessType: "server",
method: "POST",
body: {
value: "[email protected]",
value: mailbox.emailAddress,
type: "email",
used_for_auth: false,
is_verified: true,
Expand All @@ -124,7 +123,7 @@ it("create contact channel on the server", async ({ expect }) => {
"type": "email",
"used_for_auth": false,
"user_id": "<stripped UUID>",
"value": "test@example.com",
"value": "mailbox-1--<stripped UUID>@stack-generated.example.com",
},
"headers": Headers { <some fields may have been hidden> },
}
Expand Down Expand Up @@ -153,7 +152,7 @@ it("create contact channel on the server", async ({ expect }) => {
"type": "email",
"used_for_auth": false,
"user_id": "<stripped UUID>",
"value": "test@example.com",
"value": "mailbox-1--<stripped UUID>@stack-generated.example.com",
},
],
}
Expand All @@ -162,7 +161,6 @@ it("create contact channel on the server", async ({ expect }) => {


it("delete contact channel on the client", async ({ expect }) => {
await Project.createAndSwitch({ config: { magic_link_enabled: true } });
await Auth.Otp.signIn();

const meResponse = await niceBackendFetch("/api/v1/contact-channels?user_id=me", {
Expand Down Expand Up @@ -210,7 +208,6 @@ it("delete contact channel on the client", async ({ expect }) => {
});

it("cannot delete a contact channel that doesn't exist", async ({ expect }) => {
await Project.createAndSwitch({ config: { magic_link_enabled: true } });
await Auth.Otp.signIn();

const deleteResponse = await niceBackendFetch("/api/v1/contact-channels/me/031448ab-178b-4d43-b31b-28f16c3c52a9", {
Expand All @@ -227,7 +224,6 @@ it("cannot delete a contact channel that doesn't exist", async ({ expect }) => {
});

it("lists current user's contact channels on the client", async ({ expect }) => {
await Project.createAndSwitch({ config: { magic_link_enabled: true } });
await Auth.Otp.signIn();

const response = await niceBackendFetch("/api/v1/contact-channels?user_id=me", {
Expand Down Expand Up @@ -257,7 +253,6 @@ it("lists current user's contact channels on the client", async ({ expect }) =>


it("cannot list contact channels that is not from the current user on the client", async ({ expect }) => {
await Project.createAndSwitch({ config: { magic_link_enabled: true } });
await Auth.Otp.signIn();

const response = await niceBackendFetch("/api/v1/contact-channels?user_id=031448ab-178b-4d43-b31b-28f16c3c52a9", {
Expand All @@ -273,10 +268,8 @@ it("cannot list contact channels that is not from the current user on the client
});

it("login with a newly created contact channel", async ({ expect }) => {
await Project.createAndSwitch({ config: { magic_link_enabled: true } });
await Auth.Otp.signIn();
const newMailbox = createMailbox();
backendContext.set({ mailbox: newMailbox });
const newMailbox = await bumpEmailAddress();

const ccResponse = await niceBackendFetch("/api/v1/contact-channels", {
accessType: "client",
Expand Down Expand Up @@ -316,7 +309,6 @@ it("login with a newly created contact channel", async ({ expect }) => {
});

it("creates a new account when login with a contact channel that is not used for auth", async ({ expect }) => {
await Project.createAndSwitch({ config: { magic_link_enabled: true } });
await Auth.Otp.signIn();
const newMailbox = createMailbox();

Expand Down Expand Up @@ -378,7 +370,6 @@ it("creates a new account when login with a contact channel that is not used for
});

it("should update contact channel used for auth to true even if that contact channel is already a primary channel", async ({ expect }) => {
await Project.createAndSwitch({ config: { magic_link_enabled: true } });
await Auth.Otp.signIn();
const newMailbox = createMailbox();

Expand Down Expand Up @@ -440,7 +431,6 @@ it("should update contact channel used for auth to true even if that contact cha
});

it("updates contact channel used for auth", async ({ expect }) => {
await Project.createAndSwitch({ config: { magic_link_enabled: true } });
await Auth.Otp.signIn();
const newMailbox = createMailbox();

Expand Down Expand Up @@ -502,7 +492,6 @@ it("updates contact channel used for auth", async ({ expect }) => {
});

it("updates contact channel primary status", async ({ expect }) => {
await Project.createAndSwitch({ config: { magic_link_enabled: true } });
await Auth.Otp.signIn();
const newMailbox = createMailbox();

Expand Down Expand Up @@ -575,7 +564,6 @@ it("updates contact channel primary status", async ({ expect }) => {
});

it("sets a primary contact channel to non-primary", async ({ expect }) => {
await Project.createAndSwitch({ config: { magic_link_enabled: true } });
await Auth.Otp.signIn();

const response = await ContactChannels.getTheOnlyContactChannel();
Expand Down
22 changes: 7 additions & 15 deletions apps/e2e/tests/backend/endpoints/api/v1/projects.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isBase64Url } from "@stackframe/stack-shared/dist/utils/bytes";
import { it } from "../../../../helpers";
import { Auth, InternalProjectKeys, Project, backendContext, createMailbox, niceBackendFetch } from "../../../backend-helpers";
import { Auth, InternalProjectKeys, Project, backendContext, niceBackendFetch } from "../../../backend-helpers";


it("should not have have access to the project without project keys", async ({ expect }) => {
Expand Down Expand Up @@ -1005,14 +1005,11 @@ it("deletes a project with users, teams, and permissions", async ({ expect }) =>
});

it("makes sure user have the correct managed project ID after project creation", async ({ expect }) => {
const { userId } = await Auth.Otp.signIn();

backendContext.set({ projectKeys: InternalProjectKeys });
const { projectId } = await Project.createAndGetAdminToken();
const { creatorUserId, projectId } = await Project.createAndGetAdminToken();

backendContext.set({ projectKeys: InternalProjectKeys });

const userResponse = await niceBackendFetch(`/api/v1/users/${userId}`, {
const userResponse = await niceBackendFetch(`/api/v1/users/${creatorUserId}`, {
accessType: "server",
method: "GET",
});
Expand All @@ -1022,10 +1019,8 @@ it("makes sure user have the correct managed project ID after project creation",
});

it("makes sure user don't have managed project ID after project deletion", async ({ expect }) => {
const { userId } = await Auth.Otp.signIn();

backendContext.set({ projectKeys: InternalProjectKeys });
const { adminAccessToken } = await Project.createAndGetAdminToken();
const { creatorUserId, adminAccessToken } = await Project.createAndGetAdminToken();

// Delete the project
const deleteResponse = await niceBackendFetch(`/api/v1/projects/current`, {
Expand All @@ -1046,7 +1041,7 @@ it("makes sure user don't have managed project ID after project deletion", async

backendContext.set({ projectKeys: InternalProjectKeys });

const userResponse = await niceBackendFetch(`/api/v1/users/${userId}`, {
const userResponse = await niceBackendFetch(`/api/v1/users/${creatorUserId}`, {
accessType: "server",
method: "GET",
});
Expand All @@ -1055,12 +1050,9 @@ it("makes sure user don't have managed project ID after project deletion", async
});

it("makes sure other users are not affected by project deletion", async ({ expect }) => {
const { userId: userId1 } = await Auth.Otp.signIn();
backendContext.set({ projectKeys: InternalProjectKeys });
const { projectId } = await Project.createAndGetAdminToken();
const { creatorUserId, projectId } = await Project.createAndGetAdminToken();

backendContext.set({ mailbox: createMailbox(), projectKeys: InternalProjectKeys });
await Auth.Otp.signIn();
backendContext.set({ projectKeys: InternalProjectKeys });
const { adminAccessToken } = await Project.createAndGetAdminToken();

Expand All @@ -1074,7 +1066,7 @@ it("makes sure other users are not affected by project deletion", async ({ expec
});

backendContext.set({ projectKeys: InternalProjectKeys });
const userResponse1 = await niceBackendFetch(`/api/v1/users/${userId1}`, {
const userResponse1 = await niceBackendFetch(`/api/v1/users/${creatorUserId}`, {
accessType: "server",
method: "GET",
});
Expand Down
Loading

0 comments on commit e704408

Please sign in to comment.