linkwarden-cli/test/e2e/e2e.test.ts
KaKi87 0a484efe2f Add Linkwarden CLI
Bun-based command-line client covering the Linkwarden API: links,
collections, tags, highlights, RSS, tokens, settings, import and export.
Includes unit tests, an in-memory mock server suite, and an opt-in e2e
suite for a real server.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-10 16:54:19 +01:00

80 lines
3.6 KiB
TypeScript

import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { createEnv, type Env } from "../helpers.ts";
/**
* Runs the CLI against a real Linkwarden server. Skipped unless LINKWARDEN_E2E_URL is set.
* Start one with: docker compose -f test/e2e/compose.yml -p lw-e2e up -d
* then: LINKWARDEN_E2E_URL=http://localhost:13000 bun test test/e2e
*/
const url = process.env.LINKWARDEN_E2E_URL;
const suite = url ? describe : describe.skip;
suite("real server", () => {
let env: Env;
const username = `e2e${Date.now().toString(36)}`;
beforeAll(async () => {
env = await createEnv();
const response = await fetch(`${url}/api/v1/users`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "E2E", username, password: "password123" }),
});
expect(response.ok).toBe(true);
});
afterAll(() => env.cleanup());
test("login, add, list, update, pin, delete", async () => {
const login = await env.run(["login", "--url", url!, "-u", username, "--password", "password123"]);
expect(login.code).toBe(0);
const parent = (await env.run(["collections", "create", "Parent", "--json"])).json();
const child = (await env.run(["collections", "create", "Child", "-p", "Parent", "--json"])).json();
expect(child.parentId).toBe(parent.id);
const added = await env.run(["add", "https://example.com", "-c", "Parent/Child", "-t", "one,two", "--json"]);
expect(added.code).toBe(0);
const id = added.json().created[0].id;
const listed = (await env.run(["links", "list", "-t", "one", "-c", "Parent/Child", "--all", "--json"])).json();
expect(listed.links.map((l: any) => l.id)).toEqual([id]);
expect((await env.run(["links", "update", String(id), "-n", "Renamed", "--remove-tag", "two", "--add-tag", "three"])).code).toBe(0);
const got = (await env.run(["links", "get", String(id), "--json"])).json();
expect(got.name).toBe("Renamed");
expect(got.tags.map((t: any) => t.name).sort()).toEqual(["one", "three"]);
await env.run(["links", "pin", String(id)]);
expect((await env.run(["links", "list", "--pinned", "-q"])).stdout.trim()).toBe(String(id));
await env.run(["links", "unpin", String(id)]);
expect((await env.run(["links", "list", "--pinned", "-q"])).stdout.trim()).toBe("");
expect((await env.run(["tags", "merge", "one", "three", "--into", "merged"])).code).toBe(0);
expect((await env.run(["tags", "list", "-q"])).stdout.trim().split("\n").length).toBeGreaterThan(0);
expect((await env.run(["links", "delete", String(id), "-y"])).code).toBe(0);
expect((await env.run(["links", "get", String(id)])).code).toBe(1);
});
test("export and re-import a backup", async () => {
const out = `${env.dir}/backup.json`;
expect((await env.run(["export", "-o", out])).code).toBe(0);
expect((await env.run(["import", out])).code).toBe(0);
});
test("PDF upload round trip", async () => {
const pdf = `${env.dir}/doc.pdf`;
await Bun.write(pdf, "%PDF-1.4\n1 0 obj<</Type/Catalog>>endobj\ntrailer<</Root 1 0 R>>\n%%EOF");
const added = (await env.run(["add", "-f", pdf, "--json"])).json();
const id = added.created[0].id;
const dl = `${env.dir}/dl.pdf`;
expect((await env.run(["links", "download", String(id), "-o", dl])).code).toBe(0);
expect(await Bun.file(dl).text()).toStartWith("%PDF");
});
test("tokens authenticate", async () => {
const secret = (await env.run(["tokens", "create", "e2e", "-q"])).stdout.trim();
const who = await env.run(["whoami", "--json"], { LINKWARDEN_URL: url, LINKWARDEN_TOKEN: secret });
expect(who.json().username).toBe(username);
});
});