* Add validation for team account names - Prevent creating teams with reserved names like 'billing' and 'settings' - Add regex validation to block team names with special characters - Update localization for new error messages - Extend E2E tests to cover various invalid team name scenarios * Enhance team account name validation and slug generation - Add comprehensive tests for account slug generation in Supabase - Improve team name validation schema to handle special characters - Add form validation message display in update team account name form - Refine slug generation to handle various edge cases like special characters, non-ASCII text, and mixed case
312 lines
9.0 KiB
TypeScript
312 lines
9.0 KiB
TypeScript
import { Page, expect, test } from '@playwright/test';
|
|
|
|
import { InvitationsPageObject } from '../invitations/invitations.po';
|
|
import { TeamAccountsPageObject } from './team-accounts.po';
|
|
|
|
// Helper function to set up a team with a member
|
|
async function setupTeamWithMember(page: Page, memberRole = 'member') {
|
|
// Setup invitations page object
|
|
const invitations = new InvitationsPageObject(page);
|
|
const teamAccounts = invitations.teamAccounts;
|
|
|
|
// Setup team with owner
|
|
const { email: ownerEmail, slug } = await invitations.setup();
|
|
|
|
// Navigate to members page
|
|
await invitations.navigateToMembers();
|
|
|
|
// Create a new member email and invite them with the specified role
|
|
const memberEmail = invitations.auth.createRandomEmail();
|
|
|
|
const invites = [
|
|
{
|
|
email: memberEmail,
|
|
role: memberRole,
|
|
},
|
|
];
|
|
|
|
await invitations.openInviteForm();
|
|
await invitations.inviteMembers(invites);
|
|
|
|
// Verify the invitation was sent
|
|
await expect(invitations.getInvitations()).toHaveCount(1);
|
|
|
|
// Sign out the current user
|
|
await page.context().clearCookies();
|
|
|
|
// Sign up with the new member email and accept the invitation
|
|
await invitations.auth.visitConfirmEmailLink(memberEmail);
|
|
|
|
await invitations.auth.signUp({
|
|
email: memberEmail,
|
|
password: 'password',
|
|
repeatPassword: 'password',
|
|
});
|
|
|
|
await invitations.auth.visitConfirmEmailLink(memberEmail);
|
|
|
|
await invitations.acceptInvitation();
|
|
|
|
await invitations.teamAccounts.openAccountsSelector();
|
|
|
|
await expect(invitations.teamAccounts.getTeams()).toHaveCount(1);
|
|
|
|
// Sign out and sign back in as the original owner
|
|
await page.context().clearCookies();
|
|
|
|
await page.goto('/auth/sign-in');
|
|
|
|
await invitations.auth.signIn({
|
|
email: ownerEmail,
|
|
password: 'password',
|
|
});
|
|
|
|
await page.waitForURL('/home');
|
|
|
|
// Navigate to the team members page
|
|
await page.goto(`/home/${slug}/members`);
|
|
|
|
return { invitations, teamAccounts, ownerEmail, memberEmail, slug };
|
|
}
|
|
|
|
test.describe('Team Accounts', () => {
|
|
let page: Page;
|
|
let teamAccounts: TeamAccountsPageObject;
|
|
|
|
test.beforeAll(async ({ browser }) => {
|
|
page = await browser.newPage();
|
|
teamAccounts = new TeamAccountsPageObject(page);
|
|
});
|
|
|
|
test('user can update their team name (and slug)', async () => {
|
|
await teamAccounts.setup();
|
|
|
|
const { teamName, slug } = teamAccounts.createTeamName();
|
|
|
|
await teamAccounts.goToSettings();
|
|
|
|
const request = teamAccounts.updateName(teamName, slug);
|
|
|
|
// the slug should be updated to match the new team name
|
|
const newUrl = page.waitForURL(`**/home/${slug}/settings`);
|
|
|
|
await Promise.all([request, newUrl]);
|
|
|
|
await teamAccounts.openAccountsSelector();
|
|
|
|
await expect(teamAccounts.getTeamFromSelector(teamName)).toBeVisible();
|
|
});
|
|
|
|
test('cannot create a Team account using reserved names', async ({
|
|
page,
|
|
}) => {
|
|
const teamAccounts = new TeamAccountsPageObject(page);
|
|
await teamAccounts.setup();
|
|
|
|
await teamAccounts.openAccountsSelector();
|
|
await page.click('[data-test="create-team-account-trigger"]');
|
|
|
|
await teamAccounts.tryCreateTeam('billing');
|
|
|
|
await expect(
|
|
page.getByText('This name is reserved. Please choose a different one.'),
|
|
).toBeVisible();
|
|
|
|
await teamAccounts.tryCreateTeam('settings');
|
|
|
|
await expect(
|
|
page.getByText('This name is reserved. Please choose a different one.'),
|
|
).toBeVisible();
|
|
|
|
function expectError() {
|
|
return expect(
|
|
page.getByText(
|
|
'This name cannot contain special characters. Please choose a different one.',
|
|
),
|
|
).toBeVisible();
|
|
}
|
|
|
|
await teamAccounts.tryCreateTeam('Test-Name#');
|
|
await expectError();
|
|
|
|
await teamAccounts.tryCreateTeam('Test,Name');
|
|
await expectError();
|
|
|
|
await teamAccounts.tryCreateTeam('Test Name/')
|
|
await expectError();
|
|
|
|
await teamAccounts.tryCreateTeam('Test Name\\')
|
|
await expectError();
|
|
|
|
await teamAccounts.tryCreateTeam('Test Name:')
|
|
await expectError();
|
|
|
|
await teamAccounts.tryCreateTeam('Test Name;')
|
|
await expectError();
|
|
|
|
await teamAccounts.tryCreateTeam('Test Name=');
|
|
await expectError();
|
|
|
|
await teamAccounts.tryCreateTeam('Test Name>');
|
|
await expectError();
|
|
|
|
await teamAccounts.tryCreateTeam('Test Name<');
|
|
await expectError();
|
|
|
|
await teamAccounts.tryCreateTeam('Test Name?');
|
|
await expectError();
|
|
|
|
await teamAccounts.tryCreateTeam('Test Name@');
|
|
await expectError();
|
|
|
|
await teamAccounts.tryCreateTeam('Test Name^');
|
|
await expectError();
|
|
|
|
await teamAccounts.tryCreateTeam('Test Name&');
|
|
await expectError();
|
|
|
|
await teamAccounts.tryCreateTeam('Test Name*');
|
|
await expectError();
|
|
|
|
await teamAccounts.tryCreateTeam('Test Name(');
|
|
await expectError();
|
|
|
|
await teamAccounts.tryCreateTeam('Test Name)');
|
|
await expectError();
|
|
|
|
await teamAccounts.tryCreateTeam('Test Name+');
|
|
await expectError();
|
|
|
|
await teamAccounts.tryCreateTeam('Test Name%');
|
|
await expectError();
|
|
|
|
await teamAccounts.tryCreateTeam('Test Name$');
|
|
await expectError();
|
|
|
|
await teamAccounts.tryCreateTeam('Test Name[');
|
|
await expectError();
|
|
|
|
await teamAccounts.tryCreateTeam('Test Name]');
|
|
await expectError();
|
|
});
|
|
});
|
|
|
|
test.describe('Team Account Deletion', () => {
|
|
test('user can delete their team account', async ({ page }) => {
|
|
const teamAccounts = new TeamAccountsPageObject(page);
|
|
const params = teamAccounts.createTeamName();
|
|
|
|
const { email } = await teamAccounts.setup(params);
|
|
await teamAccounts.goToSettings();
|
|
|
|
await teamAccounts.deleteAccount(email);
|
|
await teamAccounts.openAccountsSelector();
|
|
|
|
await expect(
|
|
teamAccounts.getTeamFromSelector(params.teamName),
|
|
).not.toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe('Team Member Role Management', () => {
|
|
test("owner can update a team member's role", async ({ page }) => {
|
|
// Setup team with a regular member
|
|
const { teamAccounts, memberEmail } = await setupTeamWithMember(page);
|
|
|
|
// Get the current role badge text
|
|
const memberRow = page.getByRole('row', { name: memberEmail });
|
|
|
|
const initialRoleBadge = memberRow.locator(
|
|
'[data-test="member-role-badge"]',
|
|
);
|
|
|
|
await expect(initialRoleBadge).toHaveText('Member');
|
|
|
|
// Update the member's role to admin
|
|
await teamAccounts.updateMemberRole(memberEmail, 'owner');
|
|
|
|
// Wait for the page to fully load after the update
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Verify the role was updated successfully
|
|
const updatedRoleBadge = page
|
|
.getByRole('row', { name: memberEmail })
|
|
.locator('[data-test="member-role-badge"]');
|
|
await expect(updatedRoleBadge).toHaveText('Owner');
|
|
});
|
|
});
|
|
|
|
test.describe('Team Ownership Transfer', () => {
|
|
test('owner can transfer ownership to another team member', async ({
|
|
page,
|
|
}) => {
|
|
// Setup team with an owner member (required for ownership transfer)
|
|
const { teamAccounts, ownerEmail, memberEmail } = await setupTeamWithMember(
|
|
page,
|
|
'owner',
|
|
);
|
|
|
|
// Transfer ownership to the member
|
|
await teamAccounts.transferOwnership(memberEmail, ownerEmail);
|
|
|
|
// Wait for the page to fully load after the transfer
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Verify the transfer was successful by checking if the primary owner badge
|
|
// is now on the new owner's row
|
|
const memberRow = page.getByRole('row', { name: memberEmail });
|
|
|
|
// Check for the primary owner badge on the member's row
|
|
await expect(memberRow.locator('text=Primary Owner')).toBeVisible({
|
|
timeout: 5000,
|
|
});
|
|
|
|
// The original owner should no longer have the primary owner badge
|
|
const ownerRow = page.getByRole('row', { name: ownerEmail.split('@')[0] });
|
|
await expect(ownerRow.locator('text=Primary Owner')).not.toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe('Team Account Security', () => {
|
|
test('unauthorized user cannot access team account', async ({
|
|
page,
|
|
browser,
|
|
}) => {
|
|
// 1. Create a team account with User A
|
|
const teamAccounts = new TeamAccountsPageObject(page);
|
|
const params = teamAccounts.createTeamName();
|
|
|
|
// Setup User A and create team
|
|
await teamAccounts.setup(params);
|
|
|
|
// Store team slug for later use
|
|
const teamSlug = params.slug;
|
|
|
|
// 2. Sign out User A
|
|
await page.context().clearCookies();
|
|
|
|
// 3. Create a new context for User B (to have clean cookies/session)
|
|
const userBContext = await browser.newContext();
|
|
const userBPage = await userBContext.newPage();
|
|
const userBTeamAccounts = new TeamAccountsPageObject(userBPage);
|
|
|
|
// Sign up with User B
|
|
await userBPage.goto('/auth/sign-up');
|
|
const emailB = userBTeamAccounts.auth.createRandomEmail();
|
|
|
|
await userBTeamAccounts.auth.signUp({
|
|
email: emailB,
|
|
password: 'password',
|
|
repeatPassword: 'password',
|
|
});
|
|
|
|
await userBTeamAccounts.auth.visitConfirmEmailLink(emailB);
|
|
|
|
// 4. Attempt to access the team page with User B
|
|
await userBPage.goto(`/home/${teamSlug}`);
|
|
|
|
// Check that we're not on the team page anymore (should redirect)
|
|
await expect(userBPage).toHaveURL(`/home`);
|
|
});
|
|
});
|