52 lines
5.7 KiB
TypeScript
52 lines
5.7 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
|
|
function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
|
|
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
|
|
}
|
|
|
|
const rustBridge = readFileSync("src/components/backend-core/src/ssh_bridge.rs", "utf8");
|
|
const rustData = readFileSync("src/components/backend-core/src/ssh_data_channel.rs", "utf8");
|
|
const rustMain = readFileSync("src/components/backend-core/src/main.rs", "utf8");
|
|
const rustState = readFileSync("src/components/backend-core/src/state.rs", "utf8");
|
|
const rustProviderRegistry = readFileSync("src/components/backend-core/src/provider_registry.rs", "utf8");
|
|
const provider = readFileSync("src/components/provider-gateway/src/index.ts", "utf8");
|
|
const providerRegistryTs = readFileSync("src/components/backend-core/src/provider-registry.ts", "utf8");
|
|
const providerPackage = JSON.parse(readFileSync("src/components/provider-gateway/package.json", "utf8")) as { version?: unknown };
|
|
const shared = readFileSync("src/components/shared/src/index.ts", "utf8");
|
|
const compose = readFileSync("docker-compose.yml", "utf8");
|
|
const config = readFileSync("config.json", "utf8");
|
|
const backendCoreDockerfile = readFileSync("src/components/backend-core/Dockerfile", "utf8");
|
|
|
|
assertCondition(rustBridge.includes('"host.ssh.tcp-pool"'), "backend-core ssh bridge must require host.ssh.tcp-pool");
|
|
assertCondition(rustBridge.includes("provider-gateway-upgrade-required"), "old provider must fail with upgrade-required classification");
|
|
assertCondition(rustBridge.includes("provider-data-pool-exhausted"), "tcp pool exhaustion must be visible");
|
|
assertCondition(!rustBridge.includes('"host_ssh_input"'), "ssh input must not fall back to provider control websocket");
|
|
assertCondition(!rustBridge.includes('"host_ssh_eof"'), "ssh eof must not fall back to provider control websocket");
|
|
assertCondition(!rustBridge.includes('"host_ssh_close"'), "ssh close must not fall back to provider control websocket");
|
|
assertCondition(rustBridge.includes('json!({ "type": "input"') && rustBridge.includes('json!({ "type": "eof"'), "ssh bridge must send stdin/eof over data frames");
|
|
assertCondition(!rustProviderRegistry.includes('"host_ssh_opened"') && !rustProviderRegistry.includes('"host_ssh_data"') && !rustProviderRegistry.includes('"host_ssh_exit"'), "rust provider registry must not accept old websocket ssh data messages");
|
|
|
|
assertCondition(rustData.includes("TcpListener") && rustData.includes("read_data_frame") && rustData.includes("write_data_frame"), "backend-core must expose raw TCP data frame listener");
|
|
assertCondition(rustData.includes("SSH_DATA_PROTOCOL") && rustData.includes("unidesk-host-ssh-tcp-pool-v1"), "tcp data protocol must be explicit");
|
|
assertCondition(rustData.includes("base64::engine::general_purpose::STANDARD.encode(payload)"), "only client-facing websocket payload should remain base64 encoded");
|
|
assertCondition(rustMain.includes("providerDataTcpUrl") && rustMain.includes("spawn_ssh_data_listener"), "backend-core startup must expose provider data listener visibility");
|
|
assertCondition(rustState.includes("active_ssh_data_channels") && rustState.includes("data_channel_id"), "backend state must track data channels separately from provider control socket");
|
|
|
|
assertCondition(provider.includes("createConnection") && provider.includes("sshDataChannels"), "provider-gateway must use direct TCP sockets for ssh data pool");
|
|
assertCondition(provider.includes("PROVIDER_DATA_POOL_SIZE") && provider.includes("providerGatewaySshDataPoolReady"), "provider-gateway must expose pool config and heartbeat labels");
|
|
assertCondition(provider.includes('capabilities.push("host.ssh", "host.ssh.tcp-pool")'), "provider-gateway must declare tcp-pool capability only with host ssh");
|
|
assertCondition(provider.includes("acquireSshDataChannel") && provider.includes("releaseSshDataChannel"), "provider-gateway must claim/release one data channel per ssh session");
|
|
assertCondition(provider.includes("writeSshDataFrame(dataChannel") && provider.includes("sendSshDataSessionFrame"), "provider-gateway ssh output must use tcp data frames");
|
|
assertCondition(!provider.includes('parsed.type === "host_ssh_input"') && !provider.includes('parsed.type === "host_ssh_close"'), "provider-gateway must not retain old websocket ssh input handlers");
|
|
assertCondition(providerPackage.version === "0.2.28", "provider-gateway behavior change must bump package version", providerPackage);
|
|
|
|
assertCondition(shared.includes('transport: "tcp-pool"') && shared.includes("dataChannelId: string"), "shared host_ssh_open contract must require tcp-pool fields");
|
|
assertCondition(!shared.includes("CoreHostSshInputMessage") && !shared.includes("ProviderHostSshDataMessage"), "shared protocol must remove old websocket ssh data contracts");
|
|
assertCondition(!providerRegistryTs.includes("host_ssh_data") && !providerRegistryTs.includes("host_ssh_exit"), "typescript backend registry must not forward old websocket ssh data messages");
|
|
assertCondition(compose.includes("UNIDESK_PROVIDER_DATA_PORT") && compose.includes("PROVIDER_DATA_POOL_SIZE"), "compose must wire provider data port and pool size");
|
|
assertCondition(config.includes('"providerData"') && config.includes('"port": 18084') && config.includes('"containerPort": 8082'), "config.json must declare providerData port pair");
|
|
assertCondition(backendCoreDockerfile.includes("ARG CARGO_BUILD_JOBS=1") && backendCoreDockerfile.includes('cargo build --release --jobs "${CARGO_BUILD_JOBS}"'), "backend-core main-server online build must keep cargo concurrency constrained");
|
|
assertCondition(compose.includes("UNIDESK_BACKEND_CORE_CARGO_BUILD_JOBS") && compose.includes("CARGO_BUILD_JOBS"), "compose must pass backend-core cargo build concurrency explicitly");
|
|
|
|
console.log(JSON.stringify({ ok: true, test: "ssh-data-tcp-pool-contract" }));
|