mirror of
https://github.com/documenso/documenso.git
synced 2025-11-12 07:43:16 +10:00
Allows changing the owner of an organisation within the admin panel, useful for support requests to change ownership from a testing account to the main admin account. <img width="890" height="431" alt="image" src="https://github.com/user-attachments/assets/475bbbdd-0f26-4f74-aacf-3e793366551d" />
436 lines
14 KiB
TypeScript
436 lines
14 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
|
|
import { nanoid } from '@documenso/lib/universal/id';
|
|
import { seedOrganisationMembers } from '@documenso/prisma/seed/organisations';
|
|
import { seedUser } from '@documenso/prisma/seed/users';
|
|
|
|
import { apiSignin } from '../../fixtures/authentication';
|
|
|
|
test('[ADMIN]: promote member to owner', async ({ page }) => {
|
|
// Create an admin user who can access the admin panel
|
|
const { user: adminUser } = await seedUser({
|
|
isAdmin: true,
|
|
});
|
|
|
|
// Create an organisation owner
|
|
const { user: ownerUser, organisation } = await seedUser({
|
|
isPersonalOrganisation: false,
|
|
});
|
|
|
|
// Create organisation members with different roles
|
|
const memberEmail = `member-${nanoid()}@test.documenso.com`;
|
|
const managerEmail = `manager-${nanoid()}@test.documenso.com`;
|
|
const adminMemberEmail = `admin-member-${nanoid()}@test.documenso.com`;
|
|
|
|
const [memberUser, managerUser, adminMemberUser] = await seedOrganisationMembers({
|
|
members: [
|
|
{
|
|
email: memberEmail,
|
|
name: 'Test Member',
|
|
organisationRole: 'MEMBER',
|
|
},
|
|
{
|
|
email: managerEmail,
|
|
name: 'Test Manager',
|
|
organisationRole: 'MANAGER',
|
|
},
|
|
{
|
|
email: adminMemberEmail,
|
|
name: 'Test Admin Member',
|
|
organisationRole: 'ADMIN',
|
|
},
|
|
],
|
|
organisationId: organisation.id,
|
|
});
|
|
|
|
// Sign in as admin and navigate to the organisation admin page
|
|
await apiSignin({
|
|
page,
|
|
email: adminUser.email,
|
|
redirectPath: `/admin/organisations/${organisation.id}`,
|
|
});
|
|
|
|
// Verify we're on the admin organisation page
|
|
await expect(page.getByText(`Manage organisation`)).toBeVisible();
|
|
|
|
await expect(page.getByLabel('Organisation Name')).toHaveValue(organisation.name);
|
|
|
|
// Check that the organisation members table shows the correct roles
|
|
const ownerRow = page.getByRole('row', { name: ownerUser.email });
|
|
|
|
await expect(ownerRow).toBeVisible();
|
|
await expect(ownerRow.getByRole('status').filter({ hasText: 'Owner' })).toBeVisible();
|
|
|
|
await expect(page.getByRole('row', { name: memberUser.email })).toBeVisible();
|
|
await expect(page.getByRole('row', { name: adminMemberUser.email })).toBeVisible();
|
|
await expect(page.getByRole('row', { name: managerUser.email })).toBeVisible();
|
|
|
|
// Test promoting a MEMBER to owner
|
|
const memberRow = page.getByRole('row', { name: memberUser.email });
|
|
|
|
// Find and click the "Promote to owner" button for the member
|
|
const promoteButton = memberRow.getByRole('button', { name: 'Promote to owner' });
|
|
await expect(promoteButton).toBeVisible();
|
|
await expect(promoteButton).not.toBeDisabled();
|
|
|
|
await promoteButton.click();
|
|
|
|
// Verify success toast appears
|
|
await expect(page.getByText('Member promoted to owner successfully').first()).toBeVisible();
|
|
|
|
// Reload the page to see the changes
|
|
await page.reload();
|
|
|
|
// Verify that the member is now the owner
|
|
const newOwnerRow = page.getByRole('row', { name: memberUser.email });
|
|
await expect(newOwnerRow.getByRole('status').filter({ hasText: 'Owner' })).toBeVisible();
|
|
|
|
// Verify that the previous owner is no longer marked as owner
|
|
const previousOwnerRow = page.getByRole('row', { name: ownerUser.email });
|
|
await expect(previousOwnerRow.getByRole('status').filter({ hasText: 'Owner' })).not.toBeVisible();
|
|
|
|
// Verify that the promote button is now disabled for the new owner
|
|
const newOwnerPromoteButton = newOwnerRow.getByRole('button', { name: 'Promote to owner' });
|
|
await expect(newOwnerPromoteButton).toBeDisabled();
|
|
|
|
// Test that we can't promote the current owner (button should be disabled)
|
|
await expect(newOwnerPromoteButton).toHaveAttribute('disabled');
|
|
});
|
|
|
|
test('[ADMIN]: promote manager to owner', async ({ page }) => {
|
|
// Create an admin user who can access the admin panel
|
|
const { user: adminUser } = await seedUser({
|
|
isAdmin: true,
|
|
});
|
|
|
|
// Create an organisation with owner and manager
|
|
const { organisation } = await seedUser({
|
|
isPersonalOrganisation: false,
|
|
});
|
|
|
|
const managerEmail = `manager-${nanoid()}@test.documenso.com`;
|
|
|
|
const [managerUser] = await seedOrganisationMembers({
|
|
members: [
|
|
{
|
|
email: managerEmail,
|
|
name: 'Test Manager',
|
|
organisationRole: 'MANAGER',
|
|
},
|
|
],
|
|
organisationId: organisation.id,
|
|
});
|
|
|
|
// Sign in as admin
|
|
await apiSignin({
|
|
page,
|
|
email: adminUser.email,
|
|
redirectPath: `/admin/organisations/${organisation.id}`,
|
|
});
|
|
|
|
// Promote the manager to owner
|
|
const managerRow = page.getByRole('row', { name: managerUser.email });
|
|
const promoteButton = managerRow.getByRole('button', { name: 'Promote to owner' });
|
|
|
|
await promoteButton.click();
|
|
await expect(page.getByText('Member promoted to owner successfully').first()).toBeVisible();
|
|
|
|
// Reload and verify the change
|
|
await page.reload();
|
|
await expect(managerRow.getByRole('status').filter({ hasText: 'Owner' })).toBeVisible();
|
|
});
|
|
|
|
test('[ADMIN]: promote admin member to owner', async ({ page }) => {
|
|
// Create an admin user who can access the admin panel
|
|
const { user: adminUser } = await seedUser({
|
|
isAdmin: true,
|
|
});
|
|
|
|
// Create an organisation with owner and admin member
|
|
const { organisation } = await seedUser({
|
|
isPersonalOrganisation: false,
|
|
});
|
|
|
|
const adminMemberEmail = `admin-member-${nanoid()}@test.documenso.com`;
|
|
|
|
const [adminMemberUser] = await seedOrganisationMembers({
|
|
members: [
|
|
{
|
|
email: adminMemberEmail,
|
|
name: 'Test Admin Member',
|
|
organisationRole: 'ADMIN',
|
|
},
|
|
],
|
|
organisationId: organisation.id,
|
|
});
|
|
|
|
// Sign in as admin
|
|
await apiSignin({
|
|
page,
|
|
email: adminUser.email,
|
|
redirectPath: `/admin/organisations/${organisation.id}`,
|
|
});
|
|
|
|
// Promote the admin member to owner
|
|
const adminMemberRow = page.getByRole('row', { name: adminMemberUser.email });
|
|
const promoteButton = adminMemberRow.getByRole('button', { name: 'Promote to owner' });
|
|
|
|
await promoteButton.click();
|
|
|
|
await expect(page.getByText('Member promoted to owner successfully').first()).toBeVisible({
|
|
timeout: 10_000,
|
|
});
|
|
|
|
// Reload and verify the change
|
|
await page.reload();
|
|
await expect(adminMemberRow.getByRole('status').filter({ hasText: 'Owner' })).toBeVisible();
|
|
});
|
|
|
|
test('[ADMIN]: cannot promote non-existent user', async ({ page }) => {
|
|
// Create an admin user
|
|
const { user: adminUser } = await seedUser({
|
|
isAdmin: true,
|
|
});
|
|
|
|
// Create an organisation
|
|
const { organisation } = await seedUser({
|
|
isPersonalOrganisation: false,
|
|
});
|
|
|
|
// Sign in as admin
|
|
await apiSignin({
|
|
page,
|
|
email: adminUser.email,
|
|
redirectPath: `/admin/organisations/${organisation.id}`,
|
|
});
|
|
|
|
// Try to manually call the API with invalid data - this should be handled by the UI validation
|
|
// In a real scenario, the promote button wouldn't be available for non-existent users
|
|
// But we can test that the API properly handles invalid requests
|
|
|
|
// For now, just verify that non-existent users don't show up in the members table
|
|
await expect(page.getByRole('row', { name: 'Non Existent User' })).not.toBeVisible();
|
|
});
|
|
|
|
test('[ADMIN]: verify role hierarchy after promotion', async ({ page }) => {
|
|
// Create an admin user
|
|
const { user: adminUser } = await seedUser({
|
|
isAdmin: true,
|
|
});
|
|
|
|
// Create organisation with a member
|
|
const { organisation } = await seedUser({
|
|
isPersonalOrganisation: false,
|
|
});
|
|
|
|
const memberEmail = `member-${nanoid()}@test.documenso.com`;
|
|
|
|
const [memberUser] = await seedOrganisationMembers({
|
|
members: [
|
|
{
|
|
email: memberEmail,
|
|
name: 'Test Member',
|
|
organisationRole: 'MEMBER',
|
|
},
|
|
],
|
|
organisationId: organisation.id,
|
|
});
|
|
|
|
// Sign in as admin
|
|
await apiSignin({
|
|
page,
|
|
email: adminUser.email,
|
|
redirectPath: `/admin/organisations/${organisation.id}`,
|
|
});
|
|
|
|
// Before promotion - verify member has MEMBER role
|
|
let memberRow = page.getByRole('row', { name: memberUser.email });
|
|
await expect(memberRow).toBeVisible();
|
|
await expect(memberRow.getByRole('status').filter({ hasText: 'Owner' })).not.toBeVisible();
|
|
|
|
// Promote member to owner
|
|
const promoteButton = memberRow.getByRole('button', { name: 'Promote to owner' });
|
|
await promoteButton.click();
|
|
await expect(page.getByText('Member promoted to owner successfully').first()).toBeVisible({
|
|
timeout: 10_000,
|
|
});
|
|
|
|
// Reload page to see updated state
|
|
await page.reload();
|
|
|
|
// After promotion - verify member is now owner and has admin permissions
|
|
memberRow = page.getByRole('row', { name: memberUser.email });
|
|
await expect(memberRow.getByRole('status').filter({ hasText: 'Owner' })).toBeVisible();
|
|
|
|
// Verify the promote button is now disabled for the new owner
|
|
const newOwnerPromoteButton = memberRow.getByRole('button', { name: 'Promote to owner' });
|
|
await expect(newOwnerPromoteButton).toBeDisabled();
|
|
|
|
// Sign in as the newly promoted user to verify they have owner permissions
|
|
await apiSignin({
|
|
page,
|
|
email: memberUser.email,
|
|
redirectPath: `/o/${organisation.url}/settings/general`,
|
|
});
|
|
|
|
// Verify they can access organisation settings (owner permission)
|
|
await expect(page.getByText('Organisation Settings')).toBeVisible();
|
|
await expect(page.getByRole('button', { name: 'Delete' })).toBeVisible();
|
|
});
|
|
|
|
test('[ADMIN]: error handling for invalid organisation', async ({ page }) => {
|
|
// Create an admin user
|
|
const { user: adminUser } = await seedUser({
|
|
isAdmin: true,
|
|
});
|
|
|
|
// Sign in as admin and try to access non-existent organisation
|
|
await apiSignin({
|
|
page,
|
|
email: adminUser.email,
|
|
redirectPath: `/admin/organisations/non-existent-org-id`,
|
|
});
|
|
|
|
// Should show 404 error
|
|
await expect(page.getByRole('heading', { name: 'Organisation not found' })).toBeVisible({
|
|
timeout: 10_000,
|
|
});
|
|
});
|
|
|
|
test('[ADMIN]: multiple promotions in sequence', async ({ page }) => {
|
|
// Create an admin user
|
|
const { user: adminUser } = await seedUser({
|
|
isAdmin: true,
|
|
});
|
|
|
|
// Create organisation with multiple members
|
|
const { organisation } = await seedUser({
|
|
isPersonalOrganisation: false,
|
|
});
|
|
|
|
const member1Email = `member1-${nanoid()}@test.documenso.com`;
|
|
const member2Email = `member2-${nanoid()}@test.documenso.com`;
|
|
|
|
const [member1User, member2User] = await seedOrganisationMembers({
|
|
members: [
|
|
{
|
|
email: member1Email,
|
|
name: 'Test Member 1',
|
|
organisationRole: 'MEMBER',
|
|
},
|
|
{
|
|
email: member2Email,
|
|
name: 'Test Member 2',
|
|
organisationRole: 'MANAGER',
|
|
},
|
|
],
|
|
organisationId: organisation.id,
|
|
});
|
|
|
|
// Sign in as admin
|
|
await apiSignin({
|
|
page,
|
|
email: adminUser.email,
|
|
redirectPath: `/admin/organisations/${organisation.id}`,
|
|
});
|
|
|
|
// First promotion: Member 1 becomes owner
|
|
let member1Row = page.getByRole('row', { name: member1User.email });
|
|
let promoteButton1 = member1Row.getByRole('button', { name: 'Promote to owner' });
|
|
await promoteButton1.click();
|
|
await expect(page.getByText('Member promoted to owner successfully').first()).toBeVisible({
|
|
timeout: 10_000,
|
|
});
|
|
|
|
await page.reload();
|
|
|
|
// Verify Member 1 is now owner and button is disabled
|
|
member1Row = page.getByRole('row', { name: member1User.email });
|
|
await expect(member1Row.getByRole('status').filter({ hasText: 'Owner' })).toBeVisible();
|
|
promoteButton1 = member1Row.getByRole('button', { name: 'Promote to owner' });
|
|
await expect(promoteButton1).toBeDisabled();
|
|
|
|
// Second promotion: Member 2 becomes the new owner
|
|
const member2Row = page.getByRole('row', { name: member2User.email });
|
|
const promoteButton2 = member2Row.getByRole('button', { name: 'Promote to owner' });
|
|
await expect(promoteButton2).not.toBeDisabled();
|
|
await promoteButton2.click();
|
|
await expect(page.getByText('Member promoted to owner successfully').first()).toBeVisible({
|
|
timeout: 10_000,
|
|
});
|
|
|
|
await page.reload();
|
|
|
|
// Verify Member 2 is now owner and Member 1 is no longer owner
|
|
await expect(member2Row.getByRole('status').filter({ hasText: 'Owner' })).toBeVisible();
|
|
await expect(member1Row.getByRole('status').filter({ hasText: 'Owner' })).not.toBeVisible();
|
|
|
|
// Verify Member 1's promote button is now enabled again
|
|
const newPromoteButton1 = member1Row.getByRole('button', { name: 'Promote to owner' });
|
|
await expect(newPromoteButton1).not.toBeDisabled();
|
|
});
|
|
|
|
test('[ADMIN]: verify organisation access after ownership change', async ({ page }) => {
|
|
// Create admin user
|
|
const { user: adminUser } = await seedUser({
|
|
isAdmin: true,
|
|
});
|
|
|
|
// Create organisation with owner and member
|
|
const { user: originalOwner, organisation } = await seedUser({
|
|
isPersonalOrganisation: false,
|
|
});
|
|
|
|
const memberEmail = `member-${nanoid()}@test.documenso.com`;
|
|
|
|
const [memberUser] = await seedOrganisationMembers({
|
|
members: [
|
|
{
|
|
email: memberEmail,
|
|
name: 'Test Member',
|
|
organisationRole: 'MEMBER',
|
|
},
|
|
],
|
|
organisationId: organisation.id,
|
|
});
|
|
|
|
// Sign in as admin and promote member to owner
|
|
await apiSignin({
|
|
page,
|
|
email: adminUser.email,
|
|
redirectPath: `/admin/organisations/${organisation.id}`,
|
|
});
|
|
|
|
const memberRow = page.getByRole('row', { name: memberUser.email });
|
|
const promoteButton = memberRow.getByRole('button', { name: 'Promote to owner' });
|
|
await promoteButton.click();
|
|
await expect(page.getByText('Member promoted to owner successfully').first()).toBeVisible({
|
|
timeout: 10_000,
|
|
});
|
|
|
|
// Test that the new owner can access organisation settings
|
|
await apiSignin({
|
|
page,
|
|
email: memberUser.email,
|
|
redirectPath: `/o/${organisation.url}/settings/general`,
|
|
});
|
|
|
|
// Should be able to access organisation settings
|
|
await expect(page.getByText('Organisation Settings')).toBeVisible();
|
|
await expect(page.getByLabel('Organisation Name*')).toBeVisible();
|
|
await expect(page.getByRole('button', { name: 'Update organisation' })).toBeVisible();
|
|
|
|
// Should have delete permissions
|
|
await expect(page.getByRole('button', { name: 'Delete' })).toBeVisible();
|
|
|
|
// Test that the original owner no longer has owner-level access
|
|
await apiSignin({
|
|
page,
|
|
email: originalOwner.email,
|
|
redirectPath: `/o/${organisation.url}/settings/general`,
|
|
});
|
|
|
|
// Should still be able to access settings (as they should now be an admin)
|
|
await expect(page.getByText('Organisation Settings')).toBeVisible();
|
|
});
|