fix(uploads): serve private previews through API and show batch cover status
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m30s
ci / ts / ts (packages + web) (push) Canceled after 4m13s
ci / module boundaries / boundaries (push) Successful in 8s
ci / api / api (push) Successful in 56s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m1s
ci / db / db (push) Successful in 1m7s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m30s
ci / ts / ts (packages + web) (push) Canceled after 4m13s
ci / module boundaries / boundaries (push) Successful in 8s
ci / api / api (push) Successful in 56s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m1s
ci / db / db (push) Successful in 1m7s
This commit is contained in:
1 parent
37e33c656b
commit
7d62531c02
15 files changed
+533
-410
No files matched your search
@@ -1,5 +1,15 @@
|
||||
import { expect, test, type Page } from '@playwright/test';
|
||||
|
||||
function previewWav() {
|
||||
const dataSize = 8000 * 2 * 2;
|
||||
const wav = Buffer.alloc(44 + dataSize);
|
||||
wav.write('RIFF', 0); wav.writeUInt32LE(36 + dataSize, 4); wav.write('WAVEfmt ', 8);
|
||||
wav.writeUInt32LE(16, 16); wav.writeUInt16LE(1, 20); wav.writeUInt16LE(1, 22);
|
||||
wav.writeUInt32LE(8000, 24); wav.writeUInt32LE(16000, 28); wav.writeUInt16LE(2, 32); wav.writeUInt16LE(16, 34);
|
||||
wav.write('data', 36); wav.writeUInt32LE(dataSize, 40);
|
||||
return wav;
|
||||
}
|
||||
|
||||
async function setup(page: Page, scenario: 'queue' | 'template' | 'mine') {
|
||||
await page.addInitScript(() => localStorage.setItem('pipu.locale', 'ru'));
|
||||
await page.goto('/login');
|
||||
@@ -11,7 +21,7 @@ async function setup(page: Page, scenario: 'queue' | 'template' | 'mine') {
|
||||
const modulePath = '/src/test/mobileFixtures.ts';
|
||||
const { mobileFixtures } = await import(modulePath);
|
||||
const source = structuredClone(mobileFixtures['/api/v1/admin/sources'].items[0]);
|
||||
const file = structuredClone(mobileFixtures['/api/v1/admin/uploads'].items[0]);
|
||||
const file = { ...structuredClone(mobileFixtures['/api/v1/admin/uploads'].items[0]), batch_has_cover: true };
|
||||
const files = [
|
||||
{ ...file, id: 'visible', original_name: 'visible.flac', target_source_name: source.name, target_rel_path: 'Artist/Album/01 - Song.flac', target_storage_path: 'nas:/music/Artist/Album/01 - Song.flac', stream_url: `/api/v1/admin/uploads/files/${file.id}/stream?exp=9999999999&sig=test` },
|
||||
{ ...file, id: 'approved', original_name: 'approved.flac', status: 'approved' },
|
||||
@@ -75,6 +85,7 @@ async function setup(page: Page, scenario: 'queue' | 'template' | 'mine') {
|
||||
|
||||
test('queue shows the storage and path, and recalculates after fixing placement', async ({ page }, info) => {
|
||||
await setup(page, 'queue');
|
||||
await expect(page.getByText('Есть обложка', { exact: true })).toBeVisible();
|
||||
await expect(page.getByText('Artist/Album/01 - Song.flac', { exact: true })).toBeHidden();
|
||||
await page.getByRole('button', { name: 'Развернуть пачку', exact: true }).click();
|
||||
await expect(page.getByText('Artist/Album/01 - Song.flac', { exact: true })).toBeVisible();
|
||||
@@ -170,12 +181,7 @@ test('queue shows the current full destination after saving a changed source tem
|
||||
});
|
||||
|
||||
test('queue preview loads duration before play and plays signed audio', async ({ page }) => {
|
||||
const dataSize = 8000 * 2 * 2;
|
||||
const wav = Buffer.alloc(44 + dataSize);
|
||||
wav.write('RIFF', 0); wav.writeUInt32LE(36 + dataSize, 4); wav.write('WAVEfmt ', 8);
|
||||
wav.writeUInt32LE(16, 16); wav.writeUInt16LE(1, 20); wav.writeUInt16LE(1, 22);
|
||||
wav.writeUInt32LE(8000, 24); wav.writeUInt32LE(16000, 28); wav.writeUInt16LE(2, 32); wav.writeUInt16LE(16, 34);
|
||||
wav.write('data', 36); wav.writeUInt32LE(dataSize, 40);
|
||||
const wav = previewWav();
|
||||
await page.route('**/api/v1/admin/uploads/files/*/stream?*', (route) => route.fulfill({ contentType: 'audio/wav', body: wav }));
|
||||
await setup(page, 'queue');
|
||||
await page.getByRole('button', { name: 'Развернуть пачку', exact: true }).click();
|
||||
@@ -186,3 +192,27 @@ test('queue preview loads duration before play and plays signed audio', async ({
|
||||
await expect.poll(() => audio.evaluate((el: HTMLAudioElement) => el.currentTime)).toBeGreaterThan(0);
|
||||
expect(await audio.evaluate((el: HTMLAudioElement) => el.error)).toBeNull();
|
||||
});
|
||||
|
||||
test('queue preview explains HTTP failures and recovers on retry', async ({ page }) => {
|
||||
let available = false;
|
||||
// The E2E bridge mocks window.fetch; media diagnostics must reach the same
|
||||
// intercepted HTTP route as the native audio element.
|
||||
await page.addInitScript(() => Object.assign(window, { previewNativeFetch: window.fetch.bind(window) }));
|
||||
await page.route('**/api/v1/admin/uploads/files/*/stream?*', (route) => available
|
||||
? route.fulfill({ contentType: 'audio/wav', body: previewWav() })
|
||||
: route.fulfill({ status: 403, contentType: 'text/plain', body: 'Forbidden' }));
|
||||
await setup(page, 'queue');
|
||||
await page.evaluate(() => {
|
||||
const original = window.fetch;
|
||||
const native = (window as unknown as { previewNativeFetch: typeof fetch }).previewNativeFetch;
|
||||
window.fetch = (input, init) => new URL(new Request(input, init).url).pathname.endsWith('/stream')
|
||||
? native(input, init) : original(input, init);
|
||||
});
|
||||
await page.getByRole('button', { name: 'Развернуть пачку', exact: true }).click();
|
||||
const preview = page.locator('audio').first().locator('..');
|
||||
await expect(preview.getByRole('alert')).toHaveText('Не удалось загрузить аудио: HTTP 403.');
|
||||
available = true;
|
||||
await preview.getByRole('button', { name: 'Повторить', exact: true }).click();
|
||||
await expect.poll(() => preview.locator('audio').evaluate((el: HTMLAudioElement) => el.duration)).toBe(2);
|
||||
await expect(preview.getByRole('alert')).toHaveCount(0);
|
||||
});
|
||||
Reference in new issue
Block a user