test(e2e): пункт 6 матрицы 11.6 не зависит от суточной квоты приглашений
Приглашения ограничены 10 на пользователя за 24 ч (AGENT.md 8.6), поэтому проверка инвайтов создаёт отдельного свежего владельца сервера: у постоянных пробных аккаунтов квота выгорает за серию прогонов. Прогон: 1 passed.
This commit is contained in:
+50
-19
@@ -35,6 +35,8 @@ const BASE_URL = process.env.GLCHAT_URL ?? 'https://gl.mhspx.su';
|
||||
const API = '/api/v1';
|
||||
/** Префикс имени тестового сервера: по нему убираются серверы прошлых прогонов. */
|
||||
const GUILD_PREFIX = 'Realtime E2E ';
|
||||
/** Пароль отдельного пробного владельца сервера для проверки инвайтов. */
|
||||
const INVITE_PROBE_PASSWORD = 'Rt-Invite-Probe-9x';
|
||||
/** Постоянные пробные аккаунты: A — владелец сервера, B — второй браузер. */
|
||||
const OWNER_LOGIN = { username: 'rtprobe', password: 'Rt-Probe-Owner-9x' };
|
||||
const MEMBER_LOGIN = { username: 'rtprobe2', password: 'Rt-Probe-Member-9x' };
|
||||
@@ -573,23 +575,29 @@ test.describe('realtime: пункты 1–7 (сервер, роли, оформ
|
||||
browser,
|
||||
playwright,
|
||||
}) => {
|
||||
// Владелец сервера — наблюдатель B: панель приглашений и квота его.
|
||||
const guestApi = await apiAs(playwright, owner.state);
|
||||
const ownerApi = await apiAs(playwright, member.state);
|
||||
// Приглашения ограничены суточной квотой — 10 на пользователя (AGENT.md
|
||||
// 8.6), поэтому сервер создаёт отдельный свежий владелец: у постоянных
|
||||
// пробных аккаунтов квота выгорает за серию прогонов.
|
||||
const inviterApi = await playwright.request.newContext({ baseURL: BASE_URL });
|
||||
const guestApi = await apiAs(playwright, member.state);
|
||||
let session: Session | null = null;
|
||||
let fixture: GuildFixture | null = null;
|
||||
|
||||
try {
|
||||
fixture = await prepareGuild(guestApi, ownerApi, { observerOwns: true });
|
||||
session = await openSession(browser, member);
|
||||
await ensureProbeWithWait(inviterApi, {
|
||||
username: `rtinvite${String(Date.now() % 10_000_000)}`,
|
||||
password: INVITE_PROBE_PASSWORD,
|
||||
});
|
||||
fixture = await prepareGuild(inviterApi, guestApi);
|
||||
session = await sessionFrom(browser, await inviterApi.storageState());
|
||||
const page = session.page;
|
||||
await openWorkspace(page, fixture);
|
||||
|
||||
// Список приглашений сервера читается ручкой REST: экрана списка в
|
||||
// web-клиенте нет, панель показывает только созданную ссылку (см. отчёт).
|
||||
// Список приглашений читается ручкой REST: экрана списка в web-клиенте
|
||||
// нет, панель показывает только созданную ссылку (см. отчёт).
|
||||
const listInvites = async (): Promise<string[]> => {
|
||||
const response = await repeat429(() =>
|
||||
ownerApi.get(`${API}/guilds/${fixture?.guildId}/invites`),
|
||||
inviterApi.get(`${API}/guilds/${fixture?.guildId ?? ''}/invites`),
|
||||
);
|
||||
expect(response.ok(), `список приглашений: ${await response.text()}`).toBeTruthy();
|
||||
const payload = (await response.json()) as { invites: { code: string }[] };
|
||||
@@ -597,26 +605,40 @@ test.describe('realtime: пункты 1–7 (сервер, роли, оформ
|
||||
};
|
||||
expect(await listInvites(), 'до создания приглашений нет').toHaveLength(0);
|
||||
|
||||
// Приглашение создаётся владельцем через панель сервера.
|
||||
// Квота исчерпана — пункт проверить нечем: пропускаем с явной причиной.
|
||||
const probe = await repeat429(() =>
|
||||
inviterApi.post(`${API}/guilds/${fixture?.guildId ?? ''}/invites`, {
|
||||
data: { max_uses: 0, max_age_seconds: 3600 },
|
||||
}),
|
||||
);
|
||||
if (probe.status() === 429) {
|
||||
test.skip(true, 'суточная квота приглашений исчерпана (10/24 ч)');
|
||||
}
|
||||
expect(probe.ok(), `создание приглашения: ${await probe.text()}`).toBeTruthy();
|
||||
const restCode = ((await probe.json()) as { invite: { code: string } }).invite.code;
|
||||
await expect.poll(listInvites, { timeout: 20_000 }).toContain(restCode);
|
||||
|
||||
// Второе приглашение создаётся владельцем через панель сервера.
|
||||
await page.getByTestId('guild-invite').click();
|
||||
const linkInput = page.getByTestId('guild-invite-link');
|
||||
await expect(linkInput).toBeVisible({ timeout: 20_000 });
|
||||
const code = inviteCodeFromUrl(await linkInput.inputValue());
|
||||
await expect.poll(listInvites, { timeout: 20_000 }).toContain(code);
|
||||
const uiCode = inviteCodeFromUrl(await linkInput.inputValue());
|
||||
await expect.poll(listInvites, { timeout: 20_000 }).toContain(uiCode);
|
||||
|
||||
// Отзыв: код пропадает из списка и больше не пускает новых участников.
|
||||
const revoked = await repeat429(() => ownerApi.delete(`${API}/invites/${code}`));
|
||||
const revoked = await repeat429(() => inviterApi.delete(`${API}/invites/${uiCode}`));
|
||||
expect(revoked.ok(), `отзыв приглашения: ${await revoked.text()}`).toBeTruthy();
|
||||
await expect.poll(listInvites, { timeout: 20_000 }).not.toContain(code);
|
||||
const left = await repeat429(() => guestApi.post(`${API}/guilds/${fixture.guildId}/leave`));
|
||||
expect(left.ok(), `выход из сервера: ${await left.text()}`).toBeTruthy();
|
||||
const join = await repeat429(() => guestApi.post(`${API}/invites/${code}`));
|
||||
await expect.poll(listInvites, { timeout: 20_000 }).not.toContain(uiCode);
|
||||
const join = await guestApi.post(`${API}/invites/${uiCode}`);
|
||||
expect(join.status(), 'отозванное приглашение больше не работает').toBe(404);
|
||||
await expect(linkInput).toHaveValue(new RegExp(code, 'u'));
|
||||
|
||||
// Ссылка, созданная через панель ранее, остаётся рабочей.
|
||||
await expect(linkInput).toHaveValue(new RegExp(uiCode, 'u'));
|
||||
await expect.poll(listInvites, { timeout: 20_000 }).toContain(restCode);
|
||||
} finally {
|
||||
await session?.context.close();
|
||||
await cleanup(ownerApi, fixture, MEMBER_LOGIN.password);
|
||||
await ownerApi.dispose();
|
||||
await cleanup(inviterApi, fixture, INVITE_PROBE_PASSWORD);
|
||||
await inviterApi.dispose();
|
||||
await guestApi.dispose();
|
||||
}
|
||||
});
|
||||
@@ -903,6 +925,15 @@ async function openSession(browser: Browser, account: Account): Promise<Session>
|
||||
return { context, page };
|
||||
}
|
||||
|
||||
/** sessionFrom открывает браузерный контекст на готовой сессии аккаунта. */
|
||||
async function sessionFrom(browser: Browser, state: StorageState): Promise<Session> {
|
||||
const context = await browser.newContext({ storageState: state });
|
||||
const page = await context.newPage();
|
||||
await page.goto('/app');
|
||||
await expect(page.getByTestId('guild-rail')).toBeVisible({ timeout: 30_000 });
|
||||
return { context, page };
|
||||
}
|
||||
|
||||
/**
|
||||
* joinVoice входит в голосовую комнату: кнопка входа исчезает после
|
||||
* подключения. Подключение к SFU изредка срывается с первого раза, поэтому
|
||||
|
||||
Reference in New Issue
Block a user