ci / db / db (push) Successful in 1m51s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m40s
ci / module boundaries / boundaries (push) Successful in 7s
ci / api / api (push) Successful in 33s
ci / ts / ts (packages + web) (push) Failing after 5m16s
85 lines
4.9 KiB
TypeScript
85 lines
4.9 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
|
|
test('locks the comment and retries one failed upload while another is running', async ({ page }, info) => {
|
|
await page.addInitScript(() => localStorage.setItem('pipu.locale', 'ru'));
|
|
await page.goto('/login');
|
|
await page.getByLabel('Логин', { exact: true }).fill('mobile-admin');
|
|
await page.getByRole('textbox', { name: 'Пароль', exact: true }).fill('mobile-admin-password');
|
|
await page.getByRole('button', { name: 'Войти', exact: true }).click();
|
|
await expect(page.getByRole('button', { name: 'Меню профиля' })).toBeVisible();
|
|
await page.evaluate(() => {
|
|
const original = window.fetch;
|
|
window.fetch = async (input, init) => {
|
|
const req = new Request(input, init);
|
|
const path = new URL(req.url).pathname;
|
|
const json = (data: unknown, status = 200) => new Response(JSON.stringify(data), { status, headers: { 'Content-Type': 'application/json' } });
|
|
if (path === '/api/v1/uploads/quota') return json({ limit_bytes: 1e9, used_bytes: 0, per_user_limit_bytes: null, user_used_bytes: 0, max_file_bytes: 1e9 });
|
|
if (path === '/api/v1/uploads/precheck') {
|
|
const body = await req.json();
|
|
return json({ files: body.files.map((f: { client_id: string }) => ({ client_id: f.client_id, result: 'accepted' })) });
|
|
}
|
|
if (path === '/api/v1/uploads' && req.method === 'POST') {
|
|
const body = await req.json();
|
|
document.documentElement.dataset.uploadComment = body.comment;
|
|
document.documentElement.dataset.uploadReservations = String(Number(document.documentElement.dataset.uploadReservations ?? 0) + 1);
|
|
return json({ batch_id: 'test-batch', files: body.files.map((f: { client_id: string; name: string }) => ({ client_id: f.client_id, upload_file_id: f.name, tus_url: `/api/v1/uploads/files/${f.name}` })) }, 201);
|
|
}
|
|
return original(input, init);
|
|
};
|
|
history.pushState({}, '', '/uploads/new');
|
|
window.dispatchEvent(new PopStateEvent('popstate'));
|
|
});
|
|
|
|
let retry = false;
|
|
let failures = 0;
|
|
let resumedOffset: string | undefined;
|
|
let releaseSecond!: () => void;
|
|
const secondPending = new Promise<void>((resolve) => { releaseSecond = resolve; });
|
|
await page.route('**/api/v1/uploads/files/*', async (route) => {
|
|
const req = route.request();
|
|
const first = req.url().endsWith('/first.flac');
|
|
if (req.method() === 'HEAD') {
|
|
await route.fulfill({ status: 200, headers: { 'Upload-Length': '1024', 'Upload-Offset': first && failures > 0 ? '256' : '0', 'Tus-Resumable': '1.0.0' } });
|
|
return;
|
|
}
|
|
if (first && !retry) {
|
|
failures++;
|
|
await route.fulfill({ status: 507, contentType: 'application/problem+json', body: JSON.stringify({ status: 507, code: 'uploads.storage_full', detail: 'upload storage is full' }) });
|
|
return;
|
|
}
|
|
if (first) resumedOffset = req.headers()['upload-offset'];
|
|
else await secondPending;
|
|
await route.fulfill({ status: 204, headers: { 'Upload-Offset': '1024', 'Tus-Resumable': '1.0.0' } });
|
|
});
|
|
|
|
const inputs = page.locator('input[type=file]');
|
|
await inputs.first().setInputFiles(['first.flac', 'second.flac'].map((name) => ({ name, mimeType: 'audio/flac', buffer: Buffer.alloc(1024) })));
|
|
const comment = page.getByRole('textbox', { name: 'Комментарий для администратора' });
|
|
await comment.fill('Lossless album');
|
|
await expect(comment).toBeEnabled();
|
|
const send = page.getByRole('button', { name: /^Отправить/ });
|
|
await expect(send).toBeEnabled();
|
|
await send.click();
|
|
await expect(comment).toBeDisabled();
|
|
await expect(page.getByRole('button', { name: 'Очистить список' })).toBeDisabled();
|
|
await expect(page.getByText(/На сервере закончилось место для загрузок/)).toBeVisible();
|
|
const retryButton = page.getByRole('button', { name: 'Повторить загрузку first.flac', exact: true });
|
|
await expect(retryButton).toBeEnabled();
|
|
await page.setViewportSize({ width: 390, height: 844 });
|
|
await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth - innerWidth)).toBeLessThanOrEqual(1);
|
|
await page.screenshot({ path: info.outputPath('upload-retry.png'), fullPage: true });
|
|
expect(failures).toBe(1);
|
|
retry = true;
|
|
await retryButton.click();
|
|
await expect.poll(() => resumedOffset).toBe('256');
|
|
await expect(retryButton).toHaveCount(0);
|
|
await expect(comment).toBeDisabled();
|
|
await expect(page.locator('html')).toHaveAttribute('data-upload-reservations', '1');
|
|
await expect(page.locator('html')).toHaveAttribute('data-upload-comment', 'Lossless album');
|
|
releaseSecond();
|
|
await expect(page.getByText(/Готово: 2 файла/)).toBeVisible();
|
|
await page.getByRole('button', { name: 'Загрузить ещё', exact: true }).click();
|
|
await inputs.first().setInputFiles({ name: 'third.flac', mimeType: 'audio/flac', buffer: Buffer.alloc(1024) });
|
|
await expect(comment).toBeEnabled();
|
|
});
|