fix: Bug - Sending files between users doesn't really work (chunk-time size re-gate)
Remove the leftover MAX_AUTO_SAVE_SIZE_BYTES guard from handleFileChunk's in-memory path. The request gate (canReceiveAttachment) already admits 10-50 MB generic files for in-memory receive on stores without disk streaming (browser), but the chunk handler silently dropped every chunk of such files: no ack was sent, the sender's waitForAck timed out, and the receiver's GUI never changed. Receive admission is now decided once, at request time. Adds a two-browser regression e2e that sends an 11 MB generic file and asserts Request -> progress -> Download. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
import { test, expect } from '../../fixtures/multi-client';
|
||||
import { RegisterPage } from '../../pages/register.page';
|
||||
import { ServerSearchPage } from '../../pages/server-search.page';
|
||||
import { ChatMessagesPage } from '../../pages/chat-messages.page';
|
||||
|
||||
/**
|
||||
* Regression coverage for "Sending files between users doesn't really work":
|
||||
* a generic (non-media) file above the 10 MB auto-save cap sent to a browser
|
||||
* receiver. The receiver clicks Request; previously the chunk handler dropped
|
||||
* every incoming chunk with a silent file-too-large error, the sender's ack
|
||||
* wait timed out, and the GUI never changed.
|
||||
*/
|
||||
const LARGE_FILE_SIZE_BYTES = 11 * 1024 * 1024;
|
||||
|
||||
test.describe('Large generic file transfer', () => {
|
||||
test.describe.configure({ timeout: 420_000, retries: 1 });
|
||||
|
||||
test('browser receiver can request and download a generic file above the auto-save cap', async ({ createClient }) => {
|
||||
const suffix = uniqueName('largefile');
|
||||
const serverName = `Large File Server ${suffix}`;
|
||||
const fileName = `${suffix}-dataset.bin`;
|
||||
const caption = `Large file upload ${suffix}`;
|
||||
const alice = await createClient();
|
||||
const bob = await createClient();
|
||||
const aliceMessages = new ChatMessagesPage(alice.page);
|
||||
const bobMessages = new ChatMessagesPage(bob.page);
|
||||
|
||||
await test.step('Alice and Bob register and meet in a server', async () => {
|
||||
const aliceRegister = new RegisterPage(alice.page);
|
||||
|
||||
await aliceRegister.goto();
|
||||
await aliceRegister.register(`alice_${suffix}`, 'Alice', 'TestPass123!');
|
||||
await expect(alice.page).toHaveURL(/\/dashboard/, { timeout: 15_000 });
|
||||
|
||||
const bobRegister = new RegisterPage(bob.page);
|
||||
|
||||
await bobRegister.goto();
|
||||
await bobRegister.register(`bob_${suffix}`, 'Bob', 'TestPass123!');
|
||||
await expect(bob.page).toHaveURL(/\/dashboard/, { timeout: 15_000 });
|
||||
|
||||
const aliceSearch = new ServerSearchPage(alice.page);
|
||||
|
||||
await aliceSearch.createServer(serverName, { description: 'Large generic file transfer coverage' });
|
||||
await expect(alice.page).toHaveURL(/\/room\//, { timeout: 15_000 });
|
||||
|
||||
const bobSearch = new ServerSearchPage(bob.page);
|
||||
|
||||
await bobSearch.joinServerFromSearch(serverName);
|
||||
await expect(bob.page).toHaveURL(/\/room\//, { timeout: 15_000 });
|
||||
|
||||
await aliceMessages.waitForReady();
|
||||
await bobMessages.waitForReady();
|
||||
});
|
||||
|
||||
await test.step('Alice sends an 11 MB generic file', async () => {
|
||||
await attachGeneratedBinaryFile(aliceMessages, fileName, LARGE_FILE_SIZE_BYTES);
|
||||
await aliceMessages.sendMessage(caption);
|
||||
await expect(aliceMessages.getMessageItemByText(caption)).toBeVisible({ timeout: 30_000 });
|
||||
});
|
||||
|
||||
const bobBubble = bobMessages.getMessageItemByText(caption);
|
||||
|
||||
await test.step('Bob sees the attachment card with a Request button', async () => {
|
||||
await expect(bobBubble).toBeVisible({ timeout: 30_000 });
|
||||
await expect(bobBubble.getByText(fileName, { exact: false })).toBeVisible({ timeout: 30_000 });
|
||||
await expect(bobBubble.getByRole('button', { name: /request/i })).toBeVisible({ timeout: 20_000 });
|
||||
});
|
||||
|
||||
await test.step('Bob requests the file and it downloads to completion', async () => {
|
||||
await bobBubble.getByRole('button', { name: /request/i }).click();
|
||||
|
||||
// The transfer must visibly progress (Cancel replaces Request) instead of
|
||||
// silently stalling at 0 bytes like the original bug.
|
||||
await expect(bobBubble.getByRole('button', { name: /cancel/i })).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
await expect(bobBubble.getByRole('button', { name: /download/i })).toBeVisible({ timeout: 300_000 });
|
||||
await expect(bobBubble.getByText(/too large/i)).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Builds the file inside the page so the multi-megabyte payload never crosses
|
||||
* the CDP protocol as a base64 string.
|
||||
*/
|
||||
async function attachGeneratedBinaryFile(
|
||||
messages: ChatMessagesPage,
|
||||
fileName: string,
|
||||
sizeBytes: number
|
||||
): Promise<void> {
|
||||
await messages.waitForReady();
|
||||
|
||||
await messages.composerInput.evaluate((element, { name, size }) => {
|
||||
const bytes = new Uint8Array(size);
|
||||
|
||||
for (let index = 0; index < size; index++) {
|
||||
bytes[index] = (index * 31 + 7) & 0xff;
|
||||
}
|
||||
|
||||
const dataTransfer = new DataTransfer();
|
||||
|
||||
dataTransfer.items.add(new File([bytes], name, { type: 'application/octet-stream' }));
|
||||
element.dispatchEvent(new DragEvent('drop', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
dataTransfer
|
||||
}));
|
||||
}, { name: fileName, size: sizeBytes });
|
||||
}
|
||||
|
||||
function uniqueName(prefix: string): string {
|
||||
return `${prefix}-${Date.now()}-${Math.random().toString(36)
|
||||
.slice(2, 8)}`;
|
||||
}
|
||||
Reference in New Issue
Block a user