mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
test: move packaged metro smoke to integration
This commit is contained in:
@@ -61,7 +61,6 @@
|
||||
"src/__tests__/client-companion-tunnel-worker.test.ts:196-207|test/integration/installed-package-metro.test.ts:13-18",
|
||||
"src/__tests__/client-companion-tunnel-worker.test.ts:205-212|test/integration/device-lab/device-lab-android-lifecycle.test.ts:901-908",
|
||||
"src/__tests__/client-companion-tunnel-worker.test.ts:209-220|test/integration/installed-package-metro.test.ts:18-28",
|
||||
"src/__tests__/client-companion-tunnel-worker.test.ts:397-401|test/scripts/metro-prepare-packaged-smoke.mjs:89-93",
|
||||
"src/__tests__/client-companion-tunnel-worker.test.ts:403-416|src/__tests__/client-companion-tunnel-worker.test.ts:622-635|src/__tests__/client-companion-tunnel-worker.test.ts:673-686",
|
||||
"src/__tests__/client-companion-tunnel-worker.test.ts:512-533|src/__tests__/client-companion-tunnel-worker.test.ts:662-683",
|
||||
"src/__tests__/client-companion-tunnel-worker.test.ts:512-526|src/__tests__/client-companion-tunnel-worker.test.ts:600-625|src/__tests__/client-companion-tunnel-worker.test.ts:662-676",
|
||||
@@ -502,8 +501,7 @@
|
||||
"src/utils/__tests__/device.test.ts:76-83|src/utils/__tests__/device.test.ts:85-94",
|
||||
"src/utils/__tests__/device.test.ts:161-170|src/utils/__tests__/device.test.ts:214-223",
|
||||
"src/utils/__tests__/device.test.ts:173-189|src/utils/__tests__/device.test.ts:195-210",
|
||||
"src/utils/__tests__/exec.test.ts:77-90|test/scripts/metro-prepare-packaged-smoke.mjs:124-145",
|
||||
"src/utils/__tests__/exec.test.ts:81-88|src/utils/exec.ts:336-344|test/scripts/metro-prepare-packaged-smoke.mjs:130-137",
|
||||
"src/utils/__tests__/exec.test.ts:81-88|src/utils/exec.ts:336-344",
|
||||
"src/utils/__tests__/mobile-snapshot-semantics.test.ts:338-353|src/utils/__tests__/mobile-snapshot-semantics.test.ts:86-101",
|
||||
"src/utils/__tests__/mobile-snapshot-semantics.test.ts:153-172|src/utils/__tests__/mobile-snapshot-semantics.test.ts:179-197",
|
||||
"src/utils/__tests__/mobile-snapshot-semantics.test.ts:284-309|src/utils/__tests__/mobile-snapshot-semantics.test.ts:320-345",
|
||||
@@ -552,13 +550,9 @@
|
||||
"test/integration/device-lab/device-lab-ios-lifecycle.test.ts:69-86|test/integration/device-lab/device-lab-macos-desktop.test.ts:20-37",
|
||||
"test/integration/device-lab/device-lab-ios-record-trace.test.ts:217-228|test/integration/device-lab/device-lab-ios-record-trace.test.ts:69-80",
|
||||
"test/integration/device-lab/loopback.ts:6-19|test/integration/smoke-daemon-http.test.ts:184-197|test/integration/smoke-open-remote-config.test.ts:86-99",
|
||||
"test/integration/device-lab/loopback.ts:10-18|test/integration/smoke-daemon-http.test.ts:188-196|test/integration/smoke-open-remote-config.test.ts:90-98|test/scripts/metro-prepare-packaged-smoke.mjs:56-62",
|
||||
"test/integration/installed-package-metro.test.ts:15-22|test/integration/smoke-open-remote-config.test.ts:102-109",
|
||||
"test/integration/installed-package-metro.test.ts:29-36|test/integration/smoke-open-remote-config.test.ts:115-122",
|
||||
"test/integration/smoke-daemon-http.test.ts:173-197|test/integration/smoke-open-remote-config.test.ts:78-99",
|
||||
"test/integration/smoke-daemon-http.test.ts:208-216|test/integration/smoke-daemon-http.test.ts:292-300",
|
||||
"test/integration/smoke-open-remote-config.test.ts:103-111|test/scripts/metro-prepare-packaged-smoke.mjs:66-74",
|
||||
"test/integration/smoke-open-remote-config.test.ts:146-170|test/scripts/metro-prepare-packaged-smoke.mjs:200-214",
|
||||
"test/scripts/metro-prepare-packaged-smoke.mjs:217-226|test/scripts/metro-prepare-packaged-smoke.mjs:245-254"
|
||||
"test/integration/smoke-daemon-http.test.ts:208-216|test/integration/smoke-daemon-http.test.ts:292-300"
|
||||
]
|
||||
}
|
||||
@@ -941,14 +941,6 @@
|
||||
"crap_moderate": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"test/scripts/metro-prepare-packaged-smoke.mjs": {
|
||||
"crap_critical": {
|
||||
"count": 1
|
||||
},
|
||||
"crap_moderate": {
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"runtime_coverage_findings": [],
|
||||
@@ -960,27 +952,27 @@
|
||||
"src/daemon/handlers/session-replay-script.ts:complexity",
|
||||
"src/commands/selector-read-utils.ts:high impact",
|
||||
"src/daemon/handlers/session-replay-heal.ts:complexity",
|
||||
"src/daemon/context.ts:high impact",
|
||||
"src/cli/commands/connection-runtime.ts:complexity",
|
||||
"src/daemon/android-snapshot-freshness.ts:high impact",
|
||||
"src/daemon/context.ts:high impact",
|
||||
"src/daemon/config.ts:high impact",
|
||||
"src/daemon/script-utils.ts:high impact",
|
||||
"src/platforms/boot-diagnostics.ts:complexity",
|
||||
"src/daemon/handlers/snapshot-alert.ts:complexity",
|
||||
"src/daemon/handlers/session-open-target.ts:high impact",
|
||||
"src/utils/success-text.ts:high impact",
|
||||
"src/cli/commands/output.ts:untested risk",
|
||||
"src/daemon/handlers/install-source.ts:complexity",
|
||||
"src/cli/commands/output.ts:untested risk",
|
||||
"src/utils/snapshot-processing.ts:high impact",
|
||||
"src/utils/process-identity.ts:high impact",
|
||||
"src/utils/output.ts:high impact",
|
||||
"src/utils/snapshot-lines.ts:high impact",
|
||||
"src/platforms/ios/xml.ts:high impact",
|
||||
"src/daemon/handlers/session-state.ts:complexity",
|
||||
"src/platforms/linux/snapshot.ts:high impact",
|
||||
"src/daemon/handlers/session-open.ts:complexity",
|
||||
"src/utils/args.ts:high impact",
|
||||
"src/daemon/request-cancel.ts:high impact",
|
||||
"src/platforms/linux/snapshot.ts:high impact",
|
||||
"src/cli.ts:complexity",
|
||||
"src/client-metro.ts:complexity",
|
||||
"src/utils/selector-build.ts:high impact",
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { test } from 'vitest';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import path from 'node:path';
|
||||
|
||||
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
||||
const SCRIPT_PATH = path.join(REPO_ROOT, 'test', 'scripts', 'metro-prepare-packaged-smoke.mjs');
|
||||
|
||||
test(
|
||||
'packaged metro prepare auto-starts the companion and registers before bridge success',
|
||||
{ timeout: 60_000 },
|
||||
() => {
|
||||
const result = spawnSync(process.execPath, [SCRIPT_PATH], {
|
||||
cwd: REPO_ROOT,
|
||||
encoding: 'utf8',
|
||||
timeout: 120_000,
|
||||
});
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
assert.equal(
|
||||
result.status,
|
||||
0,
|
||||
`packaged metro smoke script failed.\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`,
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -67,6 +67,16 @@ function packInstalledPackage(tempRoot: string): string {
|
||||
return path.join(packDir, tarballName);
|
||||
}
|
||||
|
||||
function ensureBuiltPackage(): void {
|
||||
const distMetroPath = path.join(repoRoot, 'dist', 'src', 'metro.js');
|
||||
if (fs.existsSync(distMetroPath)) return;
|
||||
|
||||
runCmdSync('pnpm', ['build'], {
|
||||
cwd: repoRoot,
|
||||
timeoutMs: SUBPROCESS_TIMEOUT_MS,
|
||||
});
|
||||
}
|
||||
|
||||
function extractInstalledPackage(tarballPath: string, consumerRoot: string): string {
|
||||
const nodeModulesRoot = path.join(consumerRoot, 'node_modules');
|
||||
fs.mkdirSync(nodeModulesRoot, { recursive: true });
|
||||
@@ -119,12 +129,6 @@ function acceptWebSocket(socket: Duplex, key: string): void {
|
||||
}
|
||||
|
||||
test('installed package exposes Node APIs and packaged companion tunnel entrypoint', async (t) => {
|
||||
const distMetroPath = path.join(repoRoot, 'dist', 'src', 'metro.js');
|
||||
if (!fs.existsSync(distMetroPath)) {
|
||||
t.skip('run pnpm build before executing installed-package integration tests');
|
||||
return;
|
||||
}
|
||||
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-installed-package-'));
|
||||
const consumerRoot = path.join(root, 'consumer');
|
||||
const projectRoot = path.join(root, 'project');
|
||||
@@ -229,6 +233,7 @@ test('installed package exposes Node APIs and packaged companion tunnel entrypoi
|
||||
});
|
||||
let metroPort = 0;
|
||||
try {
|
||||
ensureBuiltPackage();
|
||||
const tarballPath = packInstalledPackage(root);
|
||||
installedPackageRoot = extractInstalledPackage(tarballPath, consumerRoot);
|
||||
linkRuntimeDependencies(installedPackageRoot, consumerRoot);
|
||||
|
||||
@@ -1,460 +0,0 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawn, spawnSync } from 'node:child_process';
|
||||
import crypto from 'node:crypto';
|
||||
import fs from 'node:fs';
|
||||
import http from 'node:http';
|
||||
import net from 'node:net';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
||||
|
||||
function runChecked(command, args, cwd, stdio = 'pipe') {
|
||||
const result = spawnSync(command, args, {
|
||||
cwd,
|
||||
encoding: 'utf8',
|
||||
timeout: 120_000,
|
||||
stdio,
|
||||
});
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
assert.equal(
|
||||
result.status,
|
||||
0,
|
||||
`Expected ${command} ${args.join(' ')} to succeed.\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`,
|
||||
);
|
||||
return result.stdout;
|
||||
}
|
||||
|
||||
function findPackedTarball(packRoot) {
|
||||
const tarballName = fs.readdirSync(packRoot).find((entry) => entry.endsWith('.tgz'));
|
||||
assert.ok(tarballName, 'expected pnpm pack to produce a tarball');
|
||||
return path.join(packRoot, tarballName);
|
||||
}
|
||||
|
||||
function isProcessAlive(pid) {
|
||||
try {
|
||||
process.kill(pid, 0);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function stopProcessIfAlive(pid) {
|
||||
if (!isProcessAlive(pid)) return;
|
||||
try {
|
||||
process.kill(pid, 'SIGKILL');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async function supportsLoopbackBind() {
|
||||
return await new Promise((resolve) => {
|
||||
const server = net.createServer();
|
||||
server.once('error', () => resolve(false));
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
server.close(() => resolve(true));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function listen(server) {
|
||||
await new Promise((resolve, reject) => {
|
||||
server.once('error', reject);
|
||||
server.listen(0, '127.0.0.1', () => resolve());
|
||||
});
|
||||
const address = server.address();
|
||||
if (!address || typeof address === 'string') {
|
||||
throw new Error('Expected TCP server address');
|
||||
}
|
||||
return address.port;
|
||||
}
|
||||
|
||||
function closeServer(server) {
|
||||
if (!server.listening) return;
|
||||
server.closeAllConnections();
|
||||
server.closeIdleConnections();
|
||||
try {
|
||||
server.close();
|
||||
} catch {
|
||||
// best effort cleanup
|
||||
}
|
||||
}
|
||||
|
||||
function readJsonBody(req, callback) {
|
||||
const chunks = [];
|
||||
req.on('data', (chunk) => {
|
||||
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
||||
});
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const body = Buffer.concat(chunks).toString('utf8');
|
||||
callback(null, body ? JSON.parse(body) : {});
|
||||
} catch (error) {
|
||||
callback(error instanceof Error ? error : new Error(String(error)));
|
||||
}
|
||||
});
|
||||
req.on('error', (error) => {
|
||||
callback(error instanceof Error ? error : new Error(String(error)));
|
||||
});
|
||||
}
|
||||
|
||||
function acceptWebSocket(socket, key) {
|
||||
const accept = crypto
|
||||
.createHash('sha1')
|
||||
.update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`)
|
||||
.digest('base64');
|
||||
socket.write(
|
||||
[
|
||||
'HTTP/1.1 101 Switching Protocols',
|
||||
'Upgrade: websocket',
|
||||
'Connection: Upgrade',
|
||||
`Sec-WebSocket-Accept: ${accept}`,
|
||||
'\r\n',
|
||||
].join('\r\n'),
|
||||
);
|
||||
}
|
||||
|
||||
async function runCliWithHeartbeat(command, args, cwd) {
|
||||
return await new Promise((resolve, reject) => {
|
||||
const child = spawn(command, args, {
|
||||
cwd,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
child.stdout.setEncoding('utf8');
|
||||
child.stderr.setEncoding('utf8');
|
||||
child.stdout.on('data', (chunk) => {
|
||||
stdout += chunk;
|
||||
});
|
||||
child.stderr.on('data', (chunk) => {
|
||||
stderr += chunk;
|
||||
});
|
||||
const timeout = setTimeout(() => {
|
||||
child.kill('SIGKILL');
|
||||
reject(
|
||||
new Error(
|
||||
`Timed out waiting for packaged metro prepare.\nstdout:\n${stdout}\nstderr:\n${stderr}`,
|
||||
),
|
||||
);
|
||||
}, 30_000);
|
||||
child.on('error', (error) => {
|
||||
clearTimeout(timeout);
|
||||
reject(error);
|
||||
});
|
||||
child.on('close', (code) => {
|
||||
clearTimeout(timeout);
|
||||
resolve({
|
||||
status: code,
|
||||
stdout,
|
||||
stderr,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (!(await supportsLoopbackBind())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-packaged-metro-'));
|
||||
const projectRoot = path.join(root, 'project');
|
||||
const configDir = path.join(root, 'config');
|
||||
const packRoot = path.join(root, 'pack');
|
||||
const installRoot = path.join(root, 'install');
|
||||
const bridgeScope = {
|
||||
tenant: 'tenant-1',
|
||||
runId: 'run-1',
|
||||
leaseId: 'lease-1',
|
||||
};
|
||||
fs.mkdirSync(projectRoot, { recursive: true });
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
fs.mkdirSync(packRoot, { recursive: true });
|
||||
fs.mkdirSync(installRoot, { recursive: true });
|
||||
fs.mkdirSync(path.join(projectRoot, 'node_modules'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(projectRoot, 'package.json'),
|
||||
JSON.stringify({
|
||||
name: 'packaged-metro-smoke',
|
||||
version: '1.0.0',
|
||||
dependencies: {
|
||||
'react-native': '0.79.0',
|
||||
},
|
||||
}),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
let companionPid = 0;
|
||||
const wsSockets = new Set();
|
||||
let registerCalls = 0;
|
||||
let bridgeCalls = 0;
|
||||
let bridgeSucceeded = false;
|
||||
let registerBody;
|
||||
const bridgeBodies = [];
|
||||
let hostPort = 0;
|
||||
|
||||
const metroServer = http.createServer((req, res) => {
|
||||
if (req.method === 'GET' && req.url === '/status') {
|
||||
res.writeHead(200, {
|
||||
'content-type': 'text/plain',
|
||||
connection: 'close',
|
||||
});
|
||||
res.end('packager-status:running');
|
||||
return;
|
||||
}
|
||||
res.writeHead(404);
|
||||
res.end();
|
||||
});
|
||||
const metroPort = await listen(metroServer);
|
||||
|
||||
const hostServer = http.createServer((req, res) => {
|
||||
if (req.method === 'POST' && req.url === '/api/metro/companion/register') {
|
||||
readJsonBody(req, (error, body) => {
|
||||
if (error) {
|
||||
res.writeHead(500, {
|
||||
'content-type': 'application/json',
|
||||
connection: 'close',
|
||||
});
|
||||
res.end(JSON.stringify({ error: error.message }));
|
||||
return;
|
||||
}
|
||||
registerCalls += 1;
|
||||
registerBody = body;
|
||||
res.writeHead(200, {
|
||||
'content-type': 'application/json',
|
||||
connection: 'close',
|
||||
});
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
ok: true,
|
||||
data: {
|
||||
ws_url: `ws://127.0.0.1:${hostPort}/companion`,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.method === 'POST' && req.url === '/api/metro/bridge') {
|
||||
readJsonBody(req, (error, body) => {
|
||||
if (error) {
|
||||
res.writeHead(500, {
|
||||
'content-type': 'application/json',
|
||||
connection: 'close',
|
||||
});
|
||||
res.end(JSON.stringify({ error: error.message }));
|
||||
return;
|
||||
}
|
||||
bridgeCalls += 1;
|
||||
bridgeBodies.push(body);
|
||||
if (registerCalls === 0) {
|
||||
res.writeHead(503, {
|
||||
'content-type': 'application/json',
|
||||
connection: 'close',
|
||||
});
|
||||
res.end(JSON.stringify({ error: 'Metro companion is not connected' }));
|
||||
return;
|
||||
}
|
||||
bridgeSucceeded = true;
|
||||
res.writeHead(200, {
|
||||
'content-type': 'application/json',
|
||||
connection: 'close',
|
||||
});
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
enabled: true,
|
||||
base_url: `http://127.0.0.1:${hostPort}`,
|
||||
status_url: `http://127.0.0.1:${metroPort}/status`,
|
||||
bundle_url: 'https://packaged.metro.agent-device.dev/index.bundle?platform=ios',
|
||||
ios_runtime: {
|
||||
metro_host: 'packaged.metro.agent-device.dev',
|
||||
metro_port: 443,
|
||||
metro_bundle_url:
|
||||
'https://packaged.metro.agent-device.dev/index.bundle?platform=ios',
|
||||
},
|
||||
android_runtime: {
|
||||
metro_host: 'bridge.example.test',
|
||||
metro_port: 443,
|
||||
metro_bundle_url:
|
||||
'https://bridge.example.test/api/metro/runtimes/packaged/index.bundle?platform=android',
|
||||
},
|
||||
upstream: {
|
||||
bundle_url:
|
||||
'https://public.example.test/index.bundle?platform=ios&dev=true&minify=false',
|
||||
host: '127.0.0.1',
|
||||
port: metroPort,
|
||||
status_url: `http://127.0.0.1:${metroPort}/status`,
|
||||
},
|
||||
probe: {
|
||||
reachable: true,
|
||||
status_code: 200,
|
||||
latency_ms: 1,
|
||||
detail: 'ok',
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.writeHead(404);
|
||||
res.end();
|
||||
});
|
||||
hostServer.on('upgrade', (req, socket) => {
|
||||
if (req.url !== '/companion') {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
const key = req.headers['sec-websocket-key'];
|
||||
if (typeof key !== 'string') {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
acceptWebSocket(socket, key);
|
||||
wsSockets.add(socket);
|
||||
socket.on('close', () => {
|
||||
wsSockets.delete(socket);
|
||||
});
|
||||
socket.on('error', () => {
|
||||
wsSockets.delete(socket);
|
||||
});
|
||||
});
|
||||
hostPort = await listen(hostServer);
|
||||
|
||||
try {
|
||||
const remoteConfigPath = path.join(configDir, 'remote.json');
|
||||
fs.writeFileSync(
|
||||
remoteConfigPath,
|
||||
JSON.stringify({
|
||||
metroProjectRoot: projectRoot,
|
||||
metroProxyBaseUrl: `http://127.0.0.1:${hostPort}`,
|
||||
metroBearerToken: 'shared-token',
|
||||
...bridgeScope,
|
||||
metroPreparePort: metroPort,
|
||||
metroStartupTimeoutMs: 30_000,
|
||||
metroProbeTimeoutMs: 1_000,
|
||||
}),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
runChecked('pnpm', ['build'], REPO_ROOT, 'inherit');
|
||||
runChecked(
|
||||
'npm',
|
||||
['pack', '--ignore-scripts', '--pack-destination', packRoot],
|
||||
REPO_ROOT,
|
||||
'inherit',
|
||||
);
|
||||
const tarballPath = findPackedTarball(packRoot);
|
||||
runChecked(
|
||||
'npm',
|
||||
['install', '--ignore-scripts', '--prefix', installRoot, tarballPath],
|
||||
REPO_ROOT,
|
||||
'inherit',
|
||||
);
|
||||
const cliPath = path.join(
|
||||
installRoot,
|
||||
'node_modules',
|
||||
'agent-device',
|
||||
'bin',
|
||||
'agent-device.mjs',
|
||||
);
|
||||
const companionEntryPath = path.join(
|
||||
installRoot,
|
||||
'node_modules',
|
||||
'agent-device',
|
||||
'dist',
|
||||
'src',
|
||||
'internal',
|
||||
'companion-tunnel.js',
|
||||
);
|
||||
const topLevelCompanionEntryPath = path.join(
|
||||
installRoot,
|
||||
'node_modules',
|
||||
'agent-device',
|
||||
'dist',
|
||||
'src',
|
||||
'companion-tunnel.js',
|
||||
);
|
||||
const updateCheckEntryPath = path.join(
|
||||
installRoot,
|
||||
'node_modules',
|
||||
'agent-device',
|
||||
'dist',
|
||||
'src',
|
||||
'internal',
|
||||
'update-check-entry.js',
|
||||
);
|
||||
assert.equal(
|
||||
fs.existsSync(companionEntryPath),
|
||||
true,
|
||||
'expected packaged companion tunnel entry',
|
||||
);
|
||||
assert.equal(
|
||||
fs.existsSync(topLevelCompanionEntryPath),
|
||||
false,
|
||||
'unexpected top-level companion tunnel entry',
|
||||
);
|
||||
assert.equal(fs.existsSync(updateCheckEntryPath), true, 'expected packaged update-check entry');
|
||||
|
||||
const cliResult = await runCliWithHeartbeat(
|
||||
process.execPath,
|
||||
[cliPath, 'metro', 'prepare', '--remote-config', remoteConfigPath, '--json'],
|
||||
projectRoot,
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
cliResult.status,
|
||||
0,
|
||||
`packaged CLI failed.\nstdout:\n${cliResult.stdout}\nstderr:\n${cliResult.stderr}`,
|
||||
);
|
||||
const payload = JSON.parse(cliResult.stdout);
|
||||
assert.equal(payload.success, true);
|
||||
assert.equal(payload.data.bridge.enabled, true);
|
||||
assert.equal(payload.data.bridge.probe.reachable, true);
|
||||
|
||||
const statePath = path.join(projectRoot, '.agent-device', 'metro-companion.json');
|
||||
assert.equal(fs.existsSync(statePath), true, 'expected companion state file');
|
||||
const companionState = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
||||
companionPid = companionState.pid;
|
||||
|
||||
assert.ok(companionPid > 0, 'expected companion pid to be recorded');
|
||||
assert.equal(isProcessAlive(companionPid), true, 'expected companion process to be alive');
|
||||
assert.equal(registerCalls > 0, true, 'expected companion registration request');
|
||||
assert.equal(bridgeCalls >= 2, true, 'expected bridge retry before success');
|
||||
assert.equal(bridgeSucceeded, true, 'expected bridge success after registration');
|
||||
assert.equal(registerBody.tenantId, bridgeScope.tenant);
|
||||
assert.equal(registerBody.runId, bridgeScope.runId);
|
||||
assert.equal(registerBody.leaseId, bridgeScope.leaseId);
|
||||
assert.equal(registerBody.local_base_url, `http://127.0.0.1:${metroPort}`);
|
||||
assert.equal(bridgeBodies[0]?.tenantId, bridgeScope.tenant);
|
||||
assert.equal(bridgeBodies[0]?.runId, bridgeScope.runId);
|
||||
assert.equal(bridgeBodies[0]?.leaseId, bridgeScope.leaseId);
|
||||
assert.equal(bridgeBodies[0]?.ios_runtime, undefined);
|
||||
assert.equal(bridgeBodies.at(-1)?.tenantId, bridgeScope.tenant);
|
||||
assert.equal(bridgeBodies.at(-1)?.runId, bridgeScope.runId);
|
||||
assert.equal(bridgeBodies.at(-1)?.leaseId, bridgeScope.leaseId);
|
||||
assert.equal(bridgeBodies.at(-1)?.ios_runtime, undefined);
|
||||
} finally {
|
||||
stopProcessIfAlive(companionPid);
|
||||
for (const socket of wsSockets) {
|
||||
socket.destroy();
|
||||
}
|
||||
closeServer(hostServer);
|
||||
closeServer(metroServer);
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error instanceof Error ? (error.stack ?? error.message) : String(error));
|
||||
process.exitCode = 1;
|
||||
});
|
||||
Reference in New Issue
Block a user