diff --git a/package-lock.json b/package-lock.json index 056340cdc4..d982e6ff82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -128,7 +128,7 @@ "@indutny/parallel-prettier": "3.0.0", "@indutny/rezip-electron": "1.3.1", "@indutny/symbolicate-mac": "2.3.0", - "@signalapp/mock-server": "6.6.0", + "@signalapp/mock-server": "6.7.0", "@storybook/addon-a11y": "8.1.11", "@storybook/addon-actions": "8.1.11", "@storybook/addon-controls": "8.1.11", @@ -7176,6 +7176,21 @@ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" } }, + "node_modules/@redis/client": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.0.tgz", + "integrity": "sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==", + "dev": true, + "optional": true, + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -7239,12 +7254,14 @@ } }, "node_modules/@signalapp/mock-server": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@signalapp/mock-server/-/mock-server-6.6.0.tgz", - "integrity": "sha512-zeLz9YikLaCQfWgSy2XDeEMdLWUTpyGOteSucD1BLcmv54J2eysk7ppDJns3H13opmF4Qj1Xmy5VftG3V3QKow==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@signalapp/mock-server/-/mock-server-6.7.0.tgz", + "integrity": "sha512-ueF3RTp07Y3kxdIg3un3dyE87VdA3y+E37wNxG9HbAb8fk5WP4LYlRwa7VvJBRuI28IU8LbrrC0d7ItXGdN3sA==", "dev": true, "dependencies": { "@signalapp/libsignal-client": "^0.45.0", + "@tus/file-store": "^1.4.0", + "@tus/server": "^1.7.0", "debug": "^4.3.2", "long": "^4.0.0", "micro": "^9.3.4", @@ -10525,6 +10542,94 @@ "node": ">=10.13.0" } }, + "node_modules/@tus/file-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@tus/file-store/-/file-store-1.4.0.tgz", + "integrity": "sha512-r+K4vGEvjlF9EEKZSgh1q9pLwbt87tcWUJDgjYyzYVMSPP98tifSOzFuPqQ2GwtQgIlVNLnyYm/PNqPd3RUNFw==", + "dev": true, + "dependencies": { + "@tus/utils": "^0.3.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@redis/client": "^1.5.13" + } + }, + "node_modules/@tus/file-store/node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@tus/file-store/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@tus/server": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@tus/server/-/server-1.7.0.tgz", + "integrity": "sha512-2RlQXkTw3+fMFUbIO4AIrgF8C/OI6WtSQ2R0iWEzpSUD9i8TqJNxa7gNAGFe4SQJUtGLTq7OxUWb6+Dndblr+Q==", + "dev": true, + "dependencies": { + "@tus/utils": "^0.3.0", + "debug": "^4.3.4", + "lodash.throttle": "^4.1.1" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@redis/client": "^1.5.13" + } + }, + "node_modules/@tus/server/node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@tus/server/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@tus/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tus/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-Wt/phitrYEP6T9Tq0ikhJdGKpeEEkOT+vEVUKQKBXBUZdtWubLQKvR2V9jtzekFhLIoqm0KS5uOb7abZgebT3A==", + "dev": true, + "engines": { + "node": ">=16" + } + }, "node_modules/@types/aria-query": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.2.tgz", @@ -15642,6 +15747,16 @@ "node": ">=6" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -21494,6 +21609,16 @@ "node": ">=0.10.0" } }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "dev": true, + "optional": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", diff --git a/package.json b/package.json index 34f1887586..6a0171f0e6 100644 --- a/package.json +++ b/package.json @@ -210,7 +210,7 @@ "@indutny/parallel-prettier": "3.0.0", "@indutny/rezip-electron": "1.3.1", "@indutny/symbolicate-mac": "2.3.0", - "@signalapp/mock-server": "6.6.0", + "@signalapp/mock-server": "6.7.0", "@storybook/addon-a11y": "8.1.11", "@storybook/addon-actions": "8.1.11", "@storybook/addon-controls": "8.1.11", diff --git a/ts/components/CompositionUpload.tsx b/ts/components/CompositionUpload.tsx index e9086741a9..d68bfd1dcc 100644 --- a/ts/components/CompositionUpload.tsx +++ b/ts/components/CompositionUpload.tsx @@ -49,6 +49,7 @@ export const CompositionUpload = forwardRef( return ( + tmpPath ? fs.rm(tmpPath, { recursive: true }) : Promise.resolve() + ), this.server.close(), this.lastApp?.close(), ]), @@ -649,6 +649,7 @@ export class Bootstrap { cdn: { '0': url, '2': url, + '3': `${url}/cdn3`, }, updatesEnabled: false, diff --git a/ts/test-mock/helpers.ts b/ts/test-mock/helpers.ts index 2501586eaf..42f29c09df 100644 --- a/ts/test-mock/helpers.ts +++ b/ts/test-mock/helpers.ts @@ -139,12 +139,14 @@ export function sendTextMessage({ from, to, text, + attachments, desktop, timestamp = Date.now(), }: { from: PrimaryDevice; to: PrimaryDevice | Device | GroupInfo; text: string; + attachments?: Array; desktop: Device; timestamp?: number; }): Promise { @@ -158,6 +160,7 @@ export function sendTextMessage({ to: to as PrimaryDevice, dataMessage: { body: text, + attachments, timestamp: Long.fromNumber(timestamp), groupV2: groupInfo ? { diff --git a/ts/test-mock/messaging/attachments_test.ts b/ts/test-mock/messaging/attachments_test.ts new file mode 100644 index 0000000000..b06b4545c6 --- /dev/null +++ b/ts/test-mock/messaging/attachments_test.ts @@ -0,0 +1,111 @@ +// Copyright 2024 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import createDebug from 'debug'; +import { expect } from 'playwright/test'; +import { type PrimaryDevice, StorageState } from '@signalapp/mock-server'; +import * as path from 'path'; +import type { App } from '../playwright'; +import { Bootstrap } from '../bootstrap'; +import { + getMessageInTimelineByTimestamp, + getTimeline, + sendTextMessage, + typeIntoInput, +} from '../helpers'; +import * as durations from '../../util/durations'; +import { strictAssert } from '../../util/assert'; + +export const debug = createDebug('mock:test:attachments'); + +describe('attachments', function (this: Mocha.Suite) { + this.timeout(durations.MINUTE); + + let bootstrap: Bootstrap; + let app: App; + let pinned: PrimaryDevice; + + beforeEach(async () => { + bootstrap = new Bootstrap(); + await bootstrap.init(); + + let state = StorageState.getEmpty(); + + const { phone, contacts } = bootstrap; + [pinned] = contacts; + + state = state.addContact(pinned, { + identityKey: pinned.publicKey.serialize(), + profileKey: pinned.profileKey.serialize(), + whitelisted: true, + }); + + state = state.pin(pinned); + await phone.setStorageState(state); + + app = await bootstrap.link(); + }); + + afterEach(async function (this: Mocha.Context) { + if (!bootstrap) { + return; + } + + await bootstrap.maybeSaveLogs(this.currentTest, app); + await app.close(); + await bootstrap.teardown(); + }); + + it('can upload attachment to CDN3 and download incoming attachment', async () => { + const page = await app.getWindow(); + + await page.getByTestId(pinned.device.aci).click(); + await page + .getByTestId('attachfile-input') + .setInputFiles( + path.join(__dirname, '..', '..', '..', 'fixtures', 'cat-screenshot.png') + ); + const input = await app.waitForEnabledComposer(); + await typeIntoInput(input, 'This is my cat'); + await input.press('Enter'); + + const allMessagesLocator = getTimeline(page).getByRole('article'); + await expect(allMessagesLocator).toHaveCount(1); + + const allMessages = await allMessagesLocator.all(); + const message = allMessages[0]; + + await message.getByText('This is my cat').waitFor(); + await message + .locator('.module-message__metadata__status-icon--sent') + .waitFor(); + + const timestamp = await message + .locator('.module-message.module-message--outgoing') + .getAttribute('data-testid'); + + strictAssert(timestamp, 'timestamp must exist'); + + // For this test, just send back the same attachment that was uploaded to test a + // round-trip + const receivedMessage = await pinned.waitForMessage(); + const attachment = receivedMessage.dataMessage.attachments?.[0]; + strictAssert(attachment, 'attachment must exist'); + + const incomingTimestamp = Date.now(); + await sendTextMessage({ + from: pinned, + to: bootstrap.desktop, + desktop: bootstrap.desktop, + text: 'Wait, that is MY cat!', + attachments: [attachment], + timestamp: incomingTimestamp, + }); + + await expect( + getMessageInTimelineByTimestamp(page, incomingTimestamp).locator( + 'img.module-image__image' + ) + ).toBeVisible(); + }); +});