dsh-im-ops/test/channels/feishu/lark-sdk-handshake-patch.test.mjs

125 lines
4.5 KiB
JavaScript

import assert from 'node:assert/strict';
import { execFile } from 'node:child_process';
import { mkdtemp, readFile, rm } from 'node:fs/promises';
import { createRequire } from 'node:module';
import { tmpdir } from 'node:os';
import { dirname, join, resolve } from 'node:path';
import test from 'node:test';
import { promisify } from 'node:util';
import { fileURLToPath } from 'node:url';
import { build } from 'esbuild';
import {
larkSdkHandshakePatch,
patchLarkSdkHandshakeSource,
} from '../../../plugin-src/host/lark-sdk-handshake-patch.mjs';
const require = createRequire(import.meta.url);
const execFileAsync = promisify(execFile);
const testDirectory = dirname(fileURLToPath(import.meta.url));
test('Lark SDK patch applies every reviewed lifecycle fix to both vendor builds', async () => {
const sdkRoot = dirname(require.resolve('@larksuiteoapi/node-sdk/package.json'));
for (const flavor of ['es', 'lib']) {
const sdkPath = resolve(sdkRoot, flavor, 'index.js');
const source = await readFile(sdkPath, 'utf8');
const patched = patchLarkSdkHandshakeSource(source, sdkPath);
assert.match(patched, /this\.pendingWsInstance = null;/u);
assert.match(patched, /this\.pendingWsInstance = wsInstance;/u);
assert.match(patched, /this\.pendingWsInstance === wsInstance/u);
assert.match(patched, /wsInstance\.removeAllListeners\(['"]open['"]\);/u);
assert.match(patched, /pendingWsInstance\.terminate\(\);/u);
assert.match(
patched,
/finally \{\s+if \(currentGeneration === this\.reconnectGeneration\) \{\s+this\.isConnecting = false;/u,
);
assert.match(
patched,
/if \(this\.terminalError\) \{\s+this\.isConnecting = false;[^]*?if \(this\.isConnecting \|\|[^]*?liveWsInstance\.readyState !== WebSocket\.CLOSED/u,
);
assert.match(patched, /currentGeneration !== this\.reconnectGeneration \|\| result\.cancelled/u);
assert.match(patched, /wsInstance\.on\(['"]error['"]/u);
assert.doesNotMatch(
patched,
/handshake timeout[^]*?wsInstance\.removeAllListeners\(\);[^]*?settleOnce\(false\);/u,
);
}
});
test('Lark SDK handshake patch fails closed when the reviewed vendor shape changes', () => {
assert.throws(
() => patchLarkSdkHandshakeSource('wsInstance.terminate();', 'changed-sdk.js'),
/expected exactly one reviewed WSClient pending-socket state marker, found 0/u,
);
});
test('patched real SDK survives handshake timeout and close without uncaught or zombie reconnects', async () => {
const temporaryDirectory = await mkdtemp(join(tmpdir(), 'dsh-lark-sdk-test-'));
const outputPath = join(temporaryDirectory, 'scenario.mjs');
try {
await build({
entryPoints: [resolve(testDirectory, 'fixtures/lark-sdk-close-during-handshake.mjs')],
bundle: true,
format: 'esm',
platform: 'node',
target: ['node22'],
mainFields: ['module', 'main'],
plugins: [larkSdkHandshakePatch],
outfile: outputPath,
logLevel: 'silent',
banner: {
js: [
"import { createRequire as __dshCreateRequire } from 'node:module';",
"import { dirname as __dshDirname } from 'node:path';",
"import { fileURLToPath as __dshFileURLToPath } from 'node:url';",
'const require = __dshCreateRequire(import.meta.url);',
'const __filename = __dshFileURLToPath(import.meta.url);',
'const __dirname = __dshDirname(__filename);',
].join('\n'),
},
});
const { stdout, stderr } = await execFileAsync(process.execPath, [outputPath], {
timeout: 5_000,
});
assert.deepEqual(JSON.parse(stdout), {
handshakeTimeoutPulls: 1,
closeDuringHandshakePulls: 1,
immediateRestart: {
callbackCount: 0,
endpointPulls: 2,
stateBeforeFinalClose: 'connecting',
},
duplicateStart: {
acceptedConnections: 1,
activeSocketsAfterClose: 0,
activeSocketsBeforeClose: 1,
callbackCount: 0,
endpointPulls: 1,
stateBeforeClose: 'connecting',
},
connectedRepeatStart: {
acceptedConnections: 1,
activeSocketsAfterClose: 0,
activeSocketsBeforeClose: 1,
callbackCount: 1,
endpointPulls: 1,
stateBeforeClose: 'connected',
},
failedRestart: {
callbackCountBeforeRestart: 1,
endpointPulls: 2,
stateAfterRestart: 'connecting',
},
});
assert.equal(stderr, '');
}
finally {
await rm(temporaryDirectory, { recursive: true, force: true });
}
});