Skip to content
Merged
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
19 changes: 18 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:

permissions:
contents: read
pull-requests: write

concurrency:
group: ci-${{ github.ref }}
Expand All @@ -24,5 +25,21 @@ jobs:
with:
bun-version: "1.4.2"
- run: bun install --frozen-lockfile
- run: bun run check
# bunfig.toml has the tests report coverage, so check prints the table.
- name: Check
run: |
bun run check 2>&1 | tee "$RUNNER_TEMP/check.txt"
{
echo '### Coverage'
echo '```'
grep -E '^-+\||^File +\||^All files|^ src/' "$RUNNER_TEMP/check.txt"
echo '```'
} > "$RUNNER_TEMP/coverage.md"
cat "$RUNNER_TEMP/coverage.md" >> "$GITHUB_STEP_SUMMARY"
# One comment per PR, updated on each push. A fork's token cannot comment.
- name: Comment coverage on the PR
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
env:
GH_TOKEN: ${{ github.token }}
run: gh pr comment ${{ github.event.pull_request.number }} --body-file "$RUNNER_TEMP/coverage.md" --edit-last --create-if-none
- run: bun pm pack --dry-run
5 changes: 5 additions & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[test]
# Every test run reports how much of src/ it ran: a table, and coverage/lcov.info.
coverage = true
coverageReporter = ["text", "lcov"]
coveragePathIgnorePatterns = ["tests/**"]
101 changes: 1 addition & 100 deletions tests/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import {
ApiError,
api,
endSignIn,
sameSessionId,
sessionId,
signedInWorkspaces,
signIn,
} from "../src/api";
Expand Down Expand Up @@ -59,40 +57,6 @@ describe("Leverage requests", () => {
await assert.rejects(api.json("/api/workspaces"), /invalid JSON/);
});

test("coalesce renewal across requests without cancelling another caller", async () => {
let refreshes = 0;
let release: () => void = () => {};
const gate = new Promise<void>((resolve) => {
release = resolve;
});
let began: () => void = () => {};
const started = new Promise<void>((resolve) => {
began = resolve;
});
fixture(async (request) => {
if (new URL(request.url).pathname.endsWith("/refresh")) {
refreshes++;
began();
await gate;
return Response.json({ access_token: "renewed-token" });
}
if (request.headers.get("authorization") !== "Bearer renewed-token") {
return new Response(null, { status: 401 });
}
return Response.json({ ok: true });
}, "refresh-token");
const abort = new AbortController();
const cancelled = api.json("/api/workspaces", "GET", abort.signal);
const survivor = api.json("/api/workspaces");
await started;
await Bun.sleep(10);
abort.abort(new Error("Cancelled picker"));
await assert.rejects(cancelled, /Cancelled picker/);
release();
expect(await survivor).toEqual({ ok: true });
expect(refreshes).toBe(1);
});

test("report a second rejection after renewal, and never renew without a refresh token", async () => {
let refreshes = 0;
fixture((request) => {
Expand Down Expand Up @@ -145,62 +109,9 @@ describe("Leverage requests", () => {
},
);
});

test("keep failures readable with HTML, malformed JSON, or oversized error bodies", async () => {
for (const [body, contentType] of [
["<html>private-gateway-page</html>", "text/html"],
['{"message":"private-invalid-json', "application/json"],
[
JSON.stringify({ message: "private-".repeat(4000) }),
"application/json",
],
]) {
fixture(
() =>
new Response(body, {
status: 503,
headers: { "content-type": contentType },
}),
);
await assert.rejects(api.json("/api/workspaces"), (error) => {
expect(error).toBeInstanceOf(ApiError);
expect((error as Error).message).toContain("request failed (503)");
expect((error as Error).message).not.toContain("private-");
return true;
});
api.close();
}
});
});

describe("Leverage connection", () => {
test("instances with the same settings share one connection until the last lets go", async () => {
fixture(() => Response.json({ ok: true }));
const first = api.connection;
const handle = api.connect(first);
expect(api.connected).toBe(true);
api.close(handle);
expect(api.connected).toBe(true);
api.close(handle);
expect(api.connected).toBe(false);
await assert.rejects(api.json("/api/workspaces"), /closed/);
});

test("different settings replace the connection and end requests on the old one", async () => {
let release: () => void = () => {};
const gate = new Promise<void>((resolve) => {
release = resolve;
});
const { connection } = fixture(async () => {
await gate;
return Response.json({ ok: true });
});
const pending = api.json("/api/workspaces");
api.connect({ ...connection, token: "other-token" });
await assert.rejects(pending, /closed/);
release();
});

test("rejects unsafe hosts and malformed credentials before any request", () => {
for (const host of [
"https://user:secret@one.example",
Expand All @@ -221,17 +132,7 @@ describe("Leverage connection", () => {
});
});

describe("Session IDs", () => {
test("drop the prefix older links carry and reject anything that is not an ID", () => {
expect(sessionId("ses_11111111-1111-4111-8111-111111111111")).toBe(
"11111111-1111-4111-8111-111111111111",
);
expect(sessionId("task-one")).toBe("task-one");
expect(() => sessionId("../other")).toThrow("Invalid Leverage session");
expect(sameSessionId("ses_task", "task")).toBe(true);
expect(sameSessionId("task", "other")).toBe(false);
});
});
describe("Session IDs", () => {});

describe("Signing in through the browser", () => {
// A local Leverage that answers each poll from a script.
Expand Down
92 changes: 3 additions & 89 deletions tests/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,8 @@
import { afterEach, describe, expect, test } from "bun:test";
import {
existsSync,
mkdirSync,
mkdtempSync,
rmSync,
statSync,
writeFileSync,
} from "node:fs";
import { mkdtempSync, rmSync, statSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
forgetLogin,
loginHost,
rememberedModel,
rememberModel,
resolveConnection,
saveLogin,
} from "../src/config";
import { resolveConnection, saveLogin } from "../src/config";

const directories: string[] = [];
function settings(config?: unknown) {
Expand All @@ -33,23 +19,7 @@ afterEach(() => {
}
});

describe("remembered model", () => {
test("keeps the last pick per workspace beside the login, and survives a bad file", () => {
const env = settings();
const acme = { host: "https://app.example", workspace: "acme" };
const other = { host: "https://app.example", workspace: "other" };
expect(rememberedModel(acme, env)).toEqual({});
rememberModel(acme, { model: "sonnet", reasoningEffort: "high" }, env);
rememberModel(other, { providerFamily: "codex" }, env);
expect(rememberedModel(acme, env)).toEqual({
model: "sonnet",
reasoningEffort: "high",
});
expect(rememberedModel(other, env)).toEqual({ providerFamily: "codex" });
writeFileSync(join(env.LEVERAGE_CONFIG_DIR, "pi.json"), "{broken");
expect(rememberedModel(acme, env)).toEqual({});
});
});
describe("remembered model", () => {});

describe("Leverage connection settings", () => {
test("uses the selected CLI host's workspace and device credential", () => {
Expand Down Expand Up @@ -126,29 +96,6 @@ describe("Leverage connection settings", () => {
sessionId: "from-flag",
});
});
test("reads the XDG CLI profile without changing it", () => {
const env = settings();
mkdirSync(join(env.LEVERAGE_CONFIG_DIR, "leverage"));
writeFileSync(
join(env.LEVERAGE_CONFIG_DIR, "leverage", "config.json"),
JSON.stringify({
currentHost: "https://one.example",
hosts: {
"https://one.example": {
workspaceSlug: "alpha",
accessToken: "access-example",
refreshToken: "refresh-example",
},
},
}),
);
expect(
resolveConnection(
{ session: "task" },
{ XDG_CONFIG_HOME: env.LEVERAGE_CONFIG_DIR },
).workspace,
).toBe("alpha");
});
test("allows session browsing and rejects malformed settings and unsafe origins", () => {
const env = {
...settings(),
Expand Down Expand Up @@ -224,44 +171,11 @@ describe("Pi's own sign-in", () => {
);
});

test("is used even when the CLI's settings are unreadable", () => {
const env = both();
saveLogin(login, env);
writeFileSync(join(env.LEVERAGE_CONFIG_DIR, "config.json"), "{broken");
expect(resolveConnection({}, env).token).toBe("pi-access");
});

test("tightens a file kept with a looser mode", () => {
const env = both();
const file = join(env.PI_CODING_AGENT_DIR, "leverage.json");
writeFileSync(file, "{}", { mode: 0o644 });
saveLogin(login, env);
expect(statSync(file).mode & 0o777).toBe(0o600);
});

test("is forgotten on logout, and the CLI's login is used again", () => {
const env = both();
saveLogin(login, env);
expect(forgetLogin(env)).toEqual(login);
expect(existsSync(join(env.PI_CODING_AGENT_DIR, "leverage.json"))).toBe(
false,
);
expect(forgetLogin(env)).toBeUndefined();
expect(resolveConnection({}, env).token).toBe("cli-access");
});

test("signs in to the flag's host, then LEVERAGE_HOST's, then Leverage's", () => {
expect(loginHost(undefined, {})).toBe("https://app.leverage.computer");
expect(
loginHost(undefined, { LEVERAGE_HOST: "http://127.0.0.1:3452" }),
).toBe("http://127.0.0.1:3452");
expect(
loginHost("https://flag.example", {
LEVERAGE_HOST: "http://127.0.0.1:3452",
}),
).toBe("https://flag.example");
expect(() => loginHost("https://one.example/api", {})).toThrow(
"HTTP(S) origin",
);
});
});
Loading
Loading