104 lines
4.3 KiB
JavaScript
104 lines
4.3 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { chromium } from '@playwright/test';
|
|
import { mkdtemp, rm, readFile } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import { resolve } from 'node:path';
|
|
import { createApp } from '../../apps/server/src/app.ts';
|
|
import { loadConfig } from '../../apps/server/src/config.ts';
|
|
import { requests as table } from '../../apps/server/src/db/schema.ts';
|
|
|
|
const folder = await mkdtemp(resolve(tmpdir(), 'hop-reconnect-'));
|
|
const runtime = await createApp({
|
|
...loadConfig({
|
|
APP_URL: 'http://127.0.0.1',
|
|
DATABASE_PATH: resolve(folder, 'db.sqlite'),
|
|
UPLOAD_DIR: resolve(folder, 'uploads'),
|
|
MIN_FREE_DISK_MB: '0',
|
|
SAME_NETWORK_DISCOVERY_ENABLED: 'true',
|
|
LOCATION_DISCOVERY_ENABLED: 'false',
|
|
SESSION_SECRET: 's'.repeat(40),
|
|
DISCOVERY_SECRET: 'd'.repeat(40),
|
|
}),
|
|
webRoot: resolve('apps/web/dist'),
|
|
});
|
|
let browser;
|
|
try {
|
|
await runtime.app.listen({ host: '127.0.0.1', port: 0 });
|
|
const url = `http://127.0.0.1:${runtime.app.server.address().port}`;
|
|
runtime.setDevOrigin(url);
|
|
browser = await chromium.launch({
|
|
headless: true,
|
|
...(process.env.HOP_BROWSER_EXECUTABLE
|
|
? { executablePath: process.env.HOP_BROWSER_EXECUTABLE }
|
|
: {}),
|
|
});
|
|
const a = await browser.newContext({ locale: 'ko-KR', viewport: { width: 1280, height: 900 } }),
|
|
b = await browser.newContext({
|
|
locale: 'ko-KR',
|
|
viewport: { width: 390, height: 844 },
|
|
isMobile: true,
|
|
hasTouch: true,
|
|
});
|
|
const sender = await a.newPage(),
|
|
receiver = await b.newPage(),
|
|
errors = [];
|
|
for (const page of [sender, receiver])
|
|
page.on('pageerror', (error) => errors.push(error.message));
|
|
await Promise.all([sender.goto(url), receiver.goto(url)]);
|
|
await sender.locator('.peer-card').first().waitFor();
|
|
const receiverState = await receiver.evaluate(
|
|
async () => (await (await fetch('/api/device/me')).json()).device,
|
|
);
|
|
const guestId = runtime.service.deviceGuest(receiverState.id).guestId;
|
|
await sender.locator('.peer-card').first().click();
|
|
const bytes = Buffer.from('first-click transfer');
|
|
await sender
|
|
.locator('.send-dialog input[type="file"]')
|
|
.setInputFiles({ name: 'first.txt', mimeType: 'text/plain', buffer: bytes });
|
|
let hold = true;
|
|
runtime.io.use((socket, next) => {
|
|
if (hold && socket.data.guest.id === guestId) setTimeout(() => next(), 1200);
|
|
else next();
|
|
});
|
|
const serverSocket = [...runtime.io.sockets.sockets.values()].find(
|
|
(socket) => socket.data.guest.id === guestId,
|
|
);
|
|
serverSocket.conn.close();
|
|
await receiver.locator('.app-shell[data-online="false"]').waitFor();
|
|
const responses = [],
|
|
keys = [];
|
|
sender.on('response', (response) => {
|
|
if (
|
|
new URL(response.url()).pathname === '/api/transfer-requests' &&
|
|
response.request().method() === 'POST'
|
|
) {
|
|
responses.push(response.status());
|
|
keys.push(response.request().headers()['idempotency-key']);
|
|
}
|
|
});
|
|
await sender.getByRole('button', { name: '전송하기', exact: true }).click();
|
|
await sender.getByRole('button', { name: '연결 확인 중', exact: true }).waitFor();
|
|
assert.equal(await sender.locator('.error-toast').count(), 0);
|
|
assert.match(await sender.locator('.selected-files').innerText(), /first.txt/);
|
|
await receiver.getByRole('button', { name: '수락', exact: true }).waitFor();
|
|
hold = false;
|
|
assert.equal(responses[0], 409);
|
|
assert.equal(responses.at(-1), 200);
|
|
assert.equal(new Set(keys).size, 1, 'reconnect retry reuses one idempotency key');
|
|
assert.equal(runtime.service.db.select().from(table).all().length, 1, 'only one logical request');
|
|
const downloaded = receiver.waitForEvent('download');
|
|
await receiver.getByRole('button', { name: '수락', exact: true }).click();
|
|
const download = await downloaded;
|
|
assert.equal(download.suggestedFilename(), 'first.txt');
|
|
assert.deepEqual(await readFile(await download.path()), bytes);
|
|
assert.equal(await sender.locator('.error-toast').count(), 0);
|
|
assert.deepEqual(errors, []);
|
|
console.log(
|
|
'PASS: same-network peer reconnect on first file send, connection feedback, file draft retained, stable retry key, one request, accepted download on mobile.',
|
|
);
|
|
} finally {
|
|
await browser?.close();
|
|
await runtime.app.close();
|
|
await rm(folder, { recursive: true, force: true });
|
|
}
|