c946b73d11
Remote clients (docs/plans/remote-clients/README.md, B1–B3 for the Claude pool): - server/hub/ holds the hub-only code (devices, remote sessions, upload ingest, LAN gateway), required only in server mode; hub-boundary.test.js keeps local code from importing it. - A local install enrolls with `agenthub remote login` or Settings → AI tools → Company hub; the "Company account" AI Proxy (reserved name agenthub.company) sends a session through the hub's gateway, which adds a pool account's token — no credential reaches the machine. Only those sessions are uploaded, hourly and at their end; secret-shaped strings are redacted on the client. - Gateway refuses: unknown device (401), a message without a session id, an unregistered session under AGENTHUB_REMOTE_GATE=block, a user over quota (EQUOTA), a purged session (410). Device tokens 90 days, conversations 30. - New alert rule type remote_unmonitored: a company-account session its device never registered; a late registration clears the flag instead. - The hub imports a session once per upload round (chunks carry `more`), not once per chunk. Artifacts: - Share an artifact with chosen people (artifact_shares, PATCH sharedWith, GET /api/artifacts/people) besides everyone; a "Shared with you" tab. Only the owner or an admin sees or edits who it is shared with; audited. - Folders: each account's own arrangement (agenthub.artifacts.folders pref). - A claude.ai page keeps a copy viewable without the publishing account: its HTML file (and supporting files) snapshotted at publish, or the copy Claude saves for a Doc / Slides / Design in artifacts-export/ (a synchronous PostToolUse hint hook, scripts/artifact-export-hint.js, admin switch), attached to the page at Stop. Viewed in HtmlFrame's sandbox, Markdown, or pdf.js (pdfjs-dist 6.4.299, legacy build, loaded only for a PDF). Fixes: - tmux: a cached /proc/<pid>/exe of an exited server falls back to PATH's tmux (stop the last pane, start again within 10 s failed with ENOENT). - shelves: a shelf restores beside changes in other files. - chat: a finished reply ends the turn even with no hook at all. - Settings → AI tools: profile list display tweaks.
59 lines
2.0 KiB
TypeScript
59 lines
2.0 KiB
TypeScript
/**
|
|
* @file Tests for PdfView: pdf.js (mocked — jsdom has no canvas) draws one
|
|
* canvas per page, scripting off, and a PDF that cannot be read says so.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
import { render, screen, waitFor } from "@testing-library/react";
|
|
import "../../i18n";
|
|
|
|
const getDocument = vi.fn();
|
|
vi.mock("pdfjs-dist/legacy/build/pdf.mjs", () => ({
|
|
GlobalWorkerOptions: { workerSrc: "" },
|
|
getDocument: (...a: unknown[]) => getDocument(...a),
|
|
}));
|
|
|
|
import { PdfView } from "../PdfView";
|
|
|
|
const realFetch = globalThis.fetch;
|
|
beforeEach(() => {
|
|
getDocument.mockReset();
|
|
globalThis.fetch = vi.fn(
|
|
async () => new Response(new Uint8Array([37, 80, 68, 70]))
|
|
) as typeof fetch;
|
|
});
|
|
afterEach(() => {
|
|
globalThis.fetch = realFetch;
|
|
});
|
|
|
|
describe("PdfView", () => {
|
|
it("draws every page on its own canvas, with XFA off", async () => {
|
|
const render1 = vi.fn(() => ({ promise: Promise.resolve() }));
|
|
getDocument.mockReturnValue({
|
|
destroy: vi.fn(),
|
|
promise: Promise.resolve({
|
|
numPages: 3,
|
|
getPage: async () => ({
|
|
getViewport: ({ scale }: { scale: number }) => ({
|
|
width: 600 * scale,
|
|
height: 800 * scale,
|
|
}),
|
|
render: render1,
|
|
}),
|
|
}),
|
|
});
|
|
const { container } = render(<PdfView url="/api/artifacts/1/raw?path=a.pdf" title="a.pdf" />);
|
|
await waitFor(() => expect(container.querySelectorAll("canvas")).toHaveLength(3));
|
|
expect(render1).toHaveBeenCalledTimes(3);
|
|
expect(getDocument.mock.calls[0]![0]).toMatchObject({ enableXfa: false });
|
|
expect(screen.getByLabelText("Page 2")).toBeInTheDocument();
|
|
});
|
|
|
|
it("says so when the file cannot be read", async () => {
|
|
getDocument.mockReturnValue({ destroy: vi.fn(), promise: Promise.reject(new Error("bad")) });
|
|
render(<PdfView url="/x" title="x.pdf" />);
|
|
expect(await screen.findByText(/could not be shown/)).toBeInTheDocument();
|
|
});
|
|
});
|