testBigPushesTravelCompressed

tcmts.cli.test.e2e.sync.testBigPushesTravelCompressed · #nbtixv2u · term

A big push goes compressed to a server that says it takes compressed bodies (and small requests, and those before it has said so, as they are); a compressed body is held to the server's size limit unpacked.

import { tempDir, withChildren } from "#codebase/tcmts/cli/test/e2e/harness";
import { codebase, startServer } from "#codebase/tcmts/cli/test/e2e/sync";
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { DatabaseSync } from "node:sqlite";
import { gzipSync } from "node:zlib";

export const testBigPushesTravelCompressed = () =>
  withChildren(async (keep) => {
    const root = tempDir("sync gzip");
    const server = codebase(root, "server");
    fs.rmSync(path.join(server.dir, "scratch.ts"));
    const url = await startServer(keep, server.dir, "tok");
    const call = (route: string, body: BodyInit, headers: Record<string, string> = {}) =>
      fetch(`${url}/${route}`, {
        method: "POST",
        headers: { authorization: "Bearer tok", ...headers },
        body,
      });
    const gzip = (text: string) => new Uint8Array(gzipSync(text));
    const plain = await call("missing", JSON.stringify({ components: ["abc"] }));
    assert.match(plain.headers.get("accept-encoding") ?? "", /gzip/, "it says it takes gzip");
    const zipped = await call("missing", gzip(JSON.stringify({ components: ["abc"] })), {
      "content-encoding": "gzip",
    });
    assert.deepEqual(await zipped.json(), { missing: ["abc"] });
    // 100 MB of nothing packs into a small body; unpacked, it's over the limit
    const bomb = await call("missing", gzip(`{"components": []${" ".repeat(100 * 1024 * 1024)}}`), {
      "content-encoding": "gzip",
    });
    assert.equal(bomb.status, 413);
    assert.equal((await call("missing", "{}", { "content-encoding": "br" })).status, 415);

    // a push through a proxy that notes how each request came
    const proxy = keep(
      spawn(process.execPath, [
        "-e",
        `const http = require("node:http");
         http.createServer((req, res) => {
           console.log(req.url + " " + (req.headers["content-encoding"] ?? "identity"));
           req.pipe(http.request(new URL(req.url, ${JSON.stringify(url)}), { method: req.method, headers: req.headers }, (r) => {
             res.writeHead(r.statusCode, r.headers);
             r.pipe(res);
           }));
         }).listen(0, "127.0.0.1", function () { console.log("port " + this.address().port); });`,
      ]),
    );
    let seen = "";
    proxy.stdout!.setEncoding("utf8").on("data", (d: string) => (seen += d));
    const port = await new Promise<string>((resolve) => {
      const check = () => {
        const m = /port (\d+)/.exec(seen);
        if (m) resolve(m[1]!);
        else setTimeout(check, 20);
      };
      check();
    });
    const a = codebase(root, "a");
    a.scratch(
      Array.from(
        { length: 40 },
        (_, i) => `export const text${i} = ${JSON.stringify(`${i} `.repeat(1000))};\n`,
      ).join("") + "export const twice = (s: string): string => s + s;\n",
    );
    a.tcm("add");
    a.tcm("remote", "add", "origin", `http://127.0.0.1:${port}`, "--token", "tok");
    assert.match(a.tcm("push"), /sent 41 definition\(s\)/);
    // (the push ran synchronously: what the proxy wrote meanwhile is read now)
    await new Promise((r) => setTimeout(r, 300));
    assert.match(seen, /^\/whoami identity$/m, "before the server says it takes gzip");
    assert.match(seen, /^\/put gzip$/m, "definitions, compressed");
    assert.match(seen, /^\/set-branch identity$/m, "a small body as it is");
    assert.match(server.tcm("ls"), /text39/);
    // sent lean (the IR's head, no shape), kept whole: the server's IR is what the source gives
    const names = (await (await call("branch", JSON.stringify({ name: "main" }))).json()).branch
      .names as { path: string; hash: string }[];
    const text7 = names.find((n) => n.path === "text7")!.hash;
    const [kept] = (await (await call("get", JSON.stringify({ hashes: [text7] }))).json()).records;
    assert.ok(kept.ir.includes(JSON.stringify("7 ".repeat(1000))), "its IR, whole");
    // sent without a type shape, it has one: worked out for what the branch names
    const twice = names.find((n) => n.path === "twice")!.hash;
    // (read from the database: the server, another process, keeps what it read)
    const shapeOf = () => {
      const db = new DatabaseSync(path.join(server.dir, ".tcm", "codebase.sqlite"), {
        readOnly: true,
      });
      try {
        return db.prepare("SELECT value FROM metadata WHERE hash = ? AND key = 'shape'").get(twice);
      } finally {
        db.close();
      }
    };
    assert.ok(shapeOf(), "worked out as the server's branch came to name it");
    assert.match(server.tcm("find", ":(s: string) => string"), /twice/);
  })

uses