The deployment owner adds folders of photos and videos in the media settings. A per-folder background job indexes them (relative path, kind, size, capture date, dimensions, duration), re-indexes every six hours, and drops files that are gone. The owner's own runs get two tools: search_media (words in folder and file names, kind, capture date range, newest first) and import_media (copies one file into the conversation as an artifact). Files are never changed; hidden entries and symlinks are skipped, and an import must resolve inside its folder. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wy1md9QE1ayJbDCwJ4vAQs
159 lines
5.7 KiB
TypeScript
159 lines
5.7 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import {
|
|
dispatchBackgroundJob,
|
|
historyCompactJob,
|
|
historyCompactJobKey,
|
|
MEDIA_JOB_QUEUE,
|
|
MEDIA_LIBRARY_JOB_QUEUE,
|
|
mediaLibrarySyncJob,
|
|
mediaLibrarySyncJobKey,
|
|
mediaRunJob,
|
|
mediaRunJobKey,
|
|
messagingDeliverJob,
|
|
parseBackgroundJob,
|
|
profileConsolidateJob,
|
|
profileConsolidateJobKey,
|
|
} from "./background-jobs.js";
|
|
import type { BackgroundJobHandlers } from "./types.js";
|
|
|
|
function handlers(): BackgroundJobHandlers {
|
|
return {
|
|
"run.continue": vi.fn(async () => undefined),
|
|
"routine.wakeup": vi.fn(async () => undefined),
|
|
"computer.update": vi.fn(async () => undefined),
|
|
"computer.sleep": vi.fn(async () => undefined),
|
|
"computer.control-expire": vi.fn(async () => undefined),
|
|
"skill.teaching-expire": vi.fn(async () => undefined),
|
|
"history.compact": vi.fn(async () => undefined),
|
|
"messaging.deliver": vi.fn(async () => undefined),
|
|
"cloud_agent.poll": vi.fn(async () => undefined),
|
|
"profile.consolidate": vi.fn(async () => undefined),
|
|
"media.run": vi.fn(async () => undefined),
|
|
"media.library.sync": vi.fn(async () => undefined),
|
|
};
|
|
}
|
|
|
|
describe("background job contracts", () => {
|
|
it("validates and dispatches messaging.deliver", async () => {
|
|
const target = handlers();
|
|
await dispatchBackgroundJob(target, "messaging.deliver", { runId: "run-1" });
|
|
expect(target["messaging.deliver"]).toHaveBeenCalledWith({ runId: "run-1" });
|
|
expect(messagingDeliverJob("run-1")).toEqual({
|
|
name: "messaging.deliver",
|
|
payload: { runId: "run-1" },
|
|
replaceKey: "messaging.deliver:run-1",
|
|
});
|
|
expect(messagingDeliverJob()).toEqual({
|
|
name: "messaging.deliver",
|
|
payload: {},
|
|
replaceKey: "messaging.deliver:drain",
|
|
});
|
|
});
|
|
|
|
it("validates and dispatches a typed job", async () => {
|
|
const target = handlers();
|
|
await dispatchBackgroundJob(target, "routine.wakeup", {
|
|
routineId: "routine-1",
|
|
scheduledFor: "2026-08-15T12:00:00.000Z",
|
|
});
|
|
expect(target["routine.wakeup"]).toHaveBeenCalledWith({
|
|
routineId: "routine-1",
|
|
scheduledFor: "2026-08-15T12:00:00.000Z",
|
|
});
|
|
});
|
|
|
|
it("rejects unknown names and malformed deliveries", () => {
|
|
expect(() => parseBackgroundJob("unknown", {})).toThrow("Unknown background job");
|
|
expect(() =>
|
|
parseBackgroundJob("routine.wakeup", {
|
|
routineId: "routine-1",
|
|
scheduledFor: "not-a-date",
|
|
}),
|
|
).toThrow();
|
|
expect(() => parseBackgroundJob("run.continue", { runId: "" })).toThrow();
|
|
expect(() =>
|
|
parseBackgroundJob("computer.control-expire", {
|
|
computerId: "computer-1",
|
|
leaseId: "",
|
|
}),
|
|
).toThrow();
|
|
});
|
|
|
|
it("validates and dispatches a control-expiry job", async () => {
|
|
const target = handlers();
|
|
await dispatchBackgroundJob(target, "computer.control-expire", {
|
|
computerId: "computer-1",
|
|
leaseId: "lease-1",
|
|
});
|
|
expect(target["computer.control-expire"]).toHaveBeenCalledWith({
|
|
computerId: "computer-1",
|
|
leaseId: "lease-1",
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("historyCompactJob", () => {
|
|
it("builds a job with a replace key scoped to the thread", () => {
|
|
expect(historyCompactJob("thread-1")).toEqual({
|
|
name: "history.compact",
|
|
payload: { threadId: "thread-1" },
|
|
replaceKey: historyCompactJobKey("thread-1"),
|
|
});
|
|
});
|
|
|
|
it("keys different threads differently", () => {
|
|
expect(historyCompactJobKey("thread-1")).not.toBe(historyCompactJobKey("thread-2"));
|
|
});
|
|
});
|
|
|
|
describe("profileConsolidateJob", () => {
|
|
it("validates, dispatches, and keys one pending consolidation per user", async () => {
|
|
const target = handlers();
|
|
await dispatchBackgroundJob(target, "profile.consolidate", { userId: "user-1" });
|
|
expect(target["profile.consolidate"]).toHaveBeenCalledWith({ userId: "user-1" });
|
|
expect(() => parseBackgroundJob("profile.consolidate", { userId: "" })).toThrow();
|
|
expect(profileConsolidateJob("user-1")).toEqual({
|
|
name: "profile.consolidate",
|
|
payload: { userId: "user-1" },
|
|
replaceKey: profileConsolidateJobKey("user-1"),
|
|
});
|
|
expect(profileConsolidateJobKey("user-1")).not.toBe(profileConsolidateJobKey("user-2"));
|
|
});
|
|
});
|
|
|
|
describe("mediaRunJob", () => {
|
|
it("validates, dispatches, and runs every media job on the shared media queue", async () => {
|
|
const target = handlers();
|
|
await dispatchBackgroundJob(target, "media.run", { jobId: "job-1" });
|
|
expect(target["media.run"]).toHaveBeenCalledWith({ jobId: "job-1" });
|
|
expect(() => parseBackgroundJob("media.run", { jobId: "" })).toThrow();
|
|
expect(mediaRunJob("job-1")).toEqual({
|
|
name: "media.run",
|
|
payload: { jobId: "job-1" },
|
|
replaceKey: mediaRunJobKey("job-1"),
|
|
queue: MEDIA_JOB_QUEUE,
|
|
});
|
|
const at = new Date("2026-10-03T12:00:00.000Z");
|
|
expect(mediaRunJob("job-1", at).availableAt).toBe(at);
|
|
expect(mediaRunJobKey("job-1")).not.toBe(mediaRunJobKey("job-2"));
|
|
});
|
|
});
|
|
|
|
describe("mediaLibrarySyncJob", () => {
|
|
it("validates, dispatches, and keeps one pending sync per folder on the library queue", async () => {
|
|
const target = handlers();
|
|
await dispatchBackgroundJob(target, "media.library.sync", { sourceId: "source-1" });
|
|
expect(target["media.library.sync"]).toHaveBeenCalledWith({ sourceId: "source-1" });
|
|
expect(() => parseBackgroundJob("media.library.sync", { sourceId: "" })).toThrow();
|
|
const at = new Date("2026-10-05T12:00:00.000Z");
|
|
expect(mediaLibrarySyncJob("source-1", at)).toEqual({
|
|
name: "media.library.sync",
|
|
payload: { sourceId: "source-1" },
|
|
replaceKey: mediaLibrarySyncJobKey("source-1"),
|
|
queue: MEDIA_LIBRARY_JOB_QUEUE,
|
|
availableAt: at,
|
|
});
|
|
expect(mediaLibrarySyncJob("source-1").availableAt).toBeUndefined();
|
|
});
|
|
});
|