Sign In

@conorbronsdon/substack-mcp

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@conorbronsdon/substack-mcp - npm Package Compare versions

Comparing version
0.6.1
to
0.6.2
+30
-1
CHANGELOG.md

@@ -9,4 +9,33 @@ # Changelog

## [Unreleased]
## [0.6.2] - 2026-08-03
### Added
- A 30-second deadline on every request to Substack, overridable with
`SUBSTACK_REQUEST_TIMEOUT_MS`. Node applies no request timeout by default —
only undici's 10s *connect* timeout — so a host that accepted the connection
and then went silent could hang a tool call indefinitely. A request that hits
the deadline now raises `TimeoutError`, which names the endpoint and the
limit, instead of surfacing a bare `DOMException`. (#31)
- `SIGTERM`/`SIGINT` handlers that close the transport and exit 0. Node installs
no handler by default and the kernel ignores default-disposition signals for
PID 1, so `docker stop` waited out its full 10s grace period and then
SIGKILLed (exit 137). Measured against the published Dockerfile: 10,334ms and
exit 137 before, 357ms and exit 0 after. No init process (`tini`) needed. (#31)
### Fixed
- The startup auth check no longer blocks the MCP handshake. It ran *before*
`server.connect()`, so a host that hangs rather than refuses stalled
`initialize` for undici's full connect timeout with no output. Measured in a
container against a blackholed host: `initialize` took 11,182ms before, 822ms
after; the credential-free path (what the Docker MCP registry check exercises)
is unchanged at 14 tools and exit 0. The check still runs — it just runs after
connect, and it only ever warned. (#31)
- `tsconfig.json` now excludes `src/__tests__` from the build. `tsc` was emitting
the test suite into `dist/`, where its `vitest` imports cannot resolve because
`npm ci --omit=dev` correctly drops vitest — dead code in the published
package, and a stale `dist/` also made `vitest run` collect every suite twice.
The Dockerfile's `RUN rm -rf dist/__tests__` workaround is removed with it.
Tests are still type-checked: `npm run lint` now uses `tsconfig.lint.json`,
which covers all of `src/`. (#31)
## [0.6.1] - 2026-08-03

@@ -13,0 +42,0 @@

+2
-2

@@ -24,4 +24,4 @@ # substack-mcp

npm ci
npm run lint # tsc --noEmit (type-check)
npm run build # tsc (outputs to dist/)
npm run lint # tsc --noEmit -p tsconfig.lint.json (type-checks all of src/, tests included)
npm run build # tsc (outputs to dist/; excludes src/__tests__)
npm test # vitest run

@@ -28,0 +28,0 @@ ```

@@ -7,3 +7,6 @@ # Contributing

- `npm run lint` passes (this runs `tsc --noEmit` — the type checker is the linter).
- `npm run lint` passes (this runs `tsc --noEmit -p tsconfig.lint.json` — the type
checker is the linter). It covers `src/__tests__` too, which the build config
excludes so `tsc` doesn't emit tests into `dist/`; nothing else type-checks
them, since vitest strips types rather than checking them.
- `npm test` passes (`vitest run`). Add or update tests for any behavior you change.

@@ -10,0 +13,0 @@ - The safe-by-design boundary stays: no publish, no delete, no schedule for

import { SubstackPost, SubstackDraft, SubstackComment, SubstackNote, NoteCreatePayload, NoteAttachment, DraftUpdatePayload, ImageUploadResult, SubstackSection, SubstackScheduledPost } from "./types.js";
/**
* Per-request deadline applied to every outbound fetch.
*
* Node's default is no request timeout at all — only undici's 10s *connect*
* timeout — so a host that accepts the connection and then goes quiet hangs the
* call forever. 30s is long enough for a base64 image upload on a slow link and
* short enough that a dropped connection becomes an error instead of a stall.
* Override per client via the constructor (SUBSTACK_REQUEST_TIMEOUT_MS at the
* entry point).
*/
export declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
/**
* Largest `limit` Substack's post_management endpoints will accept.

@@ -36,3 +47,4 @@ *

private userAgent;
constructor(publicationUrl: string, sessionToken: string, userId: string, userAgent?: string);
private timeoutMs;
constructor(publicationUrl: string, sessionToken: string, userId: string, userAgent?: string, timeoutMs?: number);
private request;

@@ -39,0 +51,0 @@ validateAuth(): Promise<{

@@ -1,3 +0,14 @@

import { mapHttpStatusToError, extractErrorDetail } from "../utils/errors.js";
import { mapHttpStatusToError, extractErrorDetail, TimeoutError, isAbortError } from "../utils/errors.js";
/**
* Per-request deadline applied to every outbound fetch.
*
* Node's default is no request timeout at all — only undici's 10s *connect*
* timeout — so a host that accepts the connection and then goes quiet hangs the
* call forever. 30s is long enough for a base64 image upload on a slow link and
* short enough that a dropped connection becomes an error instead of a stall.
* Override per client via the constructor (SUBSTACK_REQUEST_TIMEOUT_MS at the
* entry point).
*/
export const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
/**
* Largest `limit` Substack's post_management endpoints will accept.

@@ -46,3 +57,4 @@ *

userAgent;
constructor(publicationUrl, sessionToken, userId, userAgent) {
timeoutMs;
constructor(publicationUrl, sessionToken, userId, userAgent, timeoutMs) {
this.publicationUrl = publicationUrl.replace(/\/$/, "");

@@ -60,2 +72,9 @@ // Substack uses connect.sid on custom domains, substack.sid on substack.com

"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
// A zero, negative, or non-finite override would abort every request
// instantly (or never), so it falls back to the default rather than
// silently breaking the client.
this.timeoutMs =
typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0
? timeoutMs
: DEFAULT_REQUEST_TIMEOUT_MS;
if (isNaN(this.userId)) {

@@ -83,7 +102,18 @@ throw new Error(`Invalid SUBSTACK_USER_ID: "${userId}" — must be a number`);

}
const response = await fetch(url, {
...options,
headers,
redirect: "follow",
});
// The signal is set last so it can't be overridden by `options`: every
// request through this method is bounded, no exceptions.
let response;
try {
response = await fetch(url, {
...options,
headers,
redirect: "follow",
signal: AbortSignal.timeout(this.timeoutMs),
});
}
catch (err) {
if (isAbortError(err))
throw new TimeoutError(url, this.timeoutMs);
throw err;
}
if (!response.ok) {

@@ -160,2 +190,3 @@ const body = await response.text().catch(() => "unknown error");

redirect: "follow",
signal: AbortSignal.timeout(this.timeoutMs),
});

@@ -162,0 +193,0 @@ if (!res.ok)

@@ -1,1 +0,1 @@

{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}

@@ -6,2 +6,52 @@ #!/usr/bin/env node

import { resolveCredentials } from "./auth/resolve-credentials.js";
/**
* Wire every way this process can be asked to stop to one clean transport close.
*
* SIGTERM/SIGINT: nothing installs a handler by default, and the kernel ignores
* default-disposition signals for PID 1 — so in a container `docker stop` waits
* out its full 10s grace period and then SIGKILLs. Handling the signal
* in-process keeps the image free of an init wrapper.
*
* stdin EOF: the normal end of an MCP stdio session. The transport doesn't
* watch for it; the process just exits once the event loop drains, which any
* still-pending request can hold open for the length of its network timeout.
* Closing explicitly makes the ordinary shutdown immediate either way.
*
* Idempotent by design: a second trigger arriving mid-shutdown is dropped, and
* a `close()` that never settles is capped by a forced exit.
*/
function installShutdownHandlers(close) {
let shuttingDown = false;
const shutdown = async (trigger) => {
if (shuttingDown)
return;
shuttingDown = true;
console.error(`Shutting down (${trigger}).`);
// Unref'd so it can never hold the event loop open by itself: it only
// fires when something *else* is still keeping the process alive, which is
// precisely the hang this guards against.
setTimeout(() => process.exit(0), 2000).unref();
try {
await close();
}
catch (err) {
console.error("Error while closing transport:", err instanceof Error ? err.message : String(err));
}
process.exit(0);
};
process.on("SIGTERM", () => void shutdown("SIGTERM"));
process.on("SIGINT", () => void shutdown("SIGINT"));
process.stdin.once("end", () => void shutdown("stdin EOF"));
}
/** Parse SUBSTACK_REQUEST_TIMEOUT_MS, warning (not failing) on garbage. */
function resolveTimeoutMs(raw) {
if (raw === undefined || raw === "")
return undefined;
const parsed = Number(raw);
if (!Number.isFinite(parsed) || parsed <= 0) {
console.error(`Warning: ignoring invalid SUBSTACK_REQUEST_TIMEOUT_MS="${raw}" — using the default.`);
return undefined;
}
return parsed;
}
async function main() {

@@ -20,7 +70,20 @@ // Env vars take precedence; a stored session (from `substack-mcp-login`)

const userAgent = process.env.SUBSTACK_USER_AGENT;
const timeoutMs = resolveTimeoutMs(process.env.SUBSTACK_REQUEST_TIMEOUT_MS);
// The client constructor rejects a non-numeric user id; fall back to "0" so
// startup surfaces the friendly missing-credentials warning above instead of
// throwing when nothing is configured yet.
const client = new SubstackClient(publicationUrl, sessionToken, userId || "0", userAgent);
// Validate auth on startup (warn but don't block — allows inspection without credentials)
const client = new SubstackClient(publicationUrl, sessionToken, userId || "0", userAgent, timeoutMs);
const server = createServer(client);
const transport = new StdioServerTransport();
// Registered before connect so a signal arriving during startup is still
// handled; McpServer.close() is safe on a server that never connected.
installShutdownHandlers(() => server.close());
await server.connect(transport);
console.error("Substack MCP server running on stdio");
// Auth is validated *after* connect, and the result only warns. Awaiting it
// first put a network round trip in front of the MCP handshake: a host that
// hangs rather than refuses (corporate proxy, blackholed route) stalled
// `initialize` for undici's full connect timeout with no output at all.
// Tools still error individually on a bad token, which is where the failure
// is actionable anyway.
try {

@@ -34,6 +97,2 @@ const user = await client.validateAuth();

}
const server = createServer(client);
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Substack MCP server running on stdio");
}

@@ -40,0 +99,0 @@ main().catch((err) => {

@@ -1,1 +0,1 @@

{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAEnE,KAAK,UAAU,IAAI;IACjB,yEAAyE;IACzE,kBAAkB;IAClB,MAAM,KAAK,GAAG,kBAAkB,EAAE,CAAC;IACnC,MAAM,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IAEvD,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,KAAK,CAAC,iCAAiC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3E,OAAO,CAAC,KAAK,CACX,qGAAqG,CACtG,CAAC;IACJ,CAAC;SAAM,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;QAClC,OAAO,CAAC,KAAK,CAAC,qCAAqC,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;IAClD,4EAA4E;IAC5E,6EAA6E;IAC7E,2CAA2C;IAC3C,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,cAAc,EAAE,YAAY,EAAE,MAAM,IAAI,GAAG,EAAE,SAAS,CAAC,CAAC;IAE1F,0FAA0F;IAC1F,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,YAAY,EAAE,CAAC;QACzC,OAAO,CAAC,KAAK,CAAC,yBAAyB,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IACpD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,2FAA2F,CAAC,CAAC;QAC3G,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAClE,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;AACxD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;IACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}

@@ -26,2 +26,24 @@ export declare class SubstackAPIError extends Error {

/**
* The client's own request deadline fired before any response arrived.
*
* There is no real status here — Substack never answered — so 408 is synthetic,
* chosen only so this fits the `SubstackAPIError` shape every tool handler
* already renders. Without it, an aborted fetch surfaces as a bare
* `DOMException: The operation was aborted due to timeout`, which says nothing
* about which request died or how to fix it.
*/
export declare class TimeoutError extends SubstackAPIError {
constructor(endpoint: string, timeoutMs: number);
}
/**
* True when a rejected `fetch` was aborted rather than failing on its own.
*
* Shape varies by runtime: `AbortSignal.timeout()` rejects with a DOMException
* named `TimeoutError`, older undici builds substitute a generic `AbortError`
* instead of propagating the signal's reason, and some wrap the abort in a
* `TypeError` with the real reason on `.cause`. All three mean the same thing,
* so all three are matched.
*/
export declare function isAbortError(err: unknown): boolean;
/**
* Maps an HTTP status code + error detail string to the appropriate typed

@@ -28,0 +50,0 @@ * error. 401/403 route to AuthenticationError with `detail` intentionally

@@ -46,2 +46,36 @@ export class SubstackAPIError extends Error {

/**
* The client's own request deadline fired before any response arrived.
*
* There is no real status here — Substack never answered — so 408 is synthetic,
* chosen only so this fits the `SubstackAPIError` shape every tool handler
* already renders. Without it, an aborted fetch surfaces as a bare
* `DOMException: The operation was aborted due to timeout`, which says nothing
* about which request died or how to fix it.
*/
export class TimeoutError extends SubstackAPIError {
constructor(endpoint, timeoutMs) {
super(408, `Request timed out after ${timeoutMs}ms with no response. The host may be unreachable, ` +
"or behind a proxy that drops packets instead of refusing the connection. " +
"Set SUBSTACK_REQUEST_TIMEOUT_MS to raise the limit if the publication is just slow.", endpoint);
this.name = "TimeoutError";
}
}
/**
* True when a rejected `fetch` was aborted rather than failing on its own.
*
* Shape varies by runtime: `AbortSignal.timeout()` rejects with a DOMException
* named `TimeoutError`, older undici builds substitute a generic `AbortError`
* instead of propagating the signal's reason, and some wrap the abort in a
* `TypeError` with the real reason on `.cause`. All three mean the same thing,
* so all three are matched.
*/
export function isAbortError(err) {
const isAbortNamed = (e) => {
const name = e?.name;
return name === "TimeoutError" || name === "AbortError";
};
return (isAbortNamed(err) ||
isAbortNamed(err?.cause));
}
/**
* Maps an HTTP status code + error detail string to the appropriate typed

@@ -48,0 +82,0 @@ * error. 401/403 route to AuthenticationError with `detail` intentionally

@@ -1,1 +0,1 @@

{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"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"}

@@ -21,6 +21,2 @@ # substack-mcp — stdio MCP server for Substack

COPY --from=builder /app/dist ./dist
# tsconfig has no test exclusion, so tsc emits src/__tests__ into dist. Those
# files import vitest, which --omit=dev correctly leaves out, so they are dead
# weight and scanner surface in a published image.
RUN rm -rf dist/__tests__

@@ -27,0 +23,0 @@ # Credentials come from the environment and are never baked into the image:

{
"name": "@conorbronsdon/substack-mcp",
"version": "0.6.1",
"version": "0.6.2",
"description": "MCP server for Substack — read posts, manage drafts, publish Notes. Long-form posts are draft-only by design (no publish, no delete); Notes publish immediately.",

@@ -12,3 +12,3 @@ "type": "module",

"build": "tsc",
"lint": "tsc --noEmit",
"lint": "tsc --noEmit -p tsconfig.lint.json",
"test": "vitest run",

@@ -15,0 +15,0 @@ "start": "node dist/index.js",

@@ -179,2 +179,14 @@ <div align="center">

## Request timeout
Every request to Substack is bounded by a 30-second deadline. Node applies no request timeout of its own — only a 10-second *connect* timeout — so a host that accepts the connection and then goes silent (a proxy that drops packets rather than refusing them) would otherwise hang a tool call indefinitely. A request that hits the deadline fails with a `TimeoutError` naming the endpoint and the limit.
Raise or lower it with `SUBSTACK_REQUEST_TIMEOUT_MS` (milliseconds; a non-numeric or non-positive value is ignored with a warning and the default is used):
```json
"env": {
"SUBSTACK_REQUEST_TIMEOUT_MS": "60000"
}
```
## Typed errors

@@ -191,2 +203,3 @@

| `ServerError` | 5xx | Failure on Substack's side |
| `TimeoutError` | 408 (synthetic) | The request hit the client's own deadline — no response arrived, so there is no real status to report (see [Request timeout](#request-timeout)) |
| `SubstackAPIError` | any other status | Fallback for unmapped status codes |

@@ -215,3 +228,4 @@

- Session tokens are sent as cookies. Keep your `SUBSTACK_SESSION_TOKEN` secure.
- The server validates authentication on startup and will fail fast if your token is expired.
- The server checks your credentials on startup, *after* the MCP handshake completes, and only warns — it never blocks startup on a network call. Tools still error individually if the token is expired, which is where the failure is actionable.
- `SIGTERM` and `SIGINT` are handled: the server closes its transport and exits 0, so `docker stop` returns promptly instead of waiting out the grace period.

@@ -218,0 +232,0 @@ ## Development

import { describe, it, expect } from "vitest";
import { buildAnnotations, TOOL_KINDS } from "../annotations.js";
import { createServer } from "../server.js";
// The server only touches the client inside tool handlers, which these tests
// never invoke — a bare object is enough to register everything.
const server = createServer({});
// SDK-private registry; the McpServer API has no public tool introspection.
const registered = server._registeredTools;
const READ_TOOLS = [
"get_subscriber_count",
"list_published_posts",
"list_drafts",
"get_post",
"get_draft",
"get_post_comments",
"get_sections",
"get_post_analytics",
"list_scheduled_posts",
];
const DRAFT_WRITE_TOOLS = ["create_draft", "update_draft"];
const PUBLIC_UPLOAD_TOOLS = ["upload_image"];
const PUBLISH_TOOLS = ["create_note", "create_note_with_link"];
describe("buildAnnotations mapping", () => {
it("read -> { readOnlyHint: true } and nothing else", () => {
const a = buildAnnotations("get_post");
expect(a).toEqual({ readOnlyHint: true });
});
it("draft write -> readOnlyHint:false, explicitly non-destructive and closed-world", () => {
const a = buildAnnotations("create_draft");
expect(a).toEqual({
readOnlyHint: false,
destructiveHint: false,
openWorldHint: false,
});
});
it("public upload -> openWorldHint:true (returns a publicly-fetchable URL), non-destructive", () => {
const a = buildAnnotations("upload_image");
expect(a).toEqual({
readOnlyHint: false,
destructiveHint: false,
openWorldHint: true,
});
});
it("publish (Notes) -> readOnlyHint:false, openWorldHint:true, non-destructive", () => {
const a = buildAnnotations("create_note");
expect(a).toEqual({
readOnlyHint: false,
destructiveHint: false,
openWorldHint: true,
});
});
});
describe("tool annotation classifications", () => {
it("completeness: registered tools and the classification registry match exactly", () => {
// Every registered tool must be classified, and every classified tool
// must exist — new tools cannot slip through unannotated, and stale
// registry entries cannot linger.
expect(Object.keys(registered).sort()).toEqual(Object.keys(TOOL_KINDS).sort());
});
it("every registered tool carries the annotations its classification dictates", () => {
for (const name of Object.keys(TOOL_KINDS)) {
expect(registered[name].annotations, `${name} annotations should match buildAnnotations`).toEqual(buildAnnotations(name));
}
});
it("read tools are readOnlyHint:true", () => {
for (const name of READ_TOOLS) {
expect(buildAnnotations(name).readOnlyHint, name).toBe(true);
}
});
it("draft writes are readOnlyHint:false, explicitly non-destructive and closed-world", () => {
for (const name of DRAFT_WRITE_TOOLS) {
const a = buildAnnotations(name);
expect(a.readOnlyHint, name).toBe(false);
expect(a.destructiveHint, name).toBe(false);
expect(a.openWorldHint, name).toBe(false);
}
});
it("public uploads carry openWorldHint:true (publicly-fetchable URL), non-destructive", () => {
for (const name of PUBLIC_UPLOAD_TOOLS) {
const a = buildAnnotations(name);
expect(a.readOnlyHint, name).toBe(false);
expect(a.destructiveHint, name).toBe(false);
expect(a.openWorldHint, name).toBe(true);
}
});
it("Note tools (immediate public publish) carry openWorldHint:true", () => {
for (const name of PUBLISH_TOOLS) {
const a = buildAnnotations(name);
expect(a.readOnlyHint, name).toBe(false);
expect(a.openWorldHint, name).toBe(true);
}
});
it("no write tool is destructive (this server has no deletes by design)", () => {
// Writes set destructiveHint explicitly to false — never left to MCP's
// unsafe default of true. Reads omit it (not meaningful for a read).
for (const name of [
...DRAFT_WRITE_TOOLS,
...PUBLIC_UPLOAD_TOOLS,
...PUBLISH_TOOLS,
]) {
expect(buildAnnotations(name).destructiveHint, name).toBe(false);
}
});
it("Note tool descriptions say loudly that they publish immediately", () => {
for (const name of PUBLISH_TOOLS) {
expect(registered[name].description, name).toMatch(/publishes immediately/i);
}
});
it("the classification groups above cover the whole registry", () => {
const grouped = [
...READ_TOOLS,
...DRAFT_WRITE_TOOLS,
...PUBLIC_UPLOAD_TOOLS,
...PUBLISH_TOOLS,
];
expect(grouped.sort()).toEqual(Object.keys(TOOL_KINDS).sort());
});
});
//# sourceMappingURL=annotations.test.js.map
{"version":3,"file":"annotations.test.js","sourceRoot":"","sources":["../../src/__tests__/annotations.test.ts"],"names":[],"mappings":"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"}
import { describe, it, expect, vi, afterEach } from "vitest";
import { SubstackClient, parseMagnitude, MAX_PAGE_SIZE, ANALYTICS_MAX_PAGES, ANALYTICS_SCAN_DEPTH, } from "../api/client.js";
import { AuthenticationError, RateLimitError, ValidationError, NotFoundError, ServerError, SubstackAPIError, } from "../utils/errors.js";
describe("SubstackClient constructor", () => {
it("parses valid numeric userId without throwing", () => {
expect(() => new SubstackClient("https://example.substack.com", "tok123", "42")).not.toThrow();
});
it("throws with clear message for invalid userId", () => {
expect(() => new SubstackClient("https://example.substack.com", "tok123", "abc")).toThrow('Invalid SUBSTACK_USER_ID: "abc" — must be a number');
});
it("strips trailing slash from publication URL", () => {
// Access the private field via any cast to verify behavior
const client = new SubstackClient("https://example.substack.com/", "tok", "1");
expect(client.publicationUrl).toBe("https://example.substack.com");
});
it("sets cookie string with both connect.sid and substack.sid", () => {
const client = new SubstackClient("https://example.substack.com", "mytoken", "1");
expect(client.cookie).toContain("connect.sid=mytoken");
expect(client.cookie).toContain("substack.sid=mytoken");
});
});
describe("SubstackClient requests", () => {
afterEach(() => vi.unstubAllGlobals());
function stubFetch(jsonBody) {
const fetchMock = vi.fn(async (..._args) => ({
ok: true,
status: 200,
json: async () => jsonBody,
text: async () => JSON.stringify(jsonBody),
}));
vi.stubGlobal("fetch", fetchMock);
return fetchMock;
}
it("getDrafts uses post_management/drafts with ordering and unwraps posts[]", async () => {
const fetchMock = stubFetch({ posts: [{ id: 7, draft_title: "hi" }], total: 1 });
const client = new SubstackClient("https://example.substack.com", "tok", "1");
const drafts = await client.getDrafts(0, 5);
expect(drafts).toHaveLength(1);
expect(drafts[0].id).toBe(7);
const calledUrl = fetchMock.mock.calls[0][0];
expect(calledUrl).toContain("/api/v1/post_management/drafts");
expect(calledUrl).toContain("order_by=draft_updated_at");
expect(calledUrl).toContain("order_direction=desc");
expect(calledUrl).not.toContain("/api/v1/drafts?");
});
it("getSections matches the publication by hostname and returns its sections", async () => {
const fetchMock = stubFetch({
publications: [
{ hostname: "other.substack.com", sections: [{ id: 1, name: "Other" }] },
{
hostname: "example.substack.com",
sections: [
{ id: 10, name: "Essays" },
{ id: 11, name: "Notes" },
],
},
],
});
const client = new SubstackClient("https://example.substack.com", "tok", "1");
const sections = await client.getSections();
expect(sections).toHaveLength(2);
expect(sections[0]).toEqual({ id: 10, name: "Essays" });
expect(fetchMock.mock.calls[0][0]).toContain("/api/v1/subscriptions");
});
it("getSections matches on custom_domain when hostname does not match", async () => {
const fetchMock = stubFetch({
publications: [
{
hostname: "example.substack.com",
custom_domain: "newsletter.example.com",
sections: [{ id: 7, name: "Custom" }],
},
],
});
const client = new SubstackClient("https://newsletter.example.com", "tok", "1");
const sections = await client.getSections();
expect(sections).toEqual([{ id: 7, name: "Custom" }]);
expect(fetchMock.mock.calls[0][0]).toContain("/api/v1/subscriptions");
});
it("getSections does not substring-match an unrelated publication host", async () => {
// `ample.substack.com` is a substring of `example.substack.com`; exact-host
// matching must skip it and return the real publication's sections.
stubFetch({
publications: [
{ hostname: "ample.substack.com", sections: [{ id: 1, name: "Wrong" }] },
{ hostname: "example.substack.com", sections: [{ id: 2, name: "Right" }] },
],
});
const client = new SubstackClient("https://example.substack.com", "tok", "1");
const sections = await client.getSections();
expect(sections).toEqual([{ id: 2, name: "Right" }]);
});
it("getSections returns [] when no publication matches", async () => {
stubFetch({ publications: [{ hostname: "nope.substack.com", sections: [{ id: 1, name: "X" }] }] });
const client = new SubstackClient("https://example.substack.com", "tok", "1");
expect(await client.getSections()).toEqual([]);
});
it("getScheduledPosts uses the scheduled view ordered by trigger_at asc", async () => {
const fetchMock = stubFetch({
posts: [{ id: 5, draft_title: "Queued", audience: "everyone", trigger_at: "2030-01-01T00:00:00Z" }],
});
const client = new SubstackClient("https://example.substack.com", "tok", "1");
const posts = await client.getScheduledPosts(0, 10);
expect(posts).toHaveLength(1);
expect(posts[0].id).toBe(5);
const url = fetchMock.mock.calls[0][0];
expect(url).toContain("/api/v1/post_management/scheduled");
expect(url).toContain("order_by=trigger_at");
expect(url).toContain("order_direction=asc");
});
it("getPostAnalytics finds a post's stats row in the published feed", async () => {
const fetchMock = stubFetch({
posts: [
{ id: 100, title: "A", stats: { views: 1 } },
{ id: 200, title: "B", stats: { views: 42, opened: 10 } },
],
total: 2,
});
const client = new SubstackClient("https://example.substack.com", "tok", "1");
const post = await client.getPostAnalytics(200);
expect(post?.id).toBe(200);
expect(post?.stats?.views).toBe(42);
expect(fetchMock.mock.calls[0][0]).toContain("/api/v1/post_management/published");
});
it("getPostAnalytics returns null and stops paging when the feed is exhausted", async () => {
const fetchMock = stubFetch({ posts: [{ id: 1, title: "only" }], total: 1 });
const client = new SubstackClient("https://example.substack.com", "tok", "1");
const post = await client.getPostAnalytics(999);
expect(post).toBeNull();
// A short page (1 < MAX_PAGE_SIZE) means end-of-feed: exactly one request,
// no over-paging.
expect(fetchMock).toHaveBeenCalledTimes(1);
});
it("sends a browser User-Agent and a Referer by default", async () => {
const fetchMock = stubFetch({ posts: [] });
const client = new SubstackClient("https://example.substack.com", "tok", "1");
await client.getDrafts();
const opts = fetchMock.mock.calls[0][1];
expect(opts.headers["User-Agent"]).toContain("Mozilla/5.0");
expect(opts.headers["Referer"]).toBe("https://example.substack.com/publish/home");
});
it("honors a custom User-Agent override", async () => {
const fetchMock = stubFetch({ posts: [] });
const client = new SubstackClient("https://example.substack.com", "tok", "1", "MyUA/1.0");
await client.getDrafts();
const opts = fetchMock.mock.calls[0][1];
expect(opts.headers["User-Agent"]).toBe("MyUA/1.0");
});
it("omits Content-Type on a bodyless GET", async () => {
const fetchMock = stubFetch({ posts: [] });
const client = new SubstackClient("https://example.substack.com", "tok", "1");
await client.getDrafts();
const opts = fetchMock.mock.calls[0][1];
expect(opts.headers["Content-Type"]).toBeUndefined();
});
it("sends Content-Type on a request with a body", async () => {
const fetchMock = stubFetch({ id: 1, draft_title: "hi" });
const client = new SubstackClient("https://example.substack.com", "tok", "1");
await client.createDraft("hi");
const opts = fetchMock.mock.calls[0][1];
expect(opts.headers["Content-Type"]).toBe("application/json");
});
});
describe("pagination limit cap (regression: #28)", () => {
afterEach(() => vi.unstubAllGlobals());
// Substack 400s on limit=51 and above, so every request this client issues on
// its own initiative must carry a limit VALUE within the cap. The pre-fix
// suite passed with limit=100 on the wire because it only asserted that the
// URL *contained* the endpoint path — a shape assertion cannot see a bad
// value. These read the query params back off the URLs the client actually
// built.
function paramValues(fetchMock, name) {
return fetchMock.mock.calls
.map((call) => new URL(String(call[0])).searchParams.get(name))
.filter((v) => v !== null)
.map(Number);
}
/**
* A published feed that honors offset/limit, so paging behavior follows the
* page size the client asks for rather than a fixture hardcoded around one.
*/
function stubPagedFeed(totalPosts) {
const fetchMock = vi.fn(async (url) => {
const params = new URL(String(url)).searchParams;
const offset = Number(params.get("offset") ?? 0);
const limit = Number(params.get("limit") ?? 0);
const count = Math.max(0, Math.min(limit, totalPosts - offset));
const body = {
posts: Array.from({ length: count }, (_, i) => ({
id: offset + i + 1,
title: `Post ${offset + i + 1}`,
stats: { views: 1 },
})),
total: totalPosts,
};
return {
ok: true,
status: 200,
json: async () => body,
text: async () => JSON.stringify(body),
};
});
vi.stubGlobal("fetch", fetchMock);
return fetchMock;
}
it("MAX_PAGE_SIZE is within Substack's observed 50 cap", () => {
// limit=50 → 200, limit=51 → 400 "Invalid value" (probed 2026-08-02).
expect(MAX_PAGE_SIZE).toBeLessThanOrEqual(50);
expect(MAX_PAGE_SIZE).toBeGreaterThan(0);
});
it("preserves the documented 500-post analytics scan depth", () => {
expect(MAX_PAGE_SIZE * ANALYTICS_MAX_PAGES).toBe(500);
expect(ANALYTICS_SCAN_DEPTH).toBe(500);
});
it("getPostAnalytics sends a limit value within the cap on every page", async () => {
const fetchMock = stubPagedFeed(10_000); // feed deeper than the scan bound
const client = new SubstackClient("https://example.substack.com", "tok", "1");
// An id past the scan depth, so the loop runs to its bound.
const post = await client.getPostAnalytics(9_999);
expect(post).toBeNull();
const limits = paramValues(fetchMock, "limit");
// Every request carries a limit — none slipped through unparameterized.
expect(limits).toHaveLength(fetchMock.mock.calls.length);
expect(fetchMock).toHaveBeenCalledTimes(ANALYTICS_MAX_PAGES);
// The value, not the shape: no page may exceed what Substack accepts.
expect(Math.max(...limits)).toBeLessThanOrEqual(MAX_PAGE_SIZE);
expect(limits).toEqual(Array(ANALYTICS_MAX_PAGES).fill(MAX_PAGE_SIZE));
});
it("getPostAnalytics tiles offsets across exactly the scan depth", async () => {
const fetchMock = stubPagedFeed(10_000);
const client = new SubstackClient("https://example.substack.com", "tok", "1");
await client.getPostAnalytics(9_999);
const offsets = paramValues(fetchMock, "offset");
expect(offsets).toEqual(Array.from({ length: ANALYTICS_MAX_PAGES }, (_, i) => i * MAX_PAGE_SIZE));
// Last page starts one page short of the bound, so the scan covers
// ANALYTICS_SCAN_DEPTH posts with no gap and no overshoot.
expect(Math.max(...offsets)).toBe(ANALYTICS_SCAN_DEPTH - MAX_PAGE_SIZE);
expect(Math.max(...offsets) + MAX_PAGE_SIZE).toBe(ANALYTICS_SCAN_DEPTH);
});
it("getPostAnalytics stops as soon as the post is found", async () => {
const fetchMock = stubPagedFeed(10_000);
const client = new SubstackClient("https://example.substack.com", "tok", "1");
// Post 60 lands on page 1 (offset 50), so page 2 must never be requested.
const post = await client.getPostAnalytics(60);
expect(post?.id).toBe(60);
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(paramValues(fetchMock, "limit")).toEqual([MAX_PAGE_SIZE, MAX_PAGE_SIZE]);
});
});
describe("SubstackClient error mapping (end-to-end through request())", () => {
afterEach(() => vi.unstubAllGlobals());
function stubFetchError(status, body) {
const fetchMock = vi.fn(async (..._args) => ({
ok: false,
status,
json: async () => JSON.parse(body),
text: async () => body,
}));
vi.stubGlobal("fetch", fetchMock);
return fetchMock;
}
it("maps a 401 response to AuthenticationError", async () => {
stubFetchError(401, "Not authorized");
const client = new SubstackClient("https://example.substack.com", "tok", "1");
await expect(client.getDrafts()).rejects.toBeInstanceOf(AuthenticationError);
});
it("maps a 403 Cloudflare-style block to AuthenticationError", async () => {
stubFetchError(403, "error code: 1010");
const client = new SubstackClient("https://example.substack.com", "tok", "1");
await expect(client.getDrafts()).rejects.toBeInstanceOf(AuthenticationError);
});
it("maps a 429 response to RateLimitError with the body as detail", async () => {
stubFetchError(429, JSON.stringify({ error: "Too many requests" }));
const client = new SubstackClient("https://example.substack.com", "tok", "1");
try {
await client.getDrafts();
throw new Error("should have thrown");
}
catch (err) {
expect(err).toBeInstanceOf(RateLimitError);
expect(err.message).toContain("Too many requests");
}
});
it("maps a 400 response to ValidationError", async () => {
stubFetchError(400, JSON.stringify({ error: "draft_title is required" }));
const client = new SubstackClient("https://example.substack.com", "tok", "1");
try {
await client.createDraft("");
throw new Error("should have thrown");
}
catch (err) {
expect(err).toBeInstanceOf(ValidationError);
expect(err.message).toContain("draft_title is required");
}
});
it("maps a 404 response to NotFoundError", async () => {
stubFetchError(404, "Draft not found");
const client = new SubstackClient("https://example.substack.com", "tok", "1");
try {
await client.getDraft(999);
throw new Error("should have thrown");
}
catch (err) {
expect(err).toBeInstanceOf(NotFoundError);
expect(err.message).toContain("Draft not found");
}
});
it("maps a 500 response to ServerError", async () => {
stubFetchError(500, "Internal Server Error");
const client = new SubstackClient("https://example.substack.com", "tok", "1");
try {
await client.getDrafts();
throw new Error("should have thrown");
}
catch (err) {
expect(err).toBeInstanceOf(ServerError);
expect(err.message).toContain("Internal Server Error");
}
});
it("falls back to base SubstackAPIError for an unmapped status", async () => {
stubFetchError(418, "I'm a teapot");
const client = new SubstackClient("https://example.substack.com", "tok", "1");
try {
await client.getDrafts();
throw new Error("should have thrown");
}
catch (err) {
expect(err).toBeInstanceOf(SubstackAPIError);
expect(err).not.toBeInstanceOf(AuthenticationError);
expect(err).not.toBeInstanceOf(RateLimitError);
expect(err).not.toBeInstanceOf(ValidationError);
expect(err).not.toBeInstanceOf(NotFoundError);
expect(err).not.toBeInstanceOf(ServerError);
expect(err.message).toContain("I'm a teapot");
}
});
});
describe("parseMagnitude", () => {
it("parses whole-K buckets", () => {
expect(parseMagnitude("1K+")).toBe(1000);
expect(parseMagnitude("2K")).toBe(2000);
});
it("parses fractional-K buckets (regression: .replace('K','000') produced NaN)", () => {
expect(parseMagnitude("2.5K+")).toBe(2500);
expect(parseMagnitude("1.2K")).toBe(1200);
});
it("parses bare numbers and M buckets", () => {
expect(parseMagnitude("750+")).toBe(750);
expect(parseMagnitude("1,500+")).toBe(1500);
expect(parseMagnitude("1M+")).toBe(1000000);
});
it("returns null for unparseable or zero values", () => {
expect(parseMagnitude("")).toBeNull();
expect(parseMagnitude("lots")).toBeNull();
expect(parseMagnitude("0")).toBeNull();
expect(parseMagnitude("0K")).toBeNull();
});
});
describe("SubstackClient.getSubscriberCount", () => {
const client = () => new SubstackClient("https://example.substack.com", "tok123", "42");
afterEach(() => {
vi.restoreAllMocks();
});
const jsonResponse = (body) => ({ ok: true, status: 200, json: async () => body, text: async () => JSON.stringify(body) });
const htmlResponse = (html) => ({ ok: true, status: 200, text: async () => html });
it("reports exact when the API returns subscriber_count", async () => {
vi.spyOn(globalThis, "fetch").mockResolvedValue(jsonResponse({ subscriber_count: 1073 }));
const r = await client().getSubscriberCount();
expect(r).toMatchObject({ count: 1073, precision: "exact" });
});
it("NEVER counts the paginated subscribers sample as the total", async () => {
// The regression this guards: an 11-item sample was returned as count: 11
// for a publication with >1,000 subscribers.
const sample = { subscribers: Array.from({ length: 11 }, (_, i) => ({ id: i })) };
vi.spyOn(globalThis, "fetch")
.mockResolvedValueOnce(jsonResponse(sample))
.mockResolvedValueOnce(htmlResponse('{"freeSubscriberCount":"1,000"}'));
const r = await client().getSubscriberCount();
expect(r.count).not.toBe(11);
expect(r).toMatchObject({ count: 1000, precision: "approximate" });
});
it("falls back to the rounded public value and marks it approximate", async () => {
vi.spyOn(globalThis, "fetch")
.mockResolvedValueOnce(jsonResponse({ audience: null }))
.mockResolvedValueOnce(htmlResponse('{\\"freeSubscriberCount\\":\\"1,000\\"}'));
const r = await client().getSubscriberCount();
expect(r.precision).toBe("approximate");
expect(r.count).toBe(1000);
expect(r.note).toMatch(/or higher/);
});
it("uses the order-of-magnitude bucket when no numeric count is present", async () => {
vi.spyOn(globalThis, "fetch")
.mockResolvedValueOnce(jsonResponse({}))
.mockResolvedValueOnce(htmlResponse('{"freeSubscriberCountOrderOfMagnitude":"2.5K+"}'));
const r = await client().getSubscriberCount();
expect(r).toMatchObject({ count: 2500, precision: "approximate" });
});
it("returns unavailable rather than a fabricated number when everything fails", async () => {
vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("network down"));
const r = await client().getSubscriberCount();
expect(r).toMatchObject({ count: -1, precision: "unavailable" });
});
});
//# sourceMappingURL=client.test.js.map
{"version":3,"file":"client.test.js","sourceRoot":"","sources":["../../src/__tests__/client.test.ts"],"names":[],"mappings":"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"}
import { describe, it, expect } from "vitest";
import { SubstackAPIError, AuthenticationError, RateLimitError, ValidationError, NotFoundError, ServerError, mapHttpStatusToError, extractErrorDetail, } from "../utils/errors.js";
describe("SubstackAPIError", () => {
it("formats message with status code and endpoint", () => {
const err = new SubstackAPIError(404, "not found", "/api/v1/posts/99");
expect(err.message).toBe("Substack API error (404) at /api/v1/posts/99: not found");
expect(err.statusCode).toBe(404);
expect(err.endpoint).toBe("/api/v1/posts/99");
});
it("has correct name property", () => {
const err = new SubstackAPIError(500, "oops", "/api");
expect(err.name).toBe("SubstackAPIError");
});
it("is an instance of Error", () => {
const err = new SubstackAPIError(400, "bad", "/api");
expect(err).toBeInstanceOf(Error);
});
});
describe("AuthenticationError", () => {
it("is a 401 with cookie guidance message", () => {
const err = new AuthenticationError("/api/v1/drafts");
expect(err.statusCode).toBe(401);
expect(err.message).toContain("connect.sid");
expect(err.message).toContain("substack.sid");
expect(err.endpoint).toBe("/api/v1/drafts");
});
it("has correct name property", () => {
const err = new AuthenticationError("/api");
expect(err.name).toBe("AuthenticationError");
});
it("is an instance of SubstackAPIError", () => {
const err = new AuthenticationError("/api");
expect(err).toBeInstanceOf(SubstackAPIError);
});
});
describe("RateLimitError", () => {
it("is a 429 with rate-limit guidance", () => {
const err = new RateLimitError("/api/v1/drafts", "too many requests");
expect(err.statusCode).toBe(429);
expect(err.endpoint).toBe("/api/v1/drafts");
expect(err.message).toContain("too many requests");
expect(err.message).toContain("Slow down");
});
it("has correct name property and extends SubstackAPIError", () => {
const err = new RateLimitError("/api", "x");
expect(err.name).toBe("RateLimitError");
expect(err).toBeInstanceOf(SubstackAPIError);
});
});
describe("ValidationError", () => {
it("is a 400 with argument-checking guidance", () => {
const err = new ValidationError("/api/v1/drafts", "missing draft_title");
expect(err.statusCode).toBe(400);
expect(err.message).toContain("missing draft_title");
expect(err.message).toContain("Check the arguments passed to this tool");
});
it("has correct name property and extends SubstackAPIError", () => {
const err = new ValidationError("/api", "x");
expect(err.name).toBe("ValidationError");
expect(err).toBeInstanceOf(SubstackAPIError);
});
});
describe("NotFoundError", () => {
it("is a 404 mentioning the missing resource", () => {
const err = new NotFoundError("/api/v1/drafts/99", "no such draft");
expect(err.statusCode).toBe(404);
expect(err.message).toContain("no such draft");
expect(err.message).toContain("draft, post, or note");
});
it("has correct name property and extends SubstackAPIError", () => {
const err = new NotFoundError("/api", "x");
expect(err.name).toBe("NotFoundError");
expect(err).toBeInstanceOf(SubstackAPIError);
});
});
describe("ServerError", () => {
it("is a 5xx with retry-later guidance", () => {
const err = new ServerError("/api/v1/drafts", "internal error");
expect(err.statusCode).toBe(500);
expect(err.message).toContain("internal error");
expect(err.message).toContain("try again later");
});
it("has correct name property and extends SubstackAPIError", () => {
const err = new ServerError("/api", "x");
expect(err.name).toBe("ServerError");
expect(err).toBeInstanceOf(SubstackAPIError);
});
});
describe("mapHttpStatusToError", () => {
it("maps 401 and 403 to AuthenticationError, discarding detail", () => {
const err401 = mapHttpStatusToError(401, "some raw body that should be ignored", "/api/v1/drafts");
const err403 = mapHttpStatusToError(403, "some raw body that should be ignored", "/api/v1/drafts");
expect(err401).toBeInstanceOf(AuthenticationError);
expect(err403).toBeInstanceOf(AuthenticationError);
// Message must match AuthenticationError's own hardcoded guidance exactly,
// not the discarded detail string — this is the 401/403 tradeoff.
expect(err401.message).toBe(new AuthenticationError("/api/v1/drafts").message);
expect(err401.message).not.toContain("some raw body");
});
it("maps 429 to RateLimitError", () => {
const err = mapHttpStatusToError(429, "slow down", "/api");
expect(err).toBeInstanceOf(RateLimitError);
expect(err.message).toContain("slow down");
});
it("maps 400 to ValidationError", () => {
const err = mapHttpStatusToError(400, "bad params", "/api");
expect(err).toBeInstanceOf(ValidationError);
expect(err.message).toContain("bad params");
});
it("maps 404 to NotFoundError", () => {
const err = mapHttpStatusToError(404, "no such post", "/api");
expect(err).toBeInstanceOf(NotFoundError);
expect(err.message).toContain("no such post");
});
it("maps 500-599 to ServerError", () => {
expect(mapHttpStatusToError(500, "boom", "/api")).toBeInstanceOf(ServerError);
expect(mapHttpStatusToError(502, "bad gateway", "/api")).toBeInstanceOf(ServerError);
expect(mapHttpStatusToError(503, "unavailable", "/api")).toBeInstanceOf(ServerError);
});
it("falls back to base SubstackAPIError for unmapped status codes", () => {
const err = mapHttpStatusToError(418, "teapot", "/api");
expect(err).toBeInstanceOf(SubstackAPIError);
expect(err).not.toBeInstanceOf(AuthenticationError);
expect(err).not.toBeInstanceOf(RateLimitError);
expect(err).not.toBeInstanceOf(ValidationError);
expect(err).not.toBeInstanceOf(NotFoundError);
expect(err).not.toBeInstanceOf(ServerError);
expect(err.message).toContain("Substack API error (418)");
expect(err.message).toContain("teapot");
});
});
describe("extractErrorDetail", () => {
it("returns the .error field from a JSON object body", () => {
expect(extractErrorDetail('{"error": "Invalid parameters"}', "fallback")).toBe("Invalid parameters");
});
it("returns the .message field from a JSON object body when .error is absent", () => {
expect(extractErrorDetail('{"message": "Draft is locked"}', "fallback")).toBe("Draft is locked");
});
it("joins a JSON .errors array into a single string", () => {
const result = extractErrorDetail('{"errors": ["title is required", "body is too long"]}', "fallback");
expect(result).toContain("title is required");
expect(result).toContain("body is too long");
});
it("returns a plain-text response body as-is", () => {
expect(extractErrorDetail("Not authorized", "fallback")).toBe("Not authorized");
});
it("falls back when response data is an empty string", () => {
expect(extractErrorDetail("", "fallback")).toBe("fallback");
});
it("trims and caps a Cloudflare-style HTML block page", () => {
const htmlPage = "\n\n <!DOCTYPE html><html><head><title>Attention Required! | Cloudflare</title></head>" +
"<body>" +
"error code: 1010".repeat(100) +
"</body></html>\n\n";
const result = extractErrorDetail(htmlPage, "fallback");
expect(result.length).toBeLessThanOrEqual(503); // 500 chars + "..."
expect(result.startsWith("<!DOCTYPE html>")).toBe(true);
expect(result.endsWith("...")).toBe(true);
});
});
//# sourceMappingURL=errors.test.js.map
{"version":3,"file":"errors.test.js","sourceRoot":"","sources":["../../src/__tests__/errors.test.ts"],"names":[],"mappings":"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"}
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { writeFile, mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileToDataUri, SUPPORTED_IMAGE_EXTENSIONS } from "../utils/image.js";
describe("fileToDataUri", () => {
let dir;
const pngBytes = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
beforeAll(async () => {
dir = await mkdtemp(join(tmpdir(), "substack-image-"));
});
afterAll(async () => {
await rm(dir, { recursive: true, force: true });
});
it("encodes a png file as a data URI with the right MIME type", async () => {
const path = join(dir, "pic.png");
await writeFile(path, pngBytes);
const uri = await fileToDataUri(path);
expect(uri).toBe(`data:image/png;base64,${pngBytes.toString("base64")}`);
});
it("maps .jpg and .jpeg to image/jpeg", async () => {
const jpg = join(dir, "a.jpg");
const jpeg = join(dir, "b.jpeg");
await writeFile(jpg, pngBytes);
await writeFile(jpeg, pngBytes);
expect(await fileToDataUri(jpg)).toMatch(/^data:image\/jpeg;base64,/);
expect(await fileToDataUri(jpeg)).toMatch(/^data:image\/jpeg;base64,/);
});
it("is case-insensitive on the extension", async () => {
const path = join(dir, "PIC.PNG");
await writeFile(path, pngBytes);
expect(await fileToDataUri(path)).toMatch(/^data:image\/png;base64,/);
});
it("throws on an unsupported extension", async () => {
const path = join(dir, "notes.txt");
await writeFile(path, "hello");
await expect(fileToDataUri(path)).rejects.toThrow(/Unsupported image file extension/);
});
it("throws when the file does not exist", async () => {
await expect(fileToDataUri(join(dir, "missing.png"))).rejects.toThrow();
});
it("exposes the supported extension list", () => {
expect(SUPPORTED_IMAGE_EXTENSIONS).toContain(".png");
expect(SUPPORTED_IMAGE_EXTENSIONS).toContain(".webp");
});
});
//# sourceMappingURL=image.test.js.map
{"version":3,"file":"image.test.js","sourceRoot":"","sources":["../../src/__tests__/image.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAE9E,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,IAAI,GAAW,CAAC;IAChB,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAE/E,SAAS,CAAC,KAAK,IAAI,EAAE;QACnB,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,iBAAiB,CAAC,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,KAAK,IAAI,EAAE;QAClB,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;QACzE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAClC,MAAM,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAChC,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,yBAAyB,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;QACjD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QACjC,MAAM,SAAS,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAC/B,MAAM,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAChC,MAAM,CAAC,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;QACtE,MAAM,CAAC,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;QACpD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAClC,MAAM,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAChC,MAAM,CAAC,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QAClD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;QACpC,MAAM,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC/B,MAAM,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,kCAAkC,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;QACnD,MAAM,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,CAAC,0BAA0B,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACrD,MAAM,CAAC,0BAA0B,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
import { describe, it, expect } from "vitest";
import { markdownToProseMirror, markdownToProseMirrorContent, parseInline, } from "../utils/markdown-to-prosemirror.js";
/** Helper: parse markdown and return the doc object */
function parse(md) {
return JSON.parse(markdownToProseMirror(md));
}
describe("markdownToProseMirror", () => {
describe("paragraphs", () => {
it("converts plain text to a paragraph node", () => {
const doc = parse("Hello world");
expect(doc.type).toBe("doc");
expect(doc.content).toHaveLength(1);
expect(doc.content[0].type).toBe("paragraph");
expect(doc.content[0].content[0]).toEqual({
type: "text",
text: "Hello world",
});
});
it("returns an empty paragraph for blank input", () => {
const doc = parse("");
expect(doc.content).toEqual([{ type: "paragraph" }]);
});
});
describe("headings", () => {
it.each([1, 2, 3, 4, 5, 6])("converts h%i heading", (level) => {
const hashes = "#".repeat(level);
const doc = parse(`${hashes} Heading ${level}`);
const node = doc.content[0];
expect(node.type).toBe("heading");
expect(node.attrs.level).toBe(level);
expect(node.content[0].text).toBe(`Heading ${level}`);
});
});
describe("code blocks", () => {
it("converts fenced code block with language", () => {
const md = "```typescript\nconst x = 1;\n```";
const doc = parse(md);
const node = doc.content[0];
expect(node.type).toBe("code_block");
expect(node.attrs).toEqual({ lang: "typescript" });
expect(node.content[0].text).toBe("const x = 1;");
});
it("converts fenced code block without language", () => {
const md = "```\nsome code\n```";
const doc = parse(md);
const node = doc.content[0];
expect(node.type).toBe("code_block");
expect(node.attrs).toBeUndefined();
expect(node.content[0].text).toBe("some code");
});
});
describe("blockquotes", () => {
it("converts > lines to a blockquote node", () => {
const doc = parse("> This is a quote");
const node = doc.content[0];
expect(node.type).toBe("blockquote");
expect(node.content[0].type).toBe("paragraph");
expect(node.content[0].content[0].text).toBe("This is a quote");
});
it("joins consecutive > lines", () => {
const doc = parse("> Line one\n> Line two");
const text = doc.content[0].content[0].content[0].text;
expect(text).toBe("Line one Line two");
});
});
describe("ordered lists", () => {
it("converts numbered items to ordered_list with list_items", () => {
const md = "1. First\n2. Second\n3. Third";
const doc = parse(md);
const node = doc.content[0];
expect(node.type).toBe("ordered_list");
expect(node.content).toHaveLength(3);
node.content.forEach((item) => {
expect(item.type).toBe("list_item");
expect(item.content[0].type).toBe("paragraph");
});
expect(node.content[0].content[0].content[0].text).toBe("First");
expect(node.content[2].content[0].content[0].text).toBe("Third");
});
});
describe("unordered lists", () => {
it("converts - items to bullet_list with list_items", () => {
const md = "- Alpha\n- Beta";
const doc = parse(md);
const node = doc.content[0];
expect(node.type).toBe("bullet_list");
expect(node.content).toHaveLength(2);
expect(node.content[0].type).toBe("list_item");
expect(node.content[0].content[0].content[0].text).toBe("Alpha");
});
});
describe("nested lists", () => {
it("nests a deeper-indented bullet inside the parent list_item", () => {
const md = "- Parent\n - Child\n- Sibling";
const doc = parse(md);
const list = doc.content[0];
expect(doc.content).toHaveLength(1);
expect(list.type).toBe("bullet_list");
expect(list.content).toHaveLength(2); // Parent, Sibling
const parent = list.content[0];
expect(parent.content[0].content[0].text).toBe("Parent");
// Parent's second child is the nested list
const nested = parent.content[1];
expect(nested.type).toBe("bullet_list");
expect(nested.content).toHaveLength(1);
expect(nested.content[0].content[0].content[0].text).toBe("Child");
// Sibling stays at the top level, no nested list
expect(list.content[1].content[0].content[0].text).toBe("Sibling");
expect(list.content[1].content).toHaveLength(1);
});
it("nests to arbitrary depth", () => {
const md = "- L1\n - L2\n - L3";
const doc = parse(md);
const l1Item = doc.content[0].content[0];
const l2 = l1Item.content[1];
expect(l2.type).toBe("bullet_list");
const l2Item = l2.content[0];
const l3 = l2Item.content[1];
expect(l3.type).toBe("bullet_list");
expect(l3.content[0].content[0].content[0].text).toBe("L3");
});
it("nests an ordered list inside a bullet item (mixed types)", () => {
const md = "- Bullet\n 1. One\n 2. Two";
const doc = parse(md);
const bulletItem = doc.content[0].content[0];
const nested = bulletItem.content[1];
expect(nested.type).toBe("ordered_list");
expect(nested.content).toHaveLength(2);
expect(nested.content[1].content[0].content[0].text).toBe("Two");
});
it("starts a new sibling list when the marker type flips at the same level", () => {
const md = "- Bullet\n1. Number";
const doc = parse(md);
expect(doc.content).toHaveLength(2);
expect(doc.content[0].type).toBe("bullet_list");
expect(doc.content[1].type).toBe("ordered_list");
});
it("treats 4-space indentation as one nesting level", () => {
const md = "- Parent\n - Child";
const doc = parse(md);
const parent = doc.content[0].content[0];
expect(parent.content[1].type).toBe("bullet_list");
expect(parent.content[1].content[0].content[0].content[0].text).toBe("Child");
});
});
describe("tables (Substack has no table node — code_block fallback)", () => {
it("preserves a GFM table verbatim inside a code_block", () => {
const md = "| A | B |\n| --- | --- |\n| 1 | 2 |";
const doc = parse(md);
expect(doc.content).toHaveLength(1);
const node = doc.content[0];
expect(node.type).toBe("code_block");
expect(node.content[0].text).toBe(md);
});
it("supports alignment colons in the delimiter row", () => {
const md = "| L | R |\n|:---|---:|\n| a | b |";
const doc = parse(md);
expect(doc.content[0].type).toBe("code_block");
expect(doc.content[0].content[0].text).toContain(":---");
});
it("separates a preceding paragraph from the table", () => {
const doc = parse("Intro line\n| A | B |\n| --- | --- |\n| 1 | 2 |");
expect(doc.content).toHaveLength(2);
expect(doc.content[0].type).toBe("paragraph");
expect(doc.content[0].content[0].text).toBe("Intro line");
expect(doc.content[1].type).toBe("code_block");
});
it("does not treat a paragraph containing a lone pipe as a table", () => {
const doc = parse("a | b is just prose");
expect(doc.content[0].type).toBe("paragraph");
});
});
describe("nested list robustness (no data loss)", () => {
it("keeps a leading over-indented item as its own top-level list", () => {
// Regression: the old min-indent recursion silently dropped a leading
// deeper-indented item. Nothing may be lost.
const doc = parse(" - deep first\n- shallow");
const json = JSON.stringify(doc);
expect(json).toContain("deep first");
expect(json).toContain("shallow");
});
it("preserves every item when indentation jumps 0 -> 4 -> 2", () => {
const doc = parse("- a\n - b\n - c");
const json = JSON.stringify(doc);
expect(json).toContain('"a"');
expect(json).toContain('"b"');
expect(json).toContain('"c"');
});
});
describe("paragraph termination", () => {
it("does not swallow a following h4-h6 heading into the paragraph", () => {
const doc = parse("Some text\n#### Deep heading");
expect(doc.content).toHaveLength(2);
expect(doc.content[0].type).toBe("paragraph");
expect(doc.content[0].content[0].text).toBe("Some text");
expect(doc.content[1].type).toBe("heading");
expect(doc.content[1].attrs.level).toBe(4);
});
it("consumes an indented heading (no hang) and emits a heading node", () => {
const doc = parse(" # Indented heading");
expect(doc.content).toHaveLength(1);
expect(doc.content[0].type).toBe("heading");
expect(doc.content[0].attrs.level).toBe(1);
expect(doc.content[0].content[0].text).toBe("Indented heading");
});
it("consumes an indented image line (no hang) and emits captionedImage", () => {
const doc = parse(" ![alt](https://example.com/y.png)");
expect(doc.content).toHaveLength(1);
expect(doc.content[0].type).toBe("captionedImage");
expect(doc.content[0].content[0].attrs.src).toBe("https://example.com/y.png");
});
it("separates a paragraph from a following indented heading", () => {
const doc = parse("intro\n ## Sub");
expect(doc.content).toHaveLength(2);
expect(doc.content[0].type).toBe("paragraph");
expect(doc.content[1].type).toBe("heading");
expect(doc.content[1].attrs.level).toBe(2);
});
});
describe("horizontal rules", () => {
it("converts --- to horizontal_rule", () => {
const doc = parse("---");
expect(doc.content[0]).toEqual({ type: "horizontal_rule" });
});
});
describe("images", () => {
it("wraps a standalone image in captionedImage > image2 with a caption", () => {
const doc = parse("![Alt text](https://example.com/img.png)");
const node = doc.content[0];
expect(node.type).toBe("captionedImage");
const [image, caption] = node.content;
expect(image.type).toBe("image2");
expect(image.attrs.src).toBe("https://example.com/img.png");
expect(image.attrs.alt).toBe("Alt text");
expect(caption.type).toBe("caption");
expect(caption.content[0].text).toBe("Alt text");
});
it("omits the caption node and nulls alt for empty alt text", () => {
const doc = parse("![](https://example.com/img.png)");
const node = doc.content[0];
expect(node.type).toBe("captionedImage");
expect(node.content).toHaveLength(1);
expect(node.content[0].type).toBe("image2");
expect(node.content[0].attrs.alt).toBeNull();
});
it("parses width/height and MIME from a Substack CDN filename", () => {
const doc = parse("![t](https://substack-post-media.s3.amazonaws.com/public/images/abc_1265x5808.png)");
const image = doc.content[0].content[0];
expect(image.attrs.width).toBe(1265);
expect(image.attrs.height).toBe(5808);
expect(image.attrs.resizeWidth).toBe(1265);
expect(image.attrs.type).toBe("image/png");
});
it("falls back to null dimensions for a non-CDN URL", () => {
const doc = parse("![t](https://example.com/plain.png)");
const image = doc.content[0].content[0];
expect(image.attrs.width).toBeNull();
expect(image.attrs.height).toBeNull();
expect(image.attrs.type).toBeNull();
});
it("does not read an aspect-ratio filename on a non-CDN URL as dimensions", () => {
// `hero_16x9.jpg` matches the `_WxH_` shape but is an aspect-ratio label,
// not pixel dimensions — only Substack-hosted URLs use it for real dims.
const doc = parse("![logo](https://example.com/hero_16x9.jpg)");
const image = doc.content[0].content[0];
expect(image.attrs.width).toBeNull();
expect(image.attrs.height).toBeNull();
expect(image.attrs.type).toBeNull();
});
it("parses dimensions from a substackcdn.com fetch URL", () => {
const doc = parse("![t](https://substackcdn.com/image/fetch/w_1456/abc_800x600.png)");
const image = doc.content[0].content[0];
expect(image.attrs.width).toBe(800);
expect(image.attrs.height).toBe(600);
expect(image.attrs.type).toBe("image/png");
});
});
});
describe("parseInline", () => {
it("parses bold text", () => {
const nodes = parseInline("**bold**");
expect(nodes).toEqual([
{ type: "text", text: "bold", marks: [{ type: "bold" }] },
]);
});
it("parses italic text", () => {
const nodes = parseInline("*italic*");
expect(nodes).toEqual([
{ type: "text", text: "italic", marks: [{ type: "italic" }] },
]);
});
it("parses inline code", () => {
const nodes = parseInline("`code`");
expect(nodes).toEqual([
{ type: "text", text: "code", marks: [{ type: "code" }] },
]);
});
it("parses links", () => {
const nodes = parseInline("[click](https://example.com)");
expect(nodes).toEqual([
{
type: "text",
text: "click",
marks: [{ type: "link", attrs: { href: "https://example.com" } }],
},
]);
});
it("parses mixed inline: bold + italic + link", () => {
const nodes = parseInline("Hello **bold** and *italic* and [link](https://x.com)");
expect(nodes[0]).toEqual({ type: "text", text: "Hello " });
expect(nodes[1]).toEqual({
type: "text",
text: "bold",
marks: [{ type: "bold" }],
});
expect(nodes[2]).toEqual({ type: "text", text: " and " });
expect(nodes[3]).toEqual({
type: "text",
text: "italic",
marks: [{ type: "italic" }],
});
expect(nodes[4]).toEqual({ type: "text", text: " and " });
expect(nodes[5]).toEqual({
type: "text",
text: "link",
marks: [{ type: "link", attrs: { href: "https://x.com" } }],
});
});
it("returns plain text node for text with no formatting", () => {
const nodes = parseInline("just plain text");
expect(nodes).toEqual([{ type: "text", text: "just plain text" }]);
});
});
describe("markdownToProseMirrorContent", () => {
it("returns just the content array without doc wrapper", () => {
const content = markdownToProseMirrorContent("Hello world");
expect(Array.isArray(content)).toBe(true);
expect(content).toHaveLength(1);
expect(content[0].type).toBe("paragraph");
});
});
//# sourceMappingURL=markdown-to-prosemirror.test.js.map
{"version":3,"file":"markdown-to-prosemirror.test.js","sourceRoot":"","sources":["../../src/__tests__/markdown-to-prosemirror.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACL,qBAAqB,EACrB,4BAA4B,EAC5B,WAAW,GACZ,MAAM,qCAAqC,CAAC;AAE7C,uDAAuD;AACvD,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,CAAC,KAAK,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;QAC1B,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;YACjD,MAAM,GAAG,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC;YACjC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC7B,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACpC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC9C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;gBACxC,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,aAAa;aACpB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;YACpD,MAAM,GAAG,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;YACtB,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;QACxB,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,sBAAsB,EAAE,CAAC,KAAK,EAAE,EAAE;YAC5D,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACjC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,MAAM,YAAY,KAAK,EAAE,CAAC,CAAC;YAChD,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAClC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;QAC3B,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;YAClD,MAAM,EAAE,GAAG,kCAAkC,CAAC;YAC9C,MAAM,GAAG,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;YACtB,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;YACnD,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;YACrD,MAAM,EAAE,GAAG,qBAAqB,CAAC;YACjC,MAAM,GAAG,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;YACtB,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,aAAa,EAAE,CAAC;YACnC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;QAC3B,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;YAC/C,MAAM,GAAG,GAAG,KAAK,CAAC,mBAAmB,CAAC,CAAC;YACvC,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC/C,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;YACnC,MAAM,GAAG,GAAG,KAAK,CAAC,wBAAwB,CAAC,CAAC;YAC5C,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YACvD,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;YACjE,MAAM,EAAE,GAAG,+BAA+B,CAAC;YAC3C,MAAM,GAAG,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;YACtB,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YACvC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACrC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAS,EAAE,EAAE;gBACjC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;gBACpC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACjD,CAAC,CAAC,CAAC;YACH,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACjE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;QAC/B,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;YACzD,MAAM,EAAE,GAAG,iBAAiB,CAAC;YAC7B,MAAM,GAAG,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;YACtB,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YACtC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC/C,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;QAC5B,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;YACpE,MAAM,EAAE,GAAG,gCAAgC,CAAC;YAC5C,MAAM,GAAG,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;YACtB,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC5B,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACpC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YACtC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB;YAExD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC/B,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACzD,2CAA2C;YAC3C,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACjC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YACxC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACvC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAEnE,iDAAiD;YACjD,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACnE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;YAClC,MAAM,EAAE,GAAG,wBAAwB,CAAC;YACpC,MAAM,GAAG,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;YACtB,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACzC,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC7B,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YACpC,MAAM,MAAM,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC7B,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC7B,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YACpC,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;YAClE,MAAM,EAAE,GAAG,8BAA8B,CAAC;YAC1C,MAAM,GAAG,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;YACtB,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC7C,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACrC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YACzC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACvC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wEAAwE,EAAE,GAAG,EAAE;YAChF,MAAM,EAAE,GAAG,qBAAqB,CAAC;YACjC,MAAM,GAAG,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;YACtB,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACpC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAChD,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;YACzD,MAAM,EAAE,GAAG,uBAAuB,CAAC;YACnC,MAAM,GAAG,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;YACtB,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACzC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YACnD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAClE,OAAO,CACR,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACzE,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;YAC5D,MAAM,EAAE,GAAG,qCAAqC,CAAC;YACjD,MAAM,GAAG,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;YACtB,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACpC,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACxC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;YACxD,MAAM,EAAE,GAAG,mCAAmC,CAAC;YAC/C,MAAM,GAAG,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;YACtB,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC/C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC3D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;YACxD,MAAM,GAAG,GAAG,KAAK,CAAC,iDAAiD,CAAC,CAAC;YACrE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACpC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC9C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC1D,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;YACtE,MAAM,GAAG,GAAG,KAAK,CAAC,qBAAqB,CAAC,CAAC;YACzC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,uCAAuC,EAAE,GAAG,EAAE;QACrD,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;YACtE,sEAAsE;YACtE,6CAA6C;YAC7C,MAAM,GAAG,GAAG,KAAK,CAAC,2BAA2B,CAAC,CAAC;YAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACjC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;YACjE,MAAM,GAAG,GAAG,KAAK,CAAC,qBAAqB,CAAC,CAAC;YACzC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACjC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;QACrC,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;YACvE,MAAM,GAAG,GAAG,KAAK,CAAC,8BAA8B,CAAC,CAAC;YAClD,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACpC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC9C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACzD,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC5C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;YACzE,MAAM,GAAG,GAAG,KAAK,CAAC,uBAAuB,CAAC,CAAC;YAC3C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACpC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC5C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC3C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;YAC5E,MAAM,GAAG,GAAG,KAAK,CAAC,qCAAqC,CAAC,CAAC;YACzD,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACpC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YACnD,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAC9C,2BAA2B,CAC5B,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;YACjE,MAAM,GAAG,GAAG,KAAK,CAAC,kBAAkB,CAAC,CAAC;YACtC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACpC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC9C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC5C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAChC,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;YACzC,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;YACzB,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;QACtB,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;YAC5E,MAAM,GAAG,GAAG,KAAK,CAAC,0CAA0C,CAAC,CAAC;YAC9D,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YACzC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC;YACtC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAClC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;YAC5D,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACzC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACrC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;YACjE,MAAM,GAAG,GAAG,KAAK,CAAC,kCAAkC,CAAC,CAAC;YACtD,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YACzC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC5C,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC/C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;YACnE,MAAM,GAAG,GAAG,KAAK,CACf,oFAAoF,CACrF,CAAC;YACF,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACxC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC3C,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;YACzD,MAAM,GAAG,GAAG,KAAK,CAAC,qCAAqC,CAAC,CAAC;YACzD,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACxC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC;YACrC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;YACtC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;QACtC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;YAC/E,0EAA0E;YAC1E,yEAAyE;YACzE,MAAM,GAAG,GAAG,KAAK,CAAC,4CAA4C,CAAC,CAAC;YAChE,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACxC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC;YACrC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;YACtC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;QACtC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;YAC5D,MAAM,GAAG,GAAG,KAAK,CACf,kEAAkE,CACnE,CAAC;YACF,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACxC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACpC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACrC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAC1B,MAAM,KAAK,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;QACtC,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC;YACpB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE;SAC1D,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAC5B,MAAM,KAAK,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;QACtC,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC;YACpB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE;SAC9D,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAC5B,MAAM,KAAK,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;QACpC,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC;YACpB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE;SAC1D,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,cAAc,EAAE,GAAG,EAAE;QACtB,MAAM,KAAK,GAAG,WAAW,CAAC,8BAA8B,CAAC,CAAC;QAC1D,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC;YACpB;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,qBAAqB,EAAE,EAAE,CAAC;aAClE;SACF,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,KAAK,GAAG,WAAW,CACvB,uDAAuD,CACxD,CAAC;QACF,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC3D,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;YACvB,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,MAAM;YACZ,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;SAC1B,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QAC1D,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;YACvB,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;SAC5B,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QAC1D,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;YACvB,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,MAAM;YACZ,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,EAAE,CAAC;SAC5D,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,KAAK,GAAG,WAAW,CAAC,iBAAiB,CAAC,CAAC;QAC7C,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;IAC5C,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,OAAO,GAAG,4BAA4B,CAAC,aAAa,CAAC,CAAC;QAC5D,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
import { describe, it, expect } from "vitest";
import { resolveCredentials } from "../auth/resolve-credentials.js";
const stored = {
publicationUrl: "https://stored.substack.com",
sessionToken: "stored-tok",
userId: "99",
savedAt: "2026-01-01T00:00:00Z",
};
describe("resolveCredentials", () => {
it("prefers env vars when present (source=env)", () => {
const env = {
SUBSTACK_PUBLICATION_URL: "https://env.substack.com",
SUBSTACK_SESSION_TOKEN: "env-tok",
SUBSTACK_USER_ID: "1",
};
const r = resolveCredentials(env, () => stored);
expect(r.publicationUrl).toBe("https://env.substack.com");
expect(r.sessionToken).toBe("env-tok");
expect(r.userId).toBe("1");
expect(r.source).toBe("env");
expect(r.missing).toEqual([]);
});
it("falls back to the stored session when env vars are absent (source=stored)", () => {
const r = resolveCredentials({}, () => stored);
expect(r.publicationUrl).toBe("https://stored.substack.com");
expect(r.sessionToken).toBe("stored-tok");
expect(r.userId).toBe("99");
expect(r.source).toBe("stored");
expect(r.missing).toEqual([]);
});
it("mixes per field, env winning each present field (source=mixed)", () => {
const env = { SUBSTACK_SESSION_TOKEN: "env-tok" };
const r = resolveCredentials(env, () => stored);
expect(r.sessionToken).toBe("env-tok"); // env
expect(r.publicationUrl).toBe("https://stored.substack.com"); // store
expect(r.userId).toBe("99"); // store
expect(r.source).toBe("mixed");
expect(r.missing).toEqual([]);
});
it("reports all missing when neither env nor store supplies them (source=none)", () => {
const r = resolveCredentials({}, () => null);
expect(r.missing).toEqual([
"SUBSTACK_PUBLICATION_URL",
"SUBSTACK_SESSION_TOKEN",
"SUBSTACK_USER_ID",
]);
expect(r.source).toBe("none");
expect(r.publicationUrl).toBe("");
});
it("reports only still-missing fields with a partial store", () => {
const partial = { ...stored, userId: "" };
const r = resolveCredentials({}, () => partial);
expect(r.missing).toEqual(["SUBSTACK_USER_ID"]);
expect(r.source).toBe("stored");
});
});
//# sourceMappingURL=resolve-credentials.test.js.map
{"version":3,"file":"resolve-credentials.test.js","sourceRoot":"","sources":["../../src/__tests__/resolve-credentials.test.ts"],"names":[],"mappings":"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"}
import { describe, it, expect, vi, afterEach } from "vitest";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { createServer } from "../server.js";
import { SubstackClient, MAX_PAGE_SIZE, ANALYTICS_SCAN_DEPTH, } from "../api/client.js";
/**
* Tool-layer regressions for #28.
*
* The clamp and the `describe` strings live in server.ts, so a client-only test
* cannot reach them — and the description is the load-bearing half: it is what
* tells a model that limit=100 is a legal argument. These drive the real
* registered tools over an in-memory MCP transport and assert the limit VALUE
* that reaches the wire.
*/
describe("tool pagination limits (regression: #28)", () => {
afterEach(() => vi.unstubAllGlobals());
function stubFetch() {
const body = { posts: [], total: 0 };
const fetchMock = vi.fn(async (..._args) => ({
ok: true,
status: 200,
json: async () => body,
text: async () => JSON.stringify(body),
}));
vi.stubGlobal("fetch", fetchMock);
return fetchMock;
}
async function connect() {
const server = createServer(new SubstackClient("https://example.substack.com", "tok", "1"));
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
const client = new Client({ name: "test", version: "0.0.0" });
await Promise.all([
client.connect(clientTransport),
server.connect(serverTransport),
]);
return client;
}
function limitOf(fetchMock) {
const url = new URL(String(fetchMock.mock.calls[0][0]));
return Number(url.searchParams.get("limit"));
}
const paginatedTools = [
"list_published_posts",
"list_drafts",
"list_scheduled_posts",
];
for (const name of paginatedTools) {
it(`${name} clamps an over-cap limit instead of sending it`, async () => {
const fetchMock = stubFetch();
const client = await connect();
// A caller (or a model reading the old "1-100" description) passing 100
// must not error — it gets clamped, not rejected.
const result = await client.callTool({
name,
arguments: { limit: 100 },
});
expect(result.isError).toBeFalsy();
expect(limitOf(fetchMock)).toBe(MAX_PAGE_SIZE);
expect(limitOf(fetchMock)).toBeLessThanOrEqual(MAX_PAGE_SIZE);
});
it(`${name} passes an under-cap limit through unchanged`, async () => {
const fetchMock = stubFetch();
const client = await connect();
await client.callTool({ name, arguments: { limit: 7 } });
expect(limitOf(fetchMock)).toBe(7);
});
it(`${name} advertises the real cap, not 100`, async () => {
const client = await connect();
const { tools } = await client.listTools();
const tool = tools.find((t) => t.name === name);
expect(tool).toBeDefined();
const described = String(tool.inputSchema.properties?.limit?.description ?? "");
expect(described).toContain(`1-${MAX_PAGE_SIZE}`);
expect(described).not.toContain("1-100");
});
}
it("get_post_analytics derives its scan depth rather than restating 500", async () => {
const client = await connect();
const { tools } = await client.listTools();
const tool = tools.find((t) => t.name === "get_post_analytics");
expect(tool?.description).toContain(`${ANALYTICS_SCAN_DEPTH} most recent published posts`);
});
it("get_post_analytics reports the same depth in its not-found note", async () => {
stubFetch(); // empty feed: the id is never found
const client = await connect();
const result = await client.callTool({
name: "get_post_analytics",
arguments: { post_id: 12345 },
});
const payload = JSON.parse(result.content[0].text);
expect(payload.found).toBe(false);
expect(payload.note).toContain(`${ANALYTICS_SCAN_DEPTH} most recent published posts`);
});
});
//# sourceMappingURL=server.test.js.map
{"version":3,"file":"server.test.js","sourceRoot":"","sources":["../../src/__tests__/server.test.ts"],"names":[],"mappings":"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"}
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { mkdtempSync, rmSync, writeFileSync, readFileSync, existsSync, } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { saveSession, loadSession, clearSession, sessionDir, } from "../auth/session-store.js";
let dir;
const original = process.env.SUBSTACK_MCP_HOME;
beforeEach(() => {
dir = mkdtempSync(join(tmpdir(), "substack-mcp-test-"));
process.env.SUBSTACK_MCP_HOME = dir;
});
afterEach(() => {
if (original === undefined)
delete process.env.SUBSTACK_MCP_HOME;
else
process.env.SUBSTACK_MCP_HOME = original;
rmSync(dir, { recursive: true, force: true });
});
describe("session store", () => {
it("round-trips a saved session", () => {
saveSession({
publicationUrl: "https://x.substack.com",
sessionToken: "tok",
userId: "42",
});
const loaded = loadSession();
expect(loaded?.publicationUrl).toBe("https://x.substack.com");
expect(loaded?.sessionToken).toBe("tok");
expect(loaded?.userId).toBe("42");
expect(typeof loaded?.savedAt).toBe("string");
});
it("returns null when no session file exists", () => {
expect(loadSession()).toBeNull();
});
it("does not write the token or URL in plaintext", () => {
saveSession({
publicationUrl: "https://secret.substack.com",
sessionToken: "SUPERSECRET",
userId: "1",
});
const raw = readFileSync(join(dir, "session.json"), "utf8");
expect(raw).not.toContain("SUPERSECRET");
expect(raw).not.toContain("secret.substack.com");
});
it("returns null when the ciphertext is tampered (GCM auth fails)", () => {
saveSession({
publicationUrl: "https://x.substack.com",
sessionToken: "tok",
userId: "1",
});
const file = join(dir, "session.json");
const env = JSON.parse(readFileSync(file, "utf8"));
const buf = Buffer.from(env.data, "base64");
buf[0] ^= 0xff; // flip a byte in the ciphertext
env.data = buf.toString("base64");
writeFileSync(file, JSON.stringify(env));
expect(loadSession()).toBeNull();
});
it("returns null for a corrupt, non-JSON file", () => {
writeFileSync(join(dir, "session.json"), "not json at all");
expect(loadSession()).toBeNull();
});
it("returns null for an unknown envelope version", () => {
writeFileSync(join(dir, "session.json"), JSON.stringify({ v: 2, salt: "", iv: "", tag: "", data: "" }));
expect(loadSession()).toBeNull();
});
it("clearSession removes the file", () => {
saveSession({ publicationUrl: "u", sessionToken: "t", userId: "1" });
expect(existsSync(join(dir, "session.json"))).toBe(true);
clearSession();
expect(existsSync(join(dir, "session.json"))).toBe(false);
expect(loadSession()).toBeNull();
});
it("clearSession is a no-op when nothing is stored", () => {
expect(() => clearSession()).not.toThrow();
});
it("sessionDir honors SUBSTACK_MCP_HOME", () => {
expect(sessionDir()).toBe(dir);
});
});
//# sourceMappingURL=session-store.test.js.map
{"version":3,"file":"session-store.test.js","sourceRoot":"","sources":["../../src/__tests__/session-store.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EACL,WAAW,EACX,MAAM,EACN,aAAa,EACb,YAAY,EACZ,UAAU,GACX,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EACL,WAAW,EACX,WAAW,EACX,YAAY,EACZ,UAAU,GACX,MAAM,0BAA0B,CAAC;AAElC,IAAI,GAAW,CAAC;AAChB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;AAE/C,UAAU,CAAC,GAAG,EAAE;IACd,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,oBAAoB,CAAC,CAAC,CAAC;IACxD,OAAO,CAAC,GAAG,CAAC,iBAAiB,GAAG,GAAG,CAAC;AACtC,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,GAAG,EAAE;IACb,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;;QAC5D,OAAO,CAAC,GAAG,CAAC,iBAAiB,GAAG,QAAQ,CAAC;IAC9C,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AAChD,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,WAAW,CAAC;YACV,cAAc,EAAE,wBAAwB;YACxC,YAAY,EAAE,KAAK;YACnB,MAAM,EAAE,IAAI;SACb,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,WAAW,EAAE,CAAC;QAC7B,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QAC9D,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,CAAC,OAAO,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,WAAW,CAAC;YACV,cAAc,EAAE,6BAA6B;YAC7C,YAAY,EAAE,aAAa;YAC3B,MAAM,EAAE,GAAG;SACZ,CAAC,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC;QAC5D,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;QACzC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,WAAW,CAAC;YACV,cAAc,EAAE,wBAAwB;YACxC,YAAY,EAAE,KAAK;YACnB,MAAM,EAAE,GAAG;SACZ,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QACvC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QACnD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC5C,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,gCAAgC;QAChD,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAClC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;QACzC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,iBAAiB,CAAC,CAAC;QAC5D,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,aAAa,CACX,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EACzB,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAC9D,CAAC;QACF,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,WAAW,CAAC,EAAE,cAAc,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QACrE,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzD,YAAY,EAAE,CAAC;QACf,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1D,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,CAAC,GAAG,EAAE,CAAC,YAAY,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}