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-http.test.ts
1import assert from "node:assert/strict";2import http from "node:http";3import test, { type TestContext } from "node:test";45import { buildMultipart, wechatHttp } from "./wechat-http.ts";67interface ReceivedRequest {8method: string;9url: string;10headers: http.IncomingHttpHeaders;11body: Buffer;12}1314async function startEchoServer(t: TestContext): Promise<{ baseUrl: string; received: ReceivedRequest[] }> {15const received: ReceivedRequest[] = [];16const server = http.createServer((req, res) => {17const chunks: Buffer[] = [];18req.on("data", (chunk: Buffer) => chunks.push(chunk));19req.on("end", () => {20received.push({21method: req.method ?? "",22url: req.url ?? "",23headers: req.headers,24body: Buffer.concat(chunks),25});26res.statusCode = 200;27res.setHeader("Content-Type", "application/json");28res.end(JSON.stringify({ ok: true, echo: { url: req.url, method: req.method } }));29});30});3132await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));33const address = server.address();34if (!address || typeof address === "string") {35throw new Error("Failed to start echo server");36}37const baseUrl = `http://127.0.0.1:${address.port}`;3839t.after(async () => {40await new Promise<void>((resolve) => server.close(() => resolve()));41});4243return { baseUrl, received };44}4546test("wechatHttp performs a GET and passes through query string", async (t) => {47const { baseUrl, received } = await startEchoServer(t);48const res = await wechatHttp(`${baseUrl}/cgi-bin/token?grant_type=client_credential&appid=AID`);49assert.equal(res.status, 200);50const data = await res.json<{ ok: boolean; echo: { url: string; method: string } }>();51assert.equal(data.ok, true);52assert.equal(received.length, 1);53assert.equal(received[0]!.method, "GET");54assert.equal(received[0]!.url, "/cgi-bin/token?grant_type=client_credential&appid=AID");55assert.equal(received[0]!.body.length, 0);56});5758test("wechatHttp POST sends JSON body with content-length header", async (t) => {59const { baseUrl, received } = await startEchoServer(t);60const body = JSON.stringify({ articles: [{ title: "hi" }] });61const res = await wechatHttp(`${baseUrl}/cgi-bin/draft/add?access_token=T`, {62method: "POST",63headers: { "Content-Type": "application/json" },64body,65});66assert.equal(res.status, 200);67assert.equal(received.length, 1);68assert.equal(received[0]!.method, "POST");69assert.equal(received[0]!.headers["content-type"], "application/json");70assert.equal(received[0]!.headers["content-length"], String(Buffer.byteLength(body)));71assert.equal(received[0]!.body.toString("utf-8"), body);72});7374test("wechatHttp .text() returns the raw response body", async (t) => {75const { baseUrl } = await startEchoServer(t);76const res = await wechatHttp(`${baseUrl}/hello`);77const text = await res.text();78assert.match(text, /"ok":true/);79});8081test("wechatHttp .buffer() returns a Buffer of the response body", async (t) => {82const { baseUrl } = await startEchoServer(t);83const res = await wechatHttp(`${baseUrl}/`);84const buf = await res.buffer();85assert.ok(Buffer.isBuffer(buf));86assert.ok(buf.length > 0);87});8889test("buildMultipart produces a parsable multipart payload", () => {90const fileData = Buffer.from("ZZZ");91const { contentType, body } = buildMultipart([92{93name: "media",94filename: "image.png",95contentType: "image/png",96data: fileData,97},98]);99100const boundaryMatch = contentType.match(/^multipart\/form-data; boundary=(.+)$/);101assert.ok(boundaryMatch, `expected boundary in Content-Type, got ${contentType}`);102const boundary = boundaryMatch![1]!;103const text = body.toString("binary");104assert.ok(text.startsWith(`--${boundary}\r\n`), "body must start with opening boundary");105assert.ok(106text.endsWith(`--${boundary}--\r\n`),107"body must end with closing boundary",108);109assert.match(110text,111/Content-Disposition: form-data; name="media"; filename="image\.png"\r\n/,112);113assert.match(text, /Content-Type: image\/png\r\n/);114// The raw file bytes must appear verbatim after a blank line.115assert.ok(116text.includes("\r\n\r\nZZZ\r\n"),117"body must contain the raw file bytes after the part headers",118);119});120121test("wechatHttp accepts a multipart body produced by buildMultipart", async (t) => {122const { baseUrl, received } = await startEchoServer(t);123const fileData = Buffer.from("HELLO");124const multipart = buildMultipart([125{ name: "media", filename: "x.png", contentType: "image/png", data: fileData },126]);127const res = await wechatHttp(`${baseUrl}/cgi-bin/media/uploadimg?access_token=T`, {128method: "POST",129headers: { "Content-Type": multipart.contentType },130body: multipart.body,131});132assert.equal(res.status, 200);133assert.equal(received.length, 1);134assert.equal(received[0]!.method, "POST");135assert.match(received[0]!.headers["content-type"]!, /^multipart\/form-data; boundary=/);136assert.ok(received[0]!.body.includes(Buffer.from("HELLO")));137});138139