fix(tenancy): close isolation and permission gaps

This commit is contained in:
Junyan Qin
2026-07-20 04:02:24 +08:00
parent 18bcb41e9e
commit 90a9774882
30 changed files with 3294 additions and 1764 deletions
+127
View File
@@ -53,3 +53,130 @@ test('terminal invitation errors refresh on a new fragment and allow account swi
})),
).toEqual({ token: null, userEmail: null });
});
test('login preserves an explicit invitation email mismatch error', async ({
page,
}) => {
await installLangBotApiMocks(page, { authenticated: false });
await page.route('**/api/v1/invitations/inspect', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
code: 0,
data: {
invitation: {
uuid: 'mismatch-invitation',
workspace_uuid: 'workspace-playwright',
normalized_email: 'invited@example.com',
role: 'viewer',
status: 'pending',
},
workspace: {
uuid: 'workspace-playwright',
name: 'Playwright Workspace',
},
},
msg: 'ok',
}),
});
});
await page.route('**/api/v1/invitations/accept', async (route) => {
await route.fulfill({
status: 400,
contentType: 'application/json',
body: JSON.stringify({
code: 'invitation_email_mismatch',
msg: 'Invitation email does not match the Account',
}),
});
});
await page.goto('/invitations/accept#token=mismatch-invitation');
await page.getByRole('button', { name: 'I already have an account' }).click();
await page.getByPlaceholder('Enter email address').fill('other@example.com');
await page.getByPlaceholder('Enter password').fill('password');
await page.getByRole('button', { name: 'Login with password' }).click();
await expect(page).toHaveURL(
/\/invitations\/accept\?error=invitation_email_mismatch$/,
);
await expect(
page.getByText('This invitation belongs to a different email address.'),
).toBeVisible();
await expect(page.getByText('Login successful')).toHaveCount(0);
});
test('a temporary accept failure keeps the authenticated invitation retryable', async ({
page,
}) => {
await installLangBotApiMocks(page, { authenticated: false });
await page.route('**/api/v1/invitations/inspect', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
code: 0,
data: {
invitation: {
uuid: 'retryable-invitation',
workspace_uuid: 'workspace-playwright',
normalized_email: 'invited@example.com',
role: 'viewer',
status: 'pending',
},
workspace: {
uuid: 'workspace-playwright',
name: 'Playwright Workspace',
},
},
msg: 'ok',
}),
});
});
let acceptAttempts = 0;
await page.route('**/api/v1/invitations/accept', async (route) => {
acceptAttempts += 1;
if (acceptAttempts === 1) {
await route.fulfill({
status: 503,
contentType: 'application/json',
body: JSON.stringify({
code: 'temporary_failure',
msg: 'Please retry',
}),
});
return;
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
code: 0,
data: {
token: 'accepted-token',
workspace_uuid: 'workspace-playwright',
},
msg: 'ok',
}),
});
});
await page.goto('/invitations/accept#token=retryable-invitation');
await page.getByRole('button', { name: 'I already have an account' }).click();
await page
.getByPlaceholder('Enter email address')
.fill('invited@example.com');
await page.getByPlaceholder('Enter password').fill('password');
await page.getByRole('button', { name: 'Login with password' }).click();
await expect(page).toHaveURL(/\/invitations\/accept$/);
await expect(
page.getByRole('button', { name: 'Accept with current account' }),
).toBeVisible();
await page
.getByRole('button', { name: 'Accept with current account' })
.click();
await expect(page).toHaveURL(/\/home(?:\/monitoring)?$/);
expect(acceptAttempts).toBe(2);
});