mirror of
https://github.com/documenso/documenso.git
synced 2026-06-22 04:12:06 +10:00
575 lines
23 KiB
TypeScript
575 lines
23 KiB
TypeScript
import { prisma } from '@documenso/prisma';
|
|
import { BackgroundJobStatus, DocumentStatus, EnvelopeType, Role } from '@documenso/prisma/client';
|
|
import { seedBlankDocument } from '@documenso/prisma/seed/documents';
|
|
import { seedOrganisationMembers } from '@documenso/prisma/seed/organisations';
|
|
import { seedUser } from '@documenso/prisma/seed/users';
|
|
import { expect, test } from '@playwright/test';
|
|
|
|
import { apiSignin, apiSignout } from '../../fixtures/authentication';
|
|
|
|
/**
|
|
* Helper that polls until the `admin.organisation.delete` background job for the
|
|
* supplied organisation has finished (status COMPLETED). Returns the org id.
|
|
*/
|
|
const waitForOrganisationDeletionJob = async (organisationId: string) => {
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
const job = await prisma.backgroundJob.findFirst({
|
|
where: {
|
|
jobId: 'internal.admin-delete-organisation',
|
|
// payload is JSON; match the organisationId field.
|
|
payload: {
|
|
path: ['organisationId'],
|
|
equals: organisationId,
|
|
},
|
|
},
|
|
orderBy: { submittedAt: 'desc' },
|
|
});
|
|
|
|
return job?.status ?? null;
|
|
},
|
|
{
|
|
message: `Background deletion job for organisation ${organisationId} did not complete in time`,
|
|
timeout: 30_000,
|
|
intervals: [250, 500, 1000],
|
|
},
|
|
)
|
|
.toBe(BackgroundJobStatus.COMPLETED);
|
|
};
|
|
|
|
const waitForOrganisationToBeGone = async (organisationId: string) => {
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
const org = await prisma.organisation.findUnique({
|
|
where: { id: organisationId },
|
|
select: { id: true },
|
|
});
|
|
|
|
return org === null;
|
|
},
|
|
{
|
|
message: `Organisation ${organisationId} was not removed`,
|
|
timeout: 30_000,
|
|
intervals: [250, 500, 1000],
|
|
},
|
|
)
|
|
.toBe(true);
|
|
};
|
|
|
|
test.describe.configure({ mode: 'parallel' });
|
|
|
|
// ─── Happy path ──────────────────────────────────────────────────────────────
|
|
|
|
test('[ADMIN][DELETE_ORG]: admin can delete an organisation via the dialog', async ({ page }) => {
|
|
const { user: adminUser } = await seedUser({ isAdmin: true });
|
|
const { organisation } = await seedUser({ isPersonalOrganisation: false });
|
|
|
|
await apiSignin({
|
|
page,
|
|
email: adminUser.email,
|
|
redirectPath: `/admin/organisations/${organisation.id}`,
|
|
});
|
|
|
|
await expect(page.getByRole('heading', { name: 'Danger Zone' })).toBeVisible();
|
|
|
|
// Open the dialog
|
|
await page.getByRole('button', { name: 'Delete' }).first().click();
|
|
|
|
const dialog = page.getByRole('dialog');
|
|
await expect(dialog).toBeVisible();
|
|
|
|
// The Delete submit button is initially enabled but submission should fail
|
|
// until the confirmation text matches. Type it now.
|
|
await dialog.getByRole('textbox').fill(`delete ${organisation.name}`);
|
|
|
|
// The "send email to owner" checkbox should be checked by default.
|
|
const emailCheckbox = dialog.getByRole('checkbox');
|
|
await expect(emailCheckbox).toBeChecked();
|
|
|
|
await dialog.getByRole('button', { name: 'Delete', exact: true }).click();
|
|
|
|
// Dialog closes on success
|
|
await expect(dialog).not.toBeVisible({ timeout: 10_000 });
|
|
|
|
// Background job completes and the org is removed
|
|
await waitForOrganisationDeletionJob(organisation.id);
|
|
await waitForOrganisationToBeGone(organisation.id);
|
|
});
|
|
|
|
// ─── Confirmation text validation ────────────────────────────────────────────
|
|
|
|
test('[ADMIN][DELETE_ORG]: typing the wrong confirmation text prevents deletion', async ({ page }) => {
|
|
const { user: adminUser } = await seedUser({ isAdmin: true });
|
|
const { organisation } = await seedUser({ isPersonalOrganisation: false });
|
|
|
|
await apiSignin({
|
|
page,
|
|
email: adminUser.email,
|
|
redirectPath: `/admin/organisations/${organisation.id}`,
|
|
});
|
|
|
|
await page.getByRole('button', { name: 'Delete' }).first().click();
|
|
|
|
const dialog = page.getByRole('dialog');
|
|
await expect(dialog).toBeVisible();
|
|
|
|
// Type something that does NOT match.
|
|
await dialog.getByRole('textbox').fill('delete wrong-name');
|
|
|
|
await dialog.getByRole('button', { name: 'Delete', exact: true }).click();
|
|
|
|
// Validation message should appear and the dialog should stay open.
|
|
await expect(dialog.getByText(/You must enter/)).toBeVisible();
|
|
await expect(dialog).toBeVisible();
|
|
|
|
// Org is still there.
|
|
const stillExists = await prisma.organisation.findUnique({ where: { id: organisation.id } });
|
|
expect(stillExists).not.toBeNull();
|
|
});
|
|
|
|
test('[ADMIN][DELETE_ORG]: empty confirmation text prevents deletion', async ({ page }) => {
|
|
const { user: adminUser } = await seedUser({ isAdmin: true });
|
|
const { organisation } = await seedUser({ isPersonalOrganisation: false });
|
|
|
|
await apiSignin({
|
|
page,
|
|
email: adminUser.email,
|
|
redirectPath: `/admin/organisations/${organisation.id}`,
|
|
});
|
|
|
|
await page.getByRole('button', { name: 'Delete' }).first().click();
|
|
|
|
const dialog = page.getByRole('dialog');
|
|
await dialog.getByRole('button', { name: 'Delete', exact: true }).click();
|
|
|
|
await expect(dialog.getByText(/You must enter/)).toBeVisible();
|
|
await expect(dialog).toBeVisible();
|
|
|
|
const stillExists = await prisma.organisation.findUnique({ where: { id: organisation.id } });
|
|
expect(stillExists).not.toBeNull();
|
|
});
|
|
|
|
// ─── Cancel ──────────────────────────────────────────────────────────────────
|
|
|
|
test('[ADMIN][DELETE_ORG]: clicking Cancel closes the dialog without deleting', async ({ page }) => {
|
|
const { user: adminUser } = await seedUser({ isAdmin: true });
|
|
const { organisation } = await seedUser({ isPersonalOrganisation: false });
|
|
|
|
await apiSignin({
|
|
page,
|
|
email: adminUser.email,
|
|
redirectPath: `/admin/organisations/${organisation.id}`,
|
|
});
|
|
|
|
await page.getByRole('button', { name: 'Delete' }).first().click();
|
|
|
|
const dialog = page.getByRole('dialog');
|
|
await expect(dialog).toBeVisible();
|
|
|
|
// Fill in the correct text but cancel anyway.
|
|
await dialog.getByRole('textbox').fill(`delete ${organisation.name}`);
|
|
await dialog.getByRole('button', { name: 'Cancel' }).click();
|
|
|
|
await expect(dialog).not.toBeVisible();
|
|
|
|
// Org still there.
|
|
const stillExists = await prisma.organisation.findUnique({ where: { id: organisation.id } });
|
|
expect(stillExists).not.toBeNull();
|
|
});
|
|
|
|
// ─── Email checkbox ──────────────────────────────────────────────────────────
|
|
|
|
test('[ADMIN][DELETE_ORG]: email checkbox can be unchecked, payload reflects choice', async ({ page }) => {
|
|
const { user: adminUser } = await seedUser({ isAdmin: true });
|
|
const { organisation } = await seedUser({ isPersonalOrganisation: false });
|
|
|
|
await apiSignin({
|
|
page,
|
|
email: adminUser.email,
|
|
redirectPath: `/admin/organisations/${organisation.id}`,
|
|
});
|
|
|
|
await page.getByRole('button', { name: 'Delete' }).first().click();
|
|
|
|
const dialog = page.getByRole('dialog');
|
|
const emailCheckbox = dialog.getByRole('checkbox');
|
|
|
|
// Default is checked.
|
|
await expect(emailCheckbox).toBeChecked();
|
|
|
|
// Uncheck it.
|
|
await emailCheckbox.click();
|
|
await expect(emailCheckbox).not.toBeChecked();
|
|
|
|
await dialog.getByRole('textbox').fill(`delete ${organisation.name}`);
|
|
await dialog.getByRole('button', { name: 'Delete', exact: true }).click();
|
|
|
|
await expect(dialog).not.toBeVisible({ timeout: 10_000 });
|
|
|
|
// Verify the enqueued job payload has sendEmailToOwner=false.
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
const job = await prisma.backgroundJob.findFirst({
|
|
where: {
|
|
jobId: 'internal.admin-delete-organisation',
|
|
payload: { path: ['organisationId'], equals: organisation.id },
|
|
},
|
|
});
|
|
|
|
if (!job) {
|
|
return null;
|
|
}
|
|
|
|
const payload = job.payload as { sendEmailToOwner?: boolean };
|
|
return payload.sendEmailToOwner;
|
|
},
|
|
{ timeout: 15_000 },
|
|
)
|
|
.toBe(false);
|
|
|
|
await waitForOrganisationDeletionJob(organisation.id);
|
|
await waitForOrganisationToBeGone(organisation.id);
|
|
});
|
|
|
|
// ─── Documents are orphaned, not deleted ─────────────────────────────────────
|
|
|
|
test('[ADMIN][DELETE_ORG]: envelopes authored by owner and members are orphaned, drafts are removed', async ({
|
|
page,
|
|
}) => {
|
|
const { user: adminUser } = await seedUser({ isAdmin: true });
|
|
const { user: owner, organisation, team } = await seedUser({ isPersonalOrganisation: false });
|
|
|
|
// Add two organisation members who will author their own envelopes.
|
|
const [memberUser, managerUser] = await seedOrganisationMembers({
|
|
organisationId: organisation.id,
|
|
members: [{ organisationRole: 'MEMBER' }, { organisationRole: 'MANAGER' }],
|
|
});
|
|
|
|
// ── Owner-authored envelopes ──────────────────────────────────────────────
|
|
const ownerCompleted = await seedBlankDocument(owner, team.id, { key: 'owner-completed' });
|
|
await prisma.envelope.update({
|
|
where: { id: ownerCompleted.id },
|
|
data: { status: DocumentStatus.COMPLETED },
|
|
});
|
|
|
|
const ownerPending = await seedBlankDocument(owner, team.id, { key: 'owner-pending' });
|
|
await prisma.envelope.update({
|
|
where: { id: ownerPending.id },
|
|
data: { status: DocumentStatus.PENDING },
|
|
});
|
|
|
|
const ownerDraft = await seedBlankDocument(owner, team.id, { key: 'owner-draft' });
|
|
|
|
// ── Member-authored envelopes ─────────────────────────────────────────────
|
|
const memberCompleted = await seedBlankDocument(memberUser, team.id, { key: 'member-completed' });
|
|
await prisma.envelope.update({
|
|
where: { id: memberCompleted.id },
|
|
data: { status: DocumentStatus.COMPLETED },
|
|
});
|
|
|
|
const memberPending = await seedBlankDocument(memberUser, team.id, { key: 'member-pending' });
|
|
await prisma.envelope.update({
|
|
where: { id: memberPending.id },
|
|
data: { status: DocumentStatus.PENDING },
|
|
});
|
|
|
|
const memberDraft = await seedBlankDocument(memberUser, team.id, { key: 'member-draft' });
|
|
|
|
// ── Manager-authored envelope (third author for good measure) ─────────────
|
|
const managerRejected = await seedBlankDocument(managerUser, team.id, { key: 'manager-rejected' });
|
|
await prisma.envelope.update({
|
|
where: { id: managerRejected.id },
|
|
data: { status: DocumentStatus.REJECTED },
|
|
});
|
|
|
|
// Sanity check: before deletion all 7 envelopes belong to the team and
|
|
// retain their original authors.
|
|
const beforeCount = await prisma.envelope.count({ where: { teamId: team.id } });
|
|
expect(beforeCount).toBe(7);
|
|
|
|
// ── Trigger the deletion via the admin UI ─────────────────────────────────
|
|
await apiSignin({
|
|
page,
|
|
email: adminUser.email,
|
|
redirectPath: `/admin/organisations/${organisation.id}`,
|
|
});
|
|
|
|
await page.getByRole('button', { name: 'Delete' }).first().click();
|
|
const dialog = page.getByRole('dialog');
|
|
await dialog.getByRole('textbox').fill(`delete ${organisation.name}`);
|
|
await dialog.getByRole('button', { name: 'Delete', exact: true }).click();
|
|
await expect(dialog).not.toBeVisible({ timeout: 10_000 });
|
|
|
|
await waitForOrganisationDeletionJob(organisation.id);
|
|
await waitForOrganisationToBeGone(organisation.id);
|
|
|
|
// The deleted-account service account is where orphaned envelopes land.
|
|
const deletedAccount = await prisma.user.findFirstOrThrow({
|
|
where: { email: { startsWith: 'deleted-account@' } },
|
|
select: { id: true, ownedOrganisations: { select: { teams: { select: { id: true } } } } },
|
|
});
|
|
const deletedAccountTeamId = deletedAccount.ownedOrganisations[0].teams[0].id;
|
|
|
|
// ── Owner-authored envelopes ──────────────────────────────────────────────
|
|
// Completed/pending: orphaned (reparented to service account + deletedAt set).
|
|
for (const original of [ownerCompleted, ownerPending]) {
|
|
const after = await prisma.envelope.findUnique({
|
|
where: { id: original.id },
|
|
select: { id: true, teamId: true, userId: true, deletedAt: true },
|
|
});
|
|
expect(after, `owner envelope ${original.id} should survive as orphan`).not.toBeNull();
|
|
expect(after?.teamId).toBe(deletedAccountTeamId);
|
|
expect(after?.userId).toBe(deletedAccount.id);
|
|
expect(after?.deletedAt).not.toBeNull();
|
|
}
|
|
|
|
// Draft: hard-deleted because orphan only re-parents PENDING/REJECTED/COMPLETED.
|
|
const ownerDraftAfter = await prisma.envelope.findUnique({
|
|
where: { id: ownerDraft.id },
|
|
select: { id: true },
|
|
});
|
|
expect(ownerDraftAfter, 'owner draft should be hard-deleted').toBeNull();
|
|
|
|
// ── Member-authored envelopes (the critical case) ─────────────────────────
|
|
// The orphan logic filters by teamId only — NOT by userId — so member-authored
|
|
// envelopes must be orphaned just like the owner's.
|
|
for (const original of [memberCompleted, memberPending]) {
|
|
const after = await prisma.envelope.findUnique({
|
|
where: { id: original.id },
|
|
select: { id: true, teamId: true, userId: true, deletedAt: true },
|
|
});
|
|
expect(after, `member envelope ${original.id} should survive as orphan`).not.toBeNull();
|
|
expect(after?.teamId).toBe(deletedAccountTeamId);
|
|
expect(after?.userId).toBe(deletedAccount.id);
|
|
expect(after?.deletedAt).not.toBeNull();
|
|
}
|
|
|
|
const memberDraftAfter = await prisma.envelope.findUnique({
|
|
where: { id: memberDraft.id },
|
|
select: { id: true },
|
|
});
|
|
expect(memberDraftAfter, 'member draft should be hard-deleted').toBeNull();
|
|
|
|
// ── Manager-authored rejected envelope: also orphaned ─────────────────────
|
|
const managerRejectedAfter = await prisma.envelope.findUnique({
|
|
where: { id: managerRejected.id },
|
|
select: { id: true, teamId: true, userId: true, deletedAt: true },
|
|
});
|
|
expect(managerRejectedAfter).not.toBeNull();
|
|
expect(managerRejectedAfter?.teamId).toBe(deletedAccountTeamId);
|
|
expect(managerRejectedAfter?.userId).toBe(deletedAccount.id);
|
|
|
|
// ── Original team is gone, member users still exist ───────────────────────
|
|
const teamAfter = await prisma.team.findUnique({ where: { id: team.id } });
|
|
expect(teamAfter).toBeNull();
|
|
|
|
// No envelope should reference the now-deleted team.
|
|
const orphanedToOldTeam = await prisma.envelope.count({ where: { teamId: team.id } });
|
|
expect(orphanedToOldTeam).toBe(0);
|
|
|
|
// The owner and members survive — only the org is deleted, not the users.
|
|
const ownerAfter = await prisma.user.findUnique({ where: { id: owner.id } });
|
|
const memberAfter = await prisma.user.findUnique({ where: { id: memberUser.id } });
|
|
const managerAfter = await prisma.user.findUnique({ where: { id: managerUser.id } });
|
|
expect(ownerAfter).not.toBeNull();
|
|
expect(memberAfter).not.toBeNull();
|
|
expect(managerAfter).not.toBeNull();
|
|
});
|
|
|
|
// ─── Owner can no longer access the deleted organisation ─────────────────────
|
|
|
|
test('[ADMIN][DELETE_ORG]: the original owner loses access after deletion', async ({ page }) => {
|
|
const { user: adminUser } = await seedUser({ isAdmin: true });
|
|
const { user: owner, organisation } = await seedUser({ isPersonalOrganisation: false });
|
|
|
|
await apiSignin({
|
|
page,
|
|
email: adminUser.email,
|
|
redirectPath: `/admin/organisations/${organisation.id}`,
|
|
});
|
|
|
|
await page.getByRole('button', { name: 'Delete' }).first().click();
|
|
const dialog = page.getByRole('dialog');
|
|
await dialog.getByRole('textbox').fill(`delete ${organisation.name}`);
|
|
await dialog.getByRole('button', { name: 'Delete', exact: true }).click();
|
|
await expect(dialog).not.toBeVisible({ timeout: 10_000 });
|
|
|
|
await waitForOrganisationDeletionJob(organisation.id);
|
|
await waitForOrganisationToBeGone(organisation.id);
|
|
|
|
// Sign in as the original owner and confirm they can no longer reach the
|
|
// organisation settings page.
|
|
await apiSignout({ page });
|
|
|
|
await apiSignin({
|
|
page,
|
|
email: owner.email,
|
|
redirectPath: `/o/${organisation.url}/settings/general`,
|
|
});
|
|
|
|
// They should NOT see the organisation settings heading for this org.
|
|
await expect(page.getByText('Organisation Settings')).not.toBeVisible();
|
|
});
|
|
|
|
// ─── Access control: UI ──────────────────────────────────────────────────────
|
|
|
|
test('[ADMIN][DELETE_ORG]: non-admin user cannot access /admin/organisations/$id', async ({ page }) => {
|
|
const { user: nonAdminUser } = await seedUser({ isAdmin: false });
|
|
const { organisation } = await seedUser({ isPersonalOrganisation: false });
|
|
|
|
await apiSignin({
|
|
page,
|
|
email: nonAdminUser.email,
|
|
redirectPath: `/admin/organisations/${organisation.id}`,
|
|
});
|
|
|
|
// The admin layout loader redirects non-admins to "/". They must not see the
|
|
// admin panel or any Delete affordance.
|
|
await expect(page.getByRole('heading', { name: 'Admin Panel' })).not.toBeVisible();
|
|
await expect(page.getByRole('heading', { name: 'Danger Zone' })).not.toBeVisible();
|
|
await expect(page.getByRole('button', { name: 'Delete' })).not.toBeVisible();
|
|
|
|
// The org must still exist.
|
|
const stillExists = await prisma.organisation.findUnique({ where: { id: organisation.id } });
|
|
expect(stillExists).not.toBeNull();
|
|
});
|
|
|
|
test('[ADMIN][DELETE_ORG]: unauthenticated user cannot access /admin/organisations/$id', async ({ page }) => {
|
|
const { organisation } = await seedUser({ isPersonalOrganisation: false });
|
|
|
|
// No apiSignin call. Navigate directly.
|
|
await page.goto(`/admin/organisations/${organisation.id}`);
|
|
|
|
// Unauthenticated requests should be redirected away from any /admin/* route.
|
|
await expect(page).not.toHaveURL(new RegExp(`/admin/organisations/${organisation.id}`));
|
|
await expect(page.getByRole('heading', { name: 'Danger Zone' })).not.toBeVisible();
|
|
|
|
const stillExists = await prisma.organisation.findUnique({ where: { id: organisation.id } });
|
|
expect(stillExists).not.toBeNull();
|
|
});
|
|
|
|
// ─── Belt-and-braces: organisation owner (without admin role) can't use it ──
|
|
|
|
test('[ADMIN][DELETE_ORG]: an organisation owner without admin role cannot reach the admin delete UI', async ({
|
|
page,
|
|
}) => {
|
|
const { user: owner, organisation } = await seedUser({ isPersonalOrganisation: false });
|
|
|
|
// Confirm the owner is NOT an admin (sanity check on the seed).
|
|
expect(owner.roles).not.toContain(Role.ADMIN);
|
|
|
|
await apiSignin({
|
|
page,
|
|
email: owner.email,
|
|
redirectPath: `/admin/organisations/${organisation.id}`,
|
|
});
|
|
|
|
await expect(page.getByRole('heading', { name: 'Danger Zone' })).not.toBeVisible();
|
|
await expect(page.getByRole('button', { name: 'Delete' })).not.toBeVisible();
|
|
|
|
const stillExists = await prisma.organisation.findUnique({ where: { id: organisation.id } });
|
|
expect(stillExists).not.toBeNull();
|
|
});
|
|
|
|
// ─── Org with multiple members triggers email to the OWNER only ─────────────
|
|
|
|
test('[ADMIN][DELETE_ORG]: job payload targets the organisation owner for the email notification', async ({ page }) => {
|
|
const { user: adminUser } = await seedUser({ isAdmin: true });
|
|
const { user: owner, organisation } = await seedUser({ isPersonalOrganisation: false });
|
|
|
|
await seedOrganisationMembers({
|
|
organisationId: organisation.id,
|
|
members: [{ organisationRole: 'MEMBER' }, { organisationRole: 'ADMIN' }, { organisationRole: 'MANAGER' }],
|
|
});
|
|
|
|
await apiSignin({
|
|
page,
|
|
email: adminUser.email,
|
|
redirectPath: `/admin/organisations/${organisation.id}`,
|
|
});
|
|
|
|
await page.getByRole('button', { name: 'Delete' }).first().click();
|
|
const dialog = page.getByRole('dialog');
|
|
await dialog.getByRole('textbox').fill(`delete ${organisation.name}`);
|
|
await dialog.getByRole('button', { name: 'Delete', exact: true }).click();
|
|
await expect(dialog).not.toBeVisible({ timeout: 10_000 });
|
|
|
|
// The job payload should record the admin who requested the delete and
|
|
// sendEmailToOwner=true. (Verifying the actual email send is out of scope
|
|
// for this test; we verify the payload only.)
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
const job = await prisma.backgroundJob.findFirst({
|
|
where: {
|
|
jobId: 'internal.admin-delete-organisation',
|
|
payload: { path: ['organisationId'], equals: organisation.id },
|
|
},
|
|
});
|
|
|
|
if (!job) {
|
|
return null;
|
|
}
|
|
|
|
const payload = job.payload as {
|
|
sendEmailToOwner?: boolean;
|
|
requestedByUserId?: number;
|
|
};
|
|
|
|
return payload;
|
|
},
|
|
{ timeout: 15_000 },
|
|
)
|
|
.toMatchObject({
|
|
sendEmailToOwner: true,
|
|
requestedByUserId: adminUser.id,
|
|
});
|
|
|
|
await waitForOrganisationDeletionJob(organisation.id);
|
|
await waitForOrganisationToBeGone(organisation.id);
|
|
|
|
// Owner user record itself is NOT deleted — only the org.
|
|
const ownerStillExists = await prisma.user.findUnique({ where: { id: owner.id } });
|
|
expect(ownerStillExists).not.toBeNull();
|
|
});
|
|
|
|
// ─── EnvelopeType.TEMPLATE is also cleaned up via orphan flow ───────────────
|
|
|
|
test('[ADMIN][DELETE_ORG]: template envelopes are removed (not orphaned)', async ({ page }) => {
|
|
const { user: adminUser } = await seedUser({ isAdmin: true });
|
|
const { user: owner, organisation, team } = await seedUser({ isPersonalOrganisation: false });
|
|
|
|
// Create a TEMPLATE envelope. orphanEnvelopes only re-parents DOCUMENT
|
|
// envelopes; templates fall into the "deleteMany" path.
|
|
const draftDoc = await seedBlankDocument(owner, team.id, { key: 'tmpl' });
|
|
await prisma.envelope.update({
|
|
where: { id: draftDoc.id },
|
|
data: { type: EnvelopeType.TEMPLATE },
|
|
});
|
|
|
|
await apiSignin({
|
|
page,
|
|
email: adminUser.email,
|
|
redirectPath: `/admin/organisations/${organisation.id}`,
|
|
});
|
|
|
|
await page.getByRole('button', { name: 'Delete' }).first().click();
|
|
const dialog = page.getByRole('dialog');
|
|
await dialog.getByRole('textbox').fill(`delete ${organisation.name}`);
|
|
await dialog.getByRole('button', { name: 'Delete', exact: true }).click();
|
|
await expect(dialog).not.toBeVisible({ timeout: 10_000 });
|
|
|
|
await waitForOrganisationDeletionJob(organisation.id);
|
|
await waitForOrganisationToBeGone(organisation.id);
|
|
|
|
const templateAfter = await prisma.envelope.findUnique({
|
|
where: { id: draftDoc.id },
|
|
select: { id: true },
|
|
});
|
|
|
|
expect(templateAfter).toBeNull();
|
|
});
|