Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Post articles and image-text content to WeChat Official Account via API or Chrome CDP, with markdown-to-WeChat HTML conversion.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/wechat-remote-publish.test.ts
1import assert from "node:assert/strict";2import net from "node:net";3import test from "node:test";45import {6buildSshArgs,7findFreePort,8normalizeRemoteConfig,9} from "./wechat-remote-publish.ts";1011test("normalizeRemoteConfig requires a host", () => {12assert.throws(13() => normalizeRemoteConfig({ host: "" }),14/Remote publish host is required/,15);16assert.throws(17() => normalizeRemoteConfig({ host: " " }),18/Remote publish host is required/,19);20});2122test("normalizeRemoteConfig applies user/port defaults and trims host", () => {23const result = normalizeRemoteConfig({ host: " example.com " });24assert.equal(result.host, "example.com");25assert.equal(result.user, "root");26assert.equal(result.port, 22);27assert.equal(result.identityFile, undefined);28assert.equal(result.knownHostsFile, undefined);29assert.equal(result.strictHostKeyChecking, undefined);30assert.equal(result.connectTimeout, undefined);31assert.equal(result.proxyJump, undefined);32});3334test("normalizeRemoteConfig preserves explicit user, port, and SSH options", () => {35const result = normalizeRemoteConfig({36host: "example.com",37user: "deploy",38port: 2222,39identityFile: "/home/me/.ssh/id_ed25519",40knownHostsFile: "/home/me/.ssh/known_hosts",41strictHostKeyChecking: "accept-new",42connectTimeout: 15,43proxyJump: "bastion.example.com",44});45assert.equal(result.user, "deploy");46assert.equal(result.port, 2222);47assert.equal(result.identityFile, "/home/me/.ssh/id_ed25519");48assert.equal(result.knownHostsFile, "/home/me/.ssh/known_hosts");49assert.equal(result.strictHostKeyChecking, "accept-new");50assert.equal(result.connectTimeout, 15);51assert.equal(result.proxyJump, "bastion.example.com");52});5354test("normalizeRemoteConfig rejects invalid port", () => {55assert.throws(56() => normalizeRemoteConfig({ host: "example.com", port: 0 }),57/Invalid remote publish port/,58);59assert.throws(60() => normalizeRemoteConfig({ host: "example.com", port: 65536 }),61/Invalid remote publish port/,62);63assert.throws(64() => normalizeRemoteConfig({ host: "example.com", port: 1.5 }),65/Invalid remote publish port/,66);67});6869test("normalizeRemoteConfig rejects invalid connect timeout", () => {70assert.throws(71() => normalizeRemoteConfig({ host: "example.com", connectTimeout: 0 }),72/Invalid remote_publish_connect_timeout/,73);74assert.throws(75() => normalizeRemoteConfig({ host: "example.com", connectTimeout: -3 }),76/Invalid remote_publish_connect_timeout/,77);78});7980test("normalizeRemoteConfig rejects invalid strict host key checking", () => {81assert.throws(82() =>83normalizeRemoteConfig({84host: "example.com",85// eslint-disable-next-line @typescript-eslint/no-explicit-any86strictHostKeyChecking: "maybe" as any,87}),88/Invalid remote_publish_strict_host_key_checking/,89);90});9192test("normalizeRemoteConfig falls back to default user when blank string provided", () => {93const result = normalizeRemoteConfig({ host: "example.com", user: " " });94assert.equal(result.user, "root");95});9697test("buildSshArgs emits the whitelisted minimum set", () => {98const args = buildSshArgs(99{ host: "example.com", user: "root", port: 22 },1001080,101);102assert.deepEqual(args, [103"-N",104"-T",105"-D", "127.0.0.1:1080",106"-o", "ExitOnForwardFailure=yes",107"-o", "ServerAliveInterval=30",108"-o", "ServerAliveCountMax=3",109"-p", "22",110"[email protected]",111]);112});113114test("buildSshArgs threads optional ssh options in stable order", () => {115const args = buildSshArgs(116{117host: "example.com",118user: "deploy",119port: 2222,120identityFile: "/p/id_ed25519",121knownHostsFile: "/p/known_hosts",122strictHostKeyChecking: "accept-new",123connectTimeout: 12,124proxyJump: "bastion.example.com",125},1261080,127);128assert.deepEqual(args, [129"-N",130"-T",131"-D", "127.0.0.1:1080",132"-o", "ExitOnForwardFailure=yes",133"-o", "ServerAliveInterval=30",134"-o", "ServerAliveCountMax=3",135"-p", "2222",136"-i", "/p/id_ed25519",137"-o", "UserKnownHostsFile=/p/known_hosts",138"-o", "StrictHostKeyChecking=accept-new",139"-o", "ConnectTimeout=12",140"-J", "bastion.example.com",141"[email protected]",142]);143});144145test("buildSshArgs does not emit raw ssh options for unknown fields", () => {146const args = buildSshArgs(147{148host: "example.com",149user: "root",150port: 22,151// No extra unknown keys are accepted — typed config is the whitelist.152},1531080,154);155assert.equal(156args.filter((a) => a === "-o" || a.startsWith("--")).length,1573, // ExitOnForwardFailure, ServerAliveInterval, ServerAliveCountMax (and only those)158"buildSshArgs must only emit the three baseline -o options when no extras given",159);160});161162test("buildSshArgs rejects invalid SOCKS port", () => {163assert.throws(164() => buildSshArgs({ host: "example.com", user: "root", port: 22 }, 0),165/Invalid SOCKS port/,166);167assert.throws(168() => buildSshArgs({ host: "example.com", user: "root", port: 22 }, 70_000),169/Invalid SOCKS port/,170);171});172173test("findFreePort returns a usable loopback port", async () => {174const port = await findFreePort();175assert.ok(port > 0 && port < 65536, `expected valid port, got ${port}`);176177await new Promise<void>((resolve, reject) => {178const server = net.createServer();179server.unref();180server.once("error", reject);181server.listen(port, "127.0.0.1", () => {182server.close((err) => (err ? reject(err) : resolve()));183});184});185});186