Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 62 additions & 7 deletions test/cli/install/bun-add.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,70 @@ import {
dummyBeforeAll,
dummyBeforeEach,
dummyRegistry,
makeGithubTarball,
package_dir,
requested,
root_url,
setGithubTarball,
setHandler,
} from "./dummy.registry";

beforeAll(dummyBeforeAll);
afterAll(dummyAfterAll);

// GitHub-dependency tests previously fetched real tarballs from api.github.com,
// which hard-fails CI whenever GitHub serves 5xx. They now point GITHUB_API_URL
// at the dummy server and consume fixtures that reproduce the layout GitHub
// returns (root dir `<owner>-<repo>-<short-sha>`, so the resolved commit is
// still asserted end-to-end).
const uglify_files = {
".gitattributes": "",
".github/workflows/ci.yml": "",
".gitignore": "",
"CONTRIBUTING.md": "",
"LICENSE": "",
"README.md": "",
"bin/uglifyjs": "#!/usr/bin/env node\n",
"lib/minify.js": "",
"package.json": JSON.stringify({
name: "uglify-js",
version: "3.14.1",
bin: { uglifyjs: "bin/uglifyjs" },
}),
"test/mocha.js": "",
"tools/node.js": "",
};
beforeAll(() => {
setGithubTarball("mishoo", "UglifyJS", "v3.14.1", makeGithubTarball("mishoo-UglifyJS-e219a9a", uglify_files));
setGithubTarball(
"dylan-conway",
"install-test-3",
"v1.0.0",
makeGithubTarball("dylan-conway-install-test-3-d8f1c19", {}),
);
setGithubTarball(
"dylan-conway",
"install-test-3",
"v1.0.1",
makeGithubTarball("dylan-conway-install-test-3-3e1c0a8", { "package.json": "{}" }),
);
setGithubTarball(
"dylan-conway",
"install-test-3",
"v1.0.2",
makeGithubTarball("dylan-conway-install-test-3-5c761c3", { "package.json": JSON.stringify({ name: "" }) }),
);
setGithubTarball(
"liz3",
"empty-bun-repo",
"",
makeGithubTarball("liz3-empty-bun-repo-b02fe7c", { "package.json": JSON.stringify({ name: "test-repo" }) }),
);
});
function githubEnv() {
return { ...env, GITHUB_API_URL: root_url };
}

expect.extend({
toHaveBins,
toBeValidBin,
Expand Down Expand Up @@ -1029,7 +1084,7 @@ it("should add dependency (GitHub)", async () => {
stdout: "pipe",
stdin: "pipe",
stderr: "pipe",
env,
env: githubEnv(),
});
const err = await stderr.text();
expect(err).not.toContain("error:");
Expand Down Expand Up @@ -1257,7 +1312,7 @@ it("should add aliased dependency (GitHub)", async () => {
stdout: "pipe",
stdin: "pipe",
stderr: "pipe",
env,
env: githubEnv(),
});
const err = await stderr.text();
expect(err).not.toContain("error:");
Expand Down Expand Up @@ -1338,7 +1393,7 @@ for (const { desc, dep } of gitNameTests) {
cwd: package_dir,
stdout: "ignore",
stderr: "pipe",
env,
env: githubEnv(),
});

const err = await stderr.text();
Expand Down Expand Up @@ -1732,7 +1787,7 @@ it("should not save git urls twice", async () => {
stdout: "pipe",
stdin: "pipe",
stderr: "pipe",
env,
env: githubEnv(),
});

expect(await exited1).toBe(0);
Expand All @@ -1748,7 +1803,7 @@ it("should not save git urls twice", async () => {
stdout: "pipe",
stdin: "pipe",
stderr: "pipe",
env,
env: githubEnv(),
});

expect(await exited2).toBe(0);
Expand Down Expand Up @@ -2003,7 +2058,7 @@ it("should add dependency without duplication (GitHub)", async () => {
stdout: "pipe",
stdin: "pipe",
stderr: "pipe",
env,
env: githubEnv(),
});
const err1 = await new Response(stderr1).text();
expect(err1).not.toContain("error:");
Expand Down Expand Up @@ -2063,7 +2118,7 @@ it("should add dependency without duplication (GitHub)", async () => {
stdout: "pipe",
stdin: "pipe",
stderr: "pipe",
env,
env: githubEnv(),
});
const err2 = await new Response(stderr2).text();
expect(err2).not.toContain("error:");
Expand Down
79 changes: 78 additions & 1 deletion test/cli/install/dummy.registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
* });
* ```
*/
import { file, Server } from "bun";
import { file, gzipSync, Server } from "bun";
import { tmpdirSync } from "harness";

let expect: (typeof import("bun:test"))["expect"];
Expand All @@ -48,6 +48,73 @@ let server: Server;
export let root_url: string;
export let check_npm_auth_type = { check: true };

// ============================================================================
// GitHub tarball fixtures (served at /repos/:owner/:repo/tarball/:ref)
// ============================================================================

/**
* Build a gzip'd tarball whose single top-level directory is `rootDir`,
* matching the layout GitHub's `api.github.com/repos/.../tarball/...` returns
* (root dir `<owner>-<repo>-<short-sha>`; bun reads the short sha from it).
*
* `files` maps paths relative to `rootDir` to file contents. A trailing `/`
* makes the entry a directory.
*/
export function makeGithubTarball(rootDir: string, files: Record<string, string>): Uint8Array {
const enc = new TextEncoder();
const blocks: Uint8Array[] = [];
const header = (name: string, size: number, type: "0" | "5") => {
const h = new Uint8Array(512);
h.set(enc.encode(name).slice(0, 100), 0);
h.set(enc.encode((type === "5" ? "0000755" : "0000644") + " "), 100);
h.set(enc.encode("0000000 "), 108);
h.set(enc.encode("0000000 "), 116);
h.set(enc.encode(size.toString(8).padStart(11, "0") + " "), 124);
h.set(enc.encode("00000000000 "), 136);
h.set(enc.encode(" "), 148);
h[156] = type.charCodeAt(0);
h.set(enc.encode("ustar"), 257);
h.set(enc.encode("00"), 263);
let sum = 0;
for (let i = 0; i < 512; i++) sum += h[i];
h.set(enc.encode(sum.toString(8).padStart(6, "0") + "\0 "), 148);
return h;
};
blocks.push(header(`${rootDir}/`, 0, "5"));
for (const [rel, body] of Object.entries(files)) {
const full = `${rootDir}/${rel}`;
if (rel.endsWith("/")) {
blocks.push(header(full, 0, "5"));
} else {
const bytes = enc.encode(body);
blocks.push(header(full, bytes.length, "0"));
blocks.push(bytes);
const pad = bytes.length % 512;
if (pad) blocks.push(new Uint8Array(512 - pad));
}
}
blocks.push(new Uint8Array(1024));
const total = blocks.reduce((n, b) => n + b.length, 0);
const tar = new Uint8Array(total);
let off = 0;
for (const b of blocks) {
tar.set(b, off);
off += b.length;
}
return gzipSync(tar);
}

const githubTarballs = new Map<string, Uint8Array>();

/**
* Register a tarball fixture to serve for `owner/repo` at `ref` when the
* install under test points `GITHUB_API_URL` at this server. `ref` may be
* empty (matches `bun add https://github.com/owner/repo` with no committish).
*/
export function setGithubTarball(owner: string, repo: string, ref: string, tarball: Uint8Array) {
githubTarballs.set(`/repos/${owner}/${repo}/tarball/${ref}`, tarball);
}

// ============================================================================
// Concurrent Test Context Support
// ============================================================================
Expand Down Expand Up @@ -340,6 +407,16 @@ export function dummyBeforeAll() {
server = Bun.serve({
async fetch(request) {
const url = request.url;
const pathname = new URL(url).pathname;

// GITHUB_API_URL fixture route (hermetic stand-in for api.github.com).
if (pathname.startsWith("/repos/") && pathname.includes("/tarball/")) {
const tarball = githubTarballs.get(pathname);
if (tarball) {
return new Response(tarball, { headers: { "Content-Type": "application/gzip" } });
}
return new Response(`No GitHub fixture registered for ${pathname}`, { status: 404 });
}

// Check if this is a prefixed request (for concurrent tests)
const prefixInfo = extractTestPrefix(url);
Expand Down
Loading