test(organization, username): union regression suites with next features (#9616, #9577, #9792, #9464)

Resolves the team and username test-file conflicts as the union of next's
new feature suites and main's regression tests, whose source fixes merged in
cleanly:

- team.test.ts keeps next's listUserTeams suites and adds main's
  comma-in-team-id rejection test (#9616), plus the
  databaseHooks.user.create.before -> emailVerified:true setup needed because
  the merged invitation gate now defaults on (#9577).
- username.test.ts keeps next's immutable-username suite and adds main's
  verify-email callbackURL encoding test (#9792); the admin createUser
  username validation (#9464) is orthogonal and merged cleanly.

team.test.ts 43 pass, username.test.ts 41 pass.

(cherry picked from commit ea3ed7af30c12901252085c5ddf43a7dca57f929)
This commit is contained in:
Gustavo Valverde
2026-05-31 00:10:36 +01:00
parent 3fbc2f3405
commit 62b589797e
2 changed files with 157 additions and 0 deletions
@@ -23,6 +23,15 @@ describe("team", async () => {
logger: {
level: "error",
},
databaseHooks: {
user: {
create: {
before: async (user) => ({
data: { ...user, emailVerified: true },
}),
},
},
},
});
const { headers } = await signInWithTestUser();
@@ -418,6 +427,15 @@ describe("team", async () => {
logger: {
level: "error",
},
databaseHooks: {
user: {
create: {
before: async (user) => ({
data: { ...user, emailVerified: true },
}),
},
},
},
});
const { headers } = await signInWithTestUser();
@@ -518,6 +536,15 @@ describe("setActiveTeam org scoping", async () => {
logger: {
level: "error",
},
databaseHooks: {
user: {
create: {
before: async (user) => ({
data: { ...user, emailVerified: true },
}),
},
},
},
});
const { headers } = await signInWithTestUser();
@@ -773,6 +800,15 @@ describe("multi team support", async () => {
logger: {
level: "error",
},
databaseHooks: {
user: {
create: {
before: async (user) => ({
data: { ...user, emailVerified: true },
}),
},
},
},
},
{
testWith: "sqlite",
@@ -1608,3 +1644,75 @@ describe("listUserTeams with organizationId parameter", async () => {
expect(res).toBeInstanceOf(Error);
});
});
describe("invitation with team id containing comma", async () => {
const { auth, signInWithTestUser } = await getTestInstance(
{
advanced: {
database: {
generateId: ({ model }) => {
if (model === "team") {
return `team,id,${crypto.randomUUID()}`;
}
return crypto.randomUUID();
},
},
},
plugins: [
organization({
async sendInvitationEmail() {},
teams: { enabled: true },
}),
],
logger: { level: "error" },
databaseHooks: {
user: {
create: {
before: async (user) => ({
data: { ...user, emailVerified: true },
}),
},
},
},
},
{ testWith: "sqlite" },
);
const admin = await signInWithTestUser();
const org = await auth.api.createOrganization({
headers: admin.headers,
body: { name: "Comma Org", slug: "comma-org" },
});
if (!org) throw new Error("failed to create organization");
const organizationId = org.id;
const team = await auth.api.createTeam({
headers: admin.headers,
body: { name: "Comma Team", organizationId },
});
it("seeds a team whose generated id contains the storage separator", () => {
expect(team.id).toContain(",");
});
it.each([
{ name: "string body", teamId: team.id },
{ name: "array body", teamId: [team.id] },
])("rejects createInvitation with $name", async ({ teamId }) => {
await expect(
auth.api.createInvitation({
headers: admin.headers,
body: {
email: "comma-invitee@email.com",
role: "member",
organizationId,
teamId,
},
}),
).rejects.toMatchObject({
status: "BAD_REQUEST",
body: { code: "INVALID_TEAM_ID" },
});
});
});
@@ -819,3 +819,52 @@ describe("immutable username", async () => {
expect(session?.user.displayUsername).toBe("Updated Display Name");
});
});
describe("username sign-in verify-email callbackURL", async () => {
/**
* The verify-email link sent on username sign-in for an unverified user must
* keep the caller's `callbackURL` intact. A raw interpolation truncates any
* value containing `&` at the first ampersand.
*
* @see https://github.com/better-auth/better-auth/issues/6086
*/
it("encodes callbackURL in the verify-email link on username sign-in", async () => {
let capturedUrl = "";
const { client } = await getTestInstance(
{
emailAndPassword: { enabled: true, requireEmailVerification: true },
emailVerification: {
sendOnSignIn: true,
async sendVerificationEmail({ url }) {
capturedUrl = url;
},
},
plugins: [username()],
},
{
clientOptions: {
plugins: [usernameClient()],
},
},
);
await client.signUp.email({
email: "encode-username@example.com",
username: "encode_username",
password: "correct-password",
name: "Encode Username",
});
const callbackURL = "/welcome?ref=username&plan=pro";
await client.signIn.username({
username: "encode_username",
password: "correct-password",
callbackURL,
});
expect(capturedUrl).not.toBe("");
expect(new URL(capturedUrl).searchParams.get("callbackURL")).toBe(
callbackURL,
);
});
});