Files
emdash-cms--emdash/packages/core/tests/unit/astro/request-context-route-cache.test.ts
wehub-resource-sync b3a7f98e5a
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:23:53 +08:00

99 lines
3.3 KiB
TypeScript

/**
* Regression tests: preview and toolbar responses must opt out of Astro's
* route cache, not just set `Cache-Control`.
*
* `Cache-Control: private, no-store` governs browsers and downstream
* proxies, but the shared edge cache (e.g. Workers Cache on Cloudflare)
* follows the route-cache options — on Cloudflare the adapter emits
* `Cloudflare-CDN-Cache-Control` from them. Verified on a stock deploy:
* a valid preview URL was a MISS on request 1 and a shared-cache HIT on
* requests 2+, i.e. draft content was served without any token
* verification until TTL/purge, even after token expiry.
*
* The fix calls `context.cache.set(false)` for requests carrying a
* `_preview` param and for toolbar-injected editor responses.
*/
import { describe, it, expect, vi } from "vitest";
vi.mock("astro:middleware", () => ({
defineMiddleware: (handler: unknown) => handler,
}));
import onRequest from "../../../src/astro/middleware/request-context.js";
function buildContext(opts: {
pathname?: string;
search?: string;
user?: { id: string; role: number } | null;
editCookie?: boolean;
cache: { set: (input: unknown) => void };
}) {
const url = new URL(`https://example.com${opts.pathname ?? "/blog"}${opts.search ?? ""}`);
return {
request: new Request(url),
url,
cookies: {
get: vi.fn((name: string) =>
name === "emdash-edit-mode" && opts.editCookie ? { value: "true" } : undefined,
),
set: vi.fn(),
},
locals: { user: opts.user ?? null },
cache: opts.cache,
} as unknown as Parameters<typeof onRequest>[0];
}
const htmlResponse = () =>
new Response("<html><body>hello</body></html>", {
headers: { "content-type": "text/html" },
});
describe("route-cache opt-out for preview and toolbar responses", () => {
it("opts out of the route cache when a _preview param is present", async () => {
const cache = { set: vi.fn() };
// No emdash.db on locals — token can't be verified, but the response
// still must not be stored under the per-token URL.
const context = buildContext({ search: "?_preview=some-token", cache });
await onRequest(context, async () => htmlResponse());
expect(cache.set).toHaveBeenCalledWith(false);
});
it("opts out of the route cache when the toolbar is injected", async () => {
const cache = { set: vi.fn() };
const context = buildContext({ user: { id: "u1", role: 30 }, cache });
const response = await onRequest(context, async () => htmlResponse());
// Confirm we're on the actual-injection path.
expect(await response.text()).toContain('id="emdash-toolbar"');
expect(cache.set).toHaveBeenCalledWith(false);
});
it("leaves the route cache alone for editor requests without injection (non-HTML)", async () => {
const cache = { set: vi.fn() };
const context = buildContext({
pathname: "/api/data.json",
user: { id: "u1", role: 30 },
cache,
});
await onRequest(
context,
async () => new Response('{"ok":true}', { headers: { "content-type": "application/json" } }),
);
expect(cache.set).not.toHaveBeenCalled();
});
it("leaves the route cache alone for anonymous requests without CMS signals", async () => {
const cache = { set: vi.fn() };
const context = buildContext({ cache });
await onRequest(context, async () => htmlResponse());
expect(cache.set).not.toHaveBeenCalled();
});
});