-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathresourceCatalog.test.ts
More file actions
154 lines (132 loc) · 5.75 KB
/
Copy pathresourceCatalog.test.ts
File metadata and controls
154 lines (132 loc) · 5.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import { afterEach, describe, expect, it, vi } from "vitest";
import {
NO_FILE_CONTEXT,
StandardResourceCatalog,
} from "../src/v3/resource-catalog/standardResourceCatalog.js";
// Regression tests for COULD_NOT_FIND_EXECUTOR on warm worker processes when
// a task's `task()` / `schemaTask()` call is evaluated during another task's
// execution (e.g. as a side effect of `await import(...)` of a module that
// contains a task definition).
//
// Production throw site:
// - managed-run-worker.ts:566 (post-wrap)
// - dev-run-worker.ts:578 (post-wrap)
// Pre-fix symptom: `resourceCatalog.getTask(execution.task.id)` returned
// undefined even after the worker re-imported the task entrypoint.
//
// Pre-fix mechanism: `registerTaskMetadata` silently returned when
// `_currentFileContext` was unset. Any `task()` call firing during a
// running task's run() / lifecycle hooks (directly, or transitively via a
// dynamic import) hit the silent guard. Node's ESM module cache then
// prevented recovery — the worker's setContext + re-import fallback didn't
// re-evaluate the module body, so the `task()` call never fired again.
//
// Post-fix: the runtime workers wrap their `executor.execute(...)` call with
// `setCurrentFileContext(NO_FILE_CONTEXT, NO_FILE_CONTEXT)` so any `task()`
// call firing during execution registers normally with sentinel file
// metadata. The catalog detects the sentinel and emits a one-time warning
// per task id to keep the bundle-shape pattern visible. The indexer never
// sets this sentinel context — its behavior is unchanged.
describe("StandardResourceCatalog — runtime registration via sentinel context", () => {
afterEach(() => {
delete (globalThis as { __catalogRegisterTaskMetadata?: unknown })
.__catalogRegisterTaskMetadata;
vi.restoreAllMocks();
});
it("silently drops registration when no context is set (indexer's invariant)", () => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
const catalog = new StandardResourceCatalog();
catalog.registerTaskMetadata({
id: "no-context-task",
fns: { run: async () => "ok" },
});
expect(catalog.getTask("no-context-task")).toBeUndefined();
expect(warn).not.toHaveBeenCalled();
});
it(
"registers normally and warns once when the sentinel context is set " +
"(simulates the worker's executor wrap)",
() => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
const catalog = new StandardResourceCatalog();
catalog.setCurrentFileContext(NO_FILE_CONTEXT, NO_FILE_CONTEXT);
catalog.registerTaskMetadata({
id: "lazy-task",
fns: { run: async () => "ok" },
});
catalog.clearCurrentFileContext();
const registered = catalog.getTask("lazy-task");
expect(registered).toBeDefined();
expect(registered?.id).toBe("lazy-task");
expect(registered?.filePath).toBe(NO_FILE_CONTEXT);
expect(registered?.entryPoint).toBe(NO_FILE_CONTEXT);
expect(warn).toHaveBeenCalledTimes(1);
expect(warn.mock.calls[0]?.[0]).toContain("lazy-task");
}
);
it(
"warm-start path: a task whose top-level definition fires during a " +
"dynamic import inside the sentinel wrap remains findable; the " +
"worker's setContext + re-import fallback (managed-run-worker.ts:482) " +
"is not needed",
async () => {
vi.spyOn(console, "warn").mockImplementation(() => {});
const catalog = new StandardResourceCatalog();
(globalThis as { __catalogRegisterTaskMetadata?: unknown })
.__catalogRegisterTaskMetadata = (
task: Parameters<StandardResourceCatalog["registerTaskMetadata"]>[0]
) => {
catalog.registerTaskMetadata(task);
};
// Simulate the worker wrap: setContext(NO_FILE_CONTEXT) → run user code
// (which does a dynamic import) → clearContext.
catalog.setCurrentFileContext(NO_FILE_CONTEXT, NO_FILE_CONTEXT);
await import("./fixtures/dynamic-task-module.mjs");
catalog.clearCurrentFileContext();
const registered = catalog.getTask("lazy-task");
expect(registered).toBeDefined();
expect(registered?.filePath).toBe(NO_FILE_CONTEXT);
}
);
it("warns at most once per task id under the sentinel context", () => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
const catalog = new StandardResourceCatalog();
catalog.setCurrentFileContext(NO_FILE_CONTEXT, NO_FILE_CONTEXT);
const register = (id: string) =>
catalog.registerTaskMetadata({
id,
fns: { run: async () => "ok" },
});
register("task-a");
register("task-a");
register("task-a");
expect(warn).toHaveBeenCalledTimes(1);
register("task-b");
expect(warn).toHaveBeenCalledTimes(2);
catalog.clearCurrentFileContext();
});
it(
"control: real file context registers without firing the sentinel warning",
async () => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
const catalog = new StandardResourceCatalog();
(globalThis as { __catalogRegisterTaskMetadata?: unknown })
.__catalogRegisterTaskMetadata = (
task: Parameters<StandardResourceCatalog["registerTaskMetadata"]>[0]
) => {
catalog.registerTaskMetadata(task);
};
catalog.setCurrentFileContext(
"/app/dist/lazy-task.entry.mjs",
"src/tasks/lazy-task.ts"
);
await import("./fixtures/dynamic-task-module.mjs?control");
catalog.clearCurrentFileContext();
const task = catalog.getTask("lazy-task");
expect(task).toBeDefined();
expect(task?.filePath).toBe("/app/dist/lazy-task.entry.mjs");
expect(task?.entryPoint).toBe("src/tasks/lazy-task.ts");
expect(warn).not.toHaveBeenCalled();
}
);
});