Files
upstream-rocketchat/apps/meteor/tests/end-to-end/api/http-response-truncation.ts
upstream-mirror 30810eecd7
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 2s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

87 lines
3.4 KiB
TypeScript

import { expect } from 'chai';
import { describe, it } from 'mocha';
import { apiUrl } from '../../data/api-data';
// Regression test for the dynamic-import response truncation flake (PR #41399).
//
// Through traefik with `serverstransport.maxidleconnsperhost=-1` (upstream
// keep-alive disabled), Go's transport rarely closes its upstream connection
// while the ReverseProxy is still copying a chunked gzip response body
// ("use of closed network connection"), truncating the response mid-stream:
// the browser gets 200 headers and then ERR_INCOMPLETE_CHUNKED_ENCODING, and
// the meteor module loader hangs the page. Rate is ~1.6e-4 per request under
// load, so this test replays the exact request shape (real dynamic-import
// bodies captured from a failing CI run — the fatal one is the same
// AppLayoutThemeWrapper.tsx request seen in every reproduction) at high
// volume and concurrency: red on the broken proxy config, green once
// upstream keep-alive is restored.
//
// The bodies are build-dependent fixtures: if the app tree renames these
// modules the responses shrink below the gzip threshold and lose the
// chunked+gzip shape the race needs, so the test asserts that shape first.
const FETCH_URL = `${apiUrl}/__meteor__/dynamic-import/fetch`;
const BODIES = [
'{"client":{"components":{"AppLayoutThemeWrapper.tsx":1}}}',
'{"client":{"meteor":{"login":{"index.ts":1,"cas.ts":1,"crowd.ts":1,"facebook.ts":1,"oauth.ts":1,"LoginCancelledError.ts":1,"google.ts":1,"ldap.ts":1,"meteorDeveloperAccount.ts":1,"password.ts":1,"saml.ts":1,"twitter.ts":1}},"lib":{"2fa":{"overrideLoginMethod.ts":1},"wrapRequestCredentialFn.ts":1,"loginServices.ts":1}},"node_modules":{"@rocket.chat":{"string-helpers":{"package.json":1,"dist":{"esm":{"index.js":1}}}}}}',
];
const TOTAL_REQUESTS = 30000;
const CONCURRENCY = 64;
const fetchModuleTree = async (body: string): Promise<{ error?: string }> => {
try {
const response = await fetch(FETCH_URL, {
method: 'POST',
headers: { 'Content-Type': 'text/plain;charset=UTF-8' },
body,
});
if (response.status !== 200) {
return { error: `unexpected status ${response.status}` };
}
await response.text();
return {};
} catch (error) {
return { error: `${error} (cause: ${(error as { cause?: unknown }).cause})` };
}
};
describe('dynamic-import response delivery through the proxy', () => {
it('should keep the chunked gzip response shape the race depends on', async () => {
const response = await fetch(FETCH_URL, {
method: 'POST',
headers: { 'Content-Type': 'text/plain;charset=UTF-8' },
body: BODIES[1],
});
const text = await response.text();
expect(response.status).to.equal(200);
expect(
text.length,
'fixture went stale: response too small to be gzip-chunked, update BODIES from a fresh browser trace',
).to.be.greaterThan(10000);
});
it(`should deliver ${TOTAL_REQUESTS} concurrent dynamic-import responses without a single truncation`, async function () {
this.timeout(10 * 60 * 1000);
const failures: string[] = [];
let issued = 0;
const worker = async (): Promise<void> => {
while (issued < TOTAL_REQUESTS && failures.length === 0) {
const current = issued++;
const { error } = await fetchModuleTree(BODIES[current % BODIES.length]);
if (error) {
failures.push(`request #${current}: ${error}`);
}
}
};
await Promise.all(Array.from({ length: CONCURRENCY }, worker));
expect(failures).to.deep.equal([]);
});
});