Files
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

165 lines
4.6 KiB
TypeScript

import type { Kysely } from "kysely";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { EmDashConfig } from "../../../src/astro/integration/runtime.js";
import { ContentRepository } from "../../../src/database/repositories/content.js";
import type { Database } from "../../../src/database/types.js";
import { EmDashRuntime } from "../../../src/emdash-runtime.js";
import { definePlugin } from "../../../src/plugins/define-plugin.js";
import { createHookPipeline } from "../../../src/plugins/hooks.js";
import type { ContentScheduleStateChangeEvent } from "../../../src/plugins/types.js";
import { setupTestDatabaseWithCollections, teardownTestDatabase } from "../../utils/test-db.js";
const { deferredTasks } = vi.hoisted(() => ({
deferredTasks: [] as Array<() => void | Promise<void>>,
}));
vi.mock("../../../src/after.js", () => ({
after: vi.fn((fn: () => void | Promise<void>) => {
deferredTasks.push(fn);
}),
}));
async function flushLatestDeferredHook(): Promise<void> {
const task = deferredTasks.pop();
if (task) await task();
}
function buildRuntime(
db: Kysely<Database>,
afterSchedule: (event: ContentScheduleStateChangeEvent) => void,
afterUnschedule: (event: ContentScheduleStateChangeEvent) => void,
): EmDashRuntime {
const plugin = definePlugin({
id: "schedule-sync-test",
version: "1.0.0",
capabilities: ["content:read"],
hooks: {
"content:afterSchedule": (event) => {
afterSchedule(event);
},
"content:afterUnschedule": (event) => {
afterUnschedule(event);
},
},
});
const config: EmDashConfig = {};
const pipelineFactoryOptions = { db } as const;
const hooks = createHookPipeline([plugin], pipelineFactoryOptions);
const runtimeDeps = {
config,
plugins: [plugin],
// eslint-disable-next-line typescript/no-explicit-any -- match RuntimeDependencies signature
createDialect: (() => {
throw new Error("createDialect not used in this test");
}) as any,
createStorage: null,
sandboxEnabled: false,
sandboxedPluginEntries: [],
createSandboxRunner: null,
};
return new EmDashRuntime({
db,
storage: null,
configuredPlugins: [],
sandboxedPlugins: new Map(),
sandboxedPluginEntries: [],
hooks,
enabledPlugins: new Set(),
pluginStates: new Map(),
config,
mediaProviders: new Map(),
mediaProviderEntries: [],
cronExecutor: null,
cronScheduler: null,
emailPipeline: null,
allPipelinePlugins: [plugin],
pipelineFactoryOptions,
runtimeDeps,
pipelineRef: { current: hooks },
});
}
describe("content scheduling hooks", () => {
let db: Kysely<Database>;
let repo: ContentRepository;
let afterSchedule: ReturnType<typeof vi.fn>;
let afterUnschedule: ReturnType<typeof vi.fn>;
let runtime: EmDashRuntime;
beforeEach(async () => {
deferredTasks.length = 0;
db = await setupTestDatabaseWithCollections();
repo = new ContentRepository(db);
afterSchedule = vi.fn();
afterUnschedule = vi.fn();
runtime = buildRuntime(db, afterSchedule, afterUnschedule);
});
afterEach(async () => {
await teardownTestDatabase(db);
});
it("fires content:afterSchedule when a draft is scheduled", async () => {
const item = await repo.create({
type: "post",
slug: "scheduled-post",
status: "draft",
data: { title: "Scheduled post" },
});
deferredTasks.length = 0;
const scheduledAt = new Date(Date.now() + 86_400_000).toISOString();
const result = await runtime.handleContentSchedule("post", item.id, scheduledAt);
expect(result.success).toBe(true);
expect(afterSchedule).not.toHaveBeenCalled();
await flushLatestDeferredHook();
expect(afterSchedule).toHaveBeenCalledTimes(1);
expect(afterSchedule).toHaveBeenCalledWith(
expect.objectContaining({
collection: "post",
content: expect.objectContaining({
id: item.id,
status: "scheduled",
scheduledAt,
}),
}),
);
});
it("fires content:afterUnschedule when scheduled content is unscheduled", async () => {
const item = await repo.create({
type: "post",
slug: "scheduled-post",
status: "draft",
data: { title: "Scheduled post" },
});
const scheduledAt = new Date(Date.now() + 86_400_000).toISOString();
await repo.schedule("post", item.id, scheduledAt);
deferredTasks.length = 0;
const result = await runtime.handleContentUnschedule("post", item.id);
expect(result.success).toBe(true);
expect(afterUnschedule).not.toHaveBeenCalled();
await flushLatestDeferredHook();
expect(afterUnschedule).toHaveBeenCalledTimes(1);
expect(afterUnschedule).toHaveBeenCalledWith(
expect.objectContaining({
collection: "post",
content: expect.objectContaining({
id: item.id,
status: "draft",
scheduledAt: null,
}),
}),
);
});
});