mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-08-28 19:48:01 +08:00
feat: allow custom subdomains in create UI (#1109)
* feat: allow custom subdomains in create UI * test: stabilize custom subdomain browser flow * test: fix normalized address expectation * fix: gate custom subdomain input by setting * fix: scope custom subdomains to random domains * test: recreate random subdomain manually * refactor: minimize custom subdomain changes * refactor: reduce custom subdomain changes * test: fix custom subdomain locator * refactor: clarify manual subdomain validation * test: target visible custom subdomain input * refactor: simplify manual subdomain validation * refactor: use subdomain mode selector * style: stack subdomain modes vertically * test: click visible subdomain mode labels
This commit is contained in:
@@ -7,7 +7,9 @@ keep_vars = true
|
||||
[vars]
|
||||
PREFIX = "TMP"
|
||||
DEFAULT_DOMAINS = []
|
||||
DOMAINS = ["TEST.EXAMPLE.COM"]
|
||||
DOMAINS = ["TEST.EXAMPLE.COM", "MANUAL.EXAMPLE.COM"]
|
||||
RANDOM_SUBDOMAIN_DOMAINS = ["TEST.EXAMPLE.COM"]
|
||||
CREATE_ADDRESS_DEFAULT_DOMAIN_FIRST = true
|
||||
USER_ROLES = [
|
||||
{ domains = ["TEST.EXAMPLE.COM"], role = "case-role", prefix = "ROLE" },
|
||||
{ domains = [], role = "empty-role", prefix = "EMPTY" },
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { TEST_DOMAIN, WORKER_URL, WORKER_URL_ENV_OFF, WORKER_URL_SUBDOMAIN } from '../../fixtures/test-helpers';
|
||||
|
||||
const SUBDOMAIN = `team.${TEST_DOMAIN}`;
|
||||
const NESTED_SUBDOMAIN = `deep.team.${TEST_DOMAIN}`;
|
||||
const MIXED_CASE_SUBDOMAIN = `TeAm.${TEST_DOMAIN.toUpperCase()}`;
|
||||
const INVALID_LOOKALIKE_DOMAIN = `bad${TEST_DOMAIN}`;
|
||||
const INVALID_EMPTY_PREFIX_DOMAIN = `.${TEST_DOMAIN}`;
|
||||
const INVALID_EMPTY_LABEL_DOMAIN = `a..b.${TEST_DOMAIN}`;
|
||||
const INVALID_OVERLONG_DOMAIN = `${'a.'.repeat(119)}${TEST_DOMAIN}`;
|
||||
const MANUAL_BASE_DOMAIN = 'manual.example.com';
|
||||
const SUBDOMAIN = `team.${MANUAL_BASE_DOMAIN}`;
|
||||
const NESTED_SUBDOMAIN = `deep.team.${MANUAL_BASE_DOMAIN}`;
|
||||
const MIXED_CASE_SUBDOMAIN = `TeAm.${MANUAL_BASE_DOMAIN.toUpperCase()}`;
|
||||
const INVALID_LOOKALIKE_DOMAIN = `bad${MANUAL_BASE_DOMAIN}`;
|
||||
const INVALID_EMPTY_PREFIX_DOMAIN = `.${MANUAL_BASE_DOMAIN}`;
|
||||
const INVALID_EMPTY_LABEL_DOMAIN = `a..b.${MANUAL_BASE_DOMAIN}`;
|
||||
const INVALID_OVERLONG_DOMAIN = `${'a.'.repeat(119)}${MANUAL_BASE_DOMAIN}`;
|
||||
const RANDOM_SUBDOMAIN = `team.${TEST_DOMAIN}`;
|
||||
const CREATE_ADDRESS_WORKER_URL = WORKER_URL_SUBDOMAIN || WORKER_URL;
|
||||
let originalCreateAddressStoredEnabled: boolean | undefined;
|
||||
let originalEnvOffStoredEnabled: boolean | undefined;
|
||||
@@ -119,16 +121,16 @@ test.describe('Create Address Subdomain Match', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('persisted false still keeps exact match only', async ({ request }) => {
|
||||
test('random subdomain scope allows a manually entered subdomain', async ({ request }) => {
|
||||
await saveSubdomainMatchSetting(request, CREATE_ADDRESS_WORKER_URL, false);
|
||||
|
||||
const uniqueName = `subdomain-default-${Date.now()}`;
|
||||
const res = await request.post(`${CREATE_ADDRESS_WORKER_URL}/admin/new_address`, {
|
||||
data: { name: uniqueName, domain: SUBDOMAIN },
|
||||
data: { name: uniqueName, domain: RANDOM_SUBDOMAIN },
|
||||
});
|
||||
|
||||
expect(res.ok()).toBe(false);
|
||||
expect(await res.text()).toContain('Invalid domain');
|
||||
expect(res.ok()).toBe(true);
|
||||
expect((await res.json()).address).toContain(`@${RANDOM_SUBDOMAIN}`);
|
||||
});
|
||||
|
||||
test('admin switch enables suffix subdomain match for both admin and user create APIs', async ({ request }) => {
|
||||
@@ -184,13 +186,43 @@ test.describe('Create Address Subdomain Match', () => {
|
||||
expect(await invalidOverlongRes.text()).toContain('Invalid domain');
|
||||
});
|
||||
|
||||
test('deleted random subdomain address can be recreated manually', async ({ request }) => {
|
||||
await saveSubdomainMatchSetting(request, CREATE_ADDRESS_WORKER_URL, false);
|
||||
|
||||
const name = `subreuse${Date.now()}`;
|
||||
const firstCreate = await request.post(`${CREATE_ADDRESS_WORKER_URL}/api/new_address`, {
|
||||
data: { name, domain: TEST_DOMAIN, enableRandomSubdomain: true },
|
||||
});
|
||||
expect(firstCreate.ok()).toBe(true);
|
||||
const firstAddress = await firstCreate.json();
|
||||
const generatedDomain = firstAddress.address.split('@')[1];
|
||||
expect(generatedDomain).not.toBe(TEST_DOMAIN);
|
||||
|
||||
const firstDelete = await request.delete(`${CREATE_ADDRESS_WORKER_URL}/api/delete_address`, {
|
||||
headers: { Authorization: `Bearer ${firstAddress.jwt}` },
|
||||
});
|
||||
expect(firstDelete.ok()).toBe(true);
|
||||
|
||||
const secondCreate = await request.post(`${CREATE_ADDRESS_WORKER_URL}/api/new_address`, {
|
||||
data: { name, domain: generatedDomain },
|
||||
});
|
||||
expect(secondCreate.ok()).toBe(true);
|
||||
const secondAddress = await secondCreate.json();
|
||||
expect(secondAddress.address).toBe(firstAddress.address);
|
||||
|
||||
const secondDelete = await request.delete(`${CREATE_ADDRESS_WORKER_URL}/api/delete_address`, {
|
||||
headers: { Authorization: `Bearer ${secondAddress.jwt}` },
|
||||
});
|
||||
expect(secondDelete.ok()).toBe(true);
|
||||
});
|
||||
|
||||
test('env false works as hard kill switch even if admin setting is enabled', async ({ request }) => {
|
||||
test.skip(!WORKER_URL_ENV_OFF, 'WORKER_URL_ENV_OFF is not configured');
|
||||
|
||||
await saveSubdomainMatchSetting(request, WORKER_URL_ENV_OFF, true);
|
||||
|
||||
const res = await request.post(`${WORKER_URL_ENV_OFF}/admin/new_address`, {
|
||||
data: { name: `subdomain-env-off-${Date.now()}`, domain: SUBDOMAIN },
|
||||
data: { name: `subdomain-env-off-${Date.now()}`, domain: RANDOM_SUBDOMAIN },
|
||||
});
|
||||
expect(res.ok()).toBe(false);
|
||||
expect(await res.text()).toContain('Invalid domain');
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { FRONTEND_URL, TEST_DOMAIN, deleteAddress } from '../../fixtures/test-helpers';
|
||||
|
||||
test('create an address with a custom subdomain from the UI', async ({ page, request }) => {
|
||||
let jwt: string | undefined;
|
||||
|
||||
try {
|
||||
await page.goto(`${FRONTEND_URL}/en/`);
|
||||
await page.getByRole('button', { name: 'Create New Email' }).click();
|
||||
|
||||
const name = `subui${Date.now()}`;
|
||||
const createForm = page.locator('.n-tab-pane:visible form');
|
||||
await createForm.locator('.n-input-group .n-input input').fill(name);
|
||||
|
||||
const domainSelect = createForm.locator('.n-input-group .n-select');
|
||||
await expect(domainSelect.locator('input')).toHaveCount(0);
|
||||
|
||||
const normalSubdomain = createForm.getByRole('radio', { name: 'Normal Domain' });
|
||||
const randomSubdomain = createForm.getByRole('radio', { name: 'Use Random Subdomain' });
|
||||
const customSubdomain = createForm.getByRole('radio', { name: 'Use Custom Subdomain' });
|
||||
|
||||
await expect(normalSubdomain).toBeChecked();
|
||||
await createForm.getByText('Use Random Subdomain', { exact: true }).click();
|
||||
await expect(normalSubdomain).not.toBeChecked();
|
||||
await expect(randomSubdomain).toBeChecked();
|
||||
await expect(customSubdomain).not.toBeChecked();
|
||||
|
||||
await createForm.getByText('Use Custom Subdomain', { exact: true }).click();
|
||||
await expect(randomSubdomain).not.toBeChecked();
|
||||
await expect(customSubdomain).toBeChecked();
|
||||
|
||||
await createForm.getByText('Use Random Subdomain', { exact: true }).click();
|
||||
await expect(randomSubdomain).toBeChecked();
|
||||
await expect(customSubdomain).not.toBeChecked();
|
||||
|
||||
await createForm.getByText('Use Custom Subdomain', { exact: true }).click();
|
||||
await createForm.locator('.n-input-group:visible .n-input input').last().fill('team');
|
||||
|
||||
await createForm.getByRole('button', { name: 'Create New Email' }).click();
|
||||
|
||||
const domain = `team.${TEST_DOMAIN}`;
|
||||
const address = `tmp${name}@${domain}`;
|
||||
await expect(page.locator('code').getByText(address, { exact: true })).toBeVisible();
|
||||
await page.waitForFunction(() => Boolean(localStorage.getItem('jwt')));
|
||||
jwt = await page.evaluate(() => localStorage.getItem('jwt') || undefined);
|
||||
expect(jwt).toBeTruthy();
|
||||
} finally {
|
||||
if (jwt) await deleteAddress(request, jwt);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user