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 1s
87 lines
3.4 KiB
TypeScript
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([]);
|
|
});
|
|
});
|