@databricks/appkit 0.61.0 → 0.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/CLAUDE.md +1 -0
  2. package/NOTICE.md +1 -0
  3. package/dist/agents/databricks.d.ts.map +1 -1
  4. package/dist/agents/databricks.js.map +1 -1
  5. package/dist/agents/supervisor-api.d.ts.map +1 -1
  6. package/dist/agents/supervisor-api.js.map +1 -1
  7. package/dist/app/index.d.ts.map +1 -1
  8. package/dist/app/index.js.map +1 -1
  9. package/dist/appkit/package.js +1 -1
  10. package/dist/cache/index.d.ts.map +1 -1
  11. package/dist/cache/index.js.map +1 -1
  12. package/dist/cache/storage/memory.js.map +1 -1
  13. package/dist/cache/storage/persistent.js.map +1 -1
  14. package/dist/cli/commands/codemod/index.js.map +1 -1
  15. package/dist/cli/commands/codemod/on-plugins-ready.js.map +1 -1
  16. package/dist/cli/commands/docs.js.map +1 -1
  17. package/dist/cli/commands/doctor/bundle.js.map +1 -1
  18. package/dist/cli/commands/doctor/index.js.map +1 -1
  19. package/dist/cli/commands/doctor/report.js.map +1 -1
  20. package/dist/cli/commands/doctor/resolve-targets.js +4 -6
  21. package/dist/cli/commands/doctor/resolve-targets.js.map +1 -1
  22. package/dist/cli/commands/doctor/run.js.map +1 -1
  23. package/dist/cli/commands/generate-types.js.map +1 -1
  24. package/dist/cli/commands/lint.js.map +1 -1
  25. package/dist/cli/commands/plugin/add-resource/add-resource.js.map +1 -1
  26. package/dist/cli/commands/plugin/create/create.js.map +1 -1
  27. package/dist/cli/commands/plugin/create/prompt-resource.js.map +1 -1
  28. package/dist/cli/commands/plugin/create/scaffold.js.map +1 -1
  29. package/dist/cli/commands/plugin/index.js.map +1 -1
  30. package/dist/cli/commands/plugin/list/list.js.map +1 -1
  31. package/dist/cli/commands/plugin/promote/promote.js.map +1 -1
  32. package/dist/cli/commands/plugin/sync/sync.js.map +1 -1
  33. package/dist/cli/commands/plugin/validate/validate-manifest.js.map +1 -1
  34. package/dist/cli/commands/plugin/validate/validate.js.map +1 -1
  35. package/dist/cli/commands/registry/add.js.map +1 -1
  36. package/dist/cli/commands/registry/client.js.map +1 -1
  37. package/dist/cli/commands/registry/config-writer.js.map +1 -1
  38. package/dist/cli/commands/registry/env-reconcile.js.map +1 -1
  39. package/dist/cli/commands/registry/env-writer.js.map +1 -1
  40. package/dist/cli/commands/registry/index.js.map +1 -1
  41. package/dist/cli/commands/registry/info.js.map +1 -1
  42. package/dist/cli/commands/registry/list.js.map +1 -1
  43. package/dist/cli/commands/registry/requirements.js.map +1 -1
  44. package/dist/cli/commands/registry/server-register.js.map +1 -1
  45. package/dist/cli/commands/registry/workspace-picker.js.map +1 -1
  46. package/dist/cli/commands/setup.js.map +1 -1
  47. package/dist/cli/index.js.map +1 -1
  48. package/dist/connectors/files/client.js.map +1 -1
  49. package/dist/connectors/lakebase/index.d.ts.map +1 -1
  50. package/dist/connectors/lakebase/index.js.map +1 -1
  51. package/dist/connectors/lakebase/pool-manager.d.ts.map +1 -1
  52. package/dist/connectors/lakebase/pool-manager.js.map +1 -1
  53. package/dist/connectors/lakebase/routing-pool.d.ts.map +1 -1
  54. package/dist/connectors/lakebase/routing-pool.js.map +1 -1
  55. package/dist/connectors/mcp/client.d.ts.map +1 -1
  56. package/dist/connectors/mcp/client.js.map +1 -1
  57. package/dist/connectors/sql-warehouse/client.js.map +1 -1
  58. package/dist/context/client-options.js.map +1 -1
  59. package/dist/context/execution-context.d.ts.map +1 -1
  60. package/dist/context/execution-context.js.map +1 -1
  61. package/dist/context/index.d.ts +1 -1
  62. package/dist/context/service-context.d.ts +55 -3
  63. package/dist/context/service-context.d.ts.map +1 -1
  64. package/dist/context/service-context.js.map +1 -1
  65. package/dist/core/agent/build-toolkit.js.map +1 -1
  66. package/dist/core/agent/load-agents.d.ts.map +1 -1
  67. package/dist/core/agent/load-agents.js.map +1 -1
  68. package/dist/core/agent/run-agent.d.ts.map +1 -1
  69. package/dist/core/agent/run-agent.js.map +1 -1
  70. package/dist/core/agent/toolkit-resolver.js.map +1 -1
  71. package/dist/core/agent/tools/define-tool.d.ts.map +1 -1
  72. package/dist/core/agent/tools/define-tool.js.map +1 -1
  73. package/dist/core/agent/tools/tool.d.ts.map +1 -1
  74. package/dist/core/agent/tools/tool.js.map +1 -1
  75. package/dist/core/agent/types.d.ts.map +1 -1
  76. package/dist/core/agent/types.js.map +1 -1
  77. package/dist/core/appkit.d.ts.map +1 -1
  78. package/dist/core/appkit.js.map +1 -1
  79. package/dist/core/lifecycle-manager.js.map +1 -1
  80. package/dist/core/plugin-context.d.ts +13 -0
  81. package/dist/core/plugin-context.d.ts.map +1 -1
  82. package/dist/core/plugin-context.js +12 -1
  83. package/dist/core/plugin-context.js.map +1 -1
  84. package/dist/errors/configuration.d.ts.map +1 -1
  85. package/dist/errors/configuration.js.map +1 -1
  86. package/dist/logging/logger.js.map +1 -1
  87. package/dist/logging/wide-event-emitter.js.map +1 -1
  88. package/dist/plugin/dev-reader.d.ts.map +1 -1
  89. package/dist/plugin/dev-reader.js.map +1 -1
  90. package/dist/plugin/interceptors/cache.js.map +1 -1
  91. package/dist/plugin/interceptors/retry.js.map +1 -1
  92. package/dist/plugin/interceptors/telemetry.js.map +1 -1
  93. package/dist/plugin/plugin.d.ts +2 -2
  94. package/dist/plugin/plugin.d.ts.map +1 -1
  95. package/dist/plugin/plugin.js.map +1 -1
  96. package/dist/plugins/agents/agents.d.ts.map +1 -1
  97. package/dist/plugins/agents/agents.js.map +1 -1
  98. package/dist/plugins/agents/event-translator.js.map +1 -1
  99. package/dist/plugins/agents/thread-store.js.map +1 -1
  100. package/dist/plugins/ai-search/ai-search.d.ts.map +1 -1
  101. package/dist/plugins/ai-search/ai-search.js.map +1 -1
  102. package/dist/plugins/analytics/analytics.d.ts.map +1 -1
  103. package/dist/plugins/analytics/analytics.js.map +1 -1
  104. package/dist/plugins/analytics/mv/cache.js.map +1 -1
  105. package/dist/plugins/analytics/mv/constants.js.map +1 -1
  106. package/dist/plugins/analytics/mv/formatters.js.map +1 -1
  107. package/dist/plugins/analytics/mv/metadata.js.map +1 -1
  108. package/dist/plugins/analytics/mv/registry.js.map +1 -1
  109. package/dist/plugins/analytics/mv/schemas.js.map +1 -1
  110. package/dist/plugins/analytics/query.js.map +1 -1
  111. package/dist/plugins/analytics/result-delivery.js.map +1 -1
  112. package/dist/plugins/files/helpers.js.map +1 -1
  113. package/dist/plugins/files/plugin.d.ts.map +1 -1
  114. package/dist/plugins/files/plugin.js.map +1 -1
  115. package/dist/plugins/files/types.d.ts.map +1 -1
  116. package/dist/plugins/genie/genie.d.ts.map +1 -1
  117. package/dist/plugins/genie/genie.js.map +1 -1
  118. package/dist/plugins/jobs/plugin.d.ts.map +1 -1
  119. package/dist/plugins/jobs/plugin.js.map +1 -1
  120. package/dist/plugins/jobs/types.d.ts.map +1 -1
  121. package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
  122. package/dist/plugins/lakebase/lakebase.js.map +1 -1
  123. package/dist/plugins/lakebase/types.d.ts.map +1 -1
  124. package/dist/plugins/server/base-server.js.map +1 -1
  125. package/dist/plugins/server/client-config-sanitizer.js.map +1 -1
  126. package/dist/plugins/server/index.d.ts.map +1 -1
  127. package/dist/plugins/server/index.js.map +1 -1
  128. package/dist/plugins/server/react-source-loc-vite-plugin.js.map +1 -1
  129. package/dist/plugins/server/remote-tunnel/remote-tunnel-controller.js.map +1 -1
  130. package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js.map +1 -1
  131. package/dist/plugins/server/static-server.js.map +1 -1
  132. package/dist/plugins/server/utils.js.map +1 -1
  133. package/dist/plugins/server/vite-dev-server.js.map +1 -1
  134. package/dist/plugins/serving/schema-filter.js.map +1 -1
  135. package/dist/plugins/serving/serving.d.ts.map +1 -1
  136. package/dist/plugins/serving/serving.js.map +1 -1
  137. package/dist/plugins/ui-variants/index.js.map +1 -1
  138. package/dist/registry/manifest-loader.d.ts.map +1 -1
  139. package/dist/registry/manifest-loader.js.map +1 -1
  140. package/dist/registry/resource-registry.d.ts.map +1 -1
  141. package/dist/registry/resource-registry.js.map +1 -1
  142. package/dist/registry/types.d.ts.map +1 -1
  143. package/dist/registry/types.js.map +1 -1
  144. package/dist/schemas/manifest.d.ts.map +1 -1
  145. package/dist/schemas/manifest.js.map +1 -1
  146. package/dist/schemas/metric-fqn.js.map +1 -1
  147. package/dist/shared/src/plugin.d.ts.map +1 -1
  148. package/dist/shared/src/schemas/manifest.d.ts.map +1 -1
  149. package/dist/shared/src/schemas/metric-fqn.js.map +1 -1
  150. package/dist/shared/src/schemas/metric-metadata-bundle.js.map +1 -1
  151. package/dist/shared/src/schemas/metric-source.js.map +1 -1
  152. package/dist/shared/src/sse/analytics.js.map +1 -1
  153. package/dist/shared/src/workspace-client/legacy.js.map +1 -1
  154. package/dist/shared/src/workspace-client/types.d.ts.map +1 -1
  155. package/dist/stream/sse-writer.js.map +1 -1
  156. package/dist/stream/stream-manager.d.ts.map +1 -1
  157. package/dist/stream/stream-manager.js.map +1 -1
  158. package/dist/stream/types.js.map +1 -1
  159. package/dist/telemetry/instrumentations.js.map +1 -1
  160. package/dist/telemetry/telemetry-manager.js.map +1 -1
  161. package/dist/telemetry/telemetry-provider.js.map +1 -1
  162. package/dist/telemetry/trace-sampler.js.map +1 -1
  163. package/dist/testing/expect-stream.d.ts +111 -0
  164. package/dist/testing/expect-stream.d.ts.map +1 -0
  165. package/dist/testing/expect-stream.js +154 -0
  166. package/dist/testing/expect-stream.js.map +1 -0
  167. package/dist/testing/fixtures.d.ts +257 -0
  168. package/dist/testing/fixtures.d.ts.map +1 -0
  169. package/dist/testing/fixtures.js +385 -0
  170. package/dist/testing/fixtures.js.map +1 -0
  171. package/dist/testing/index.d.ts +5 -0
  172. package/dist/testing/index.js +5 -0
  173. package/dist/testing/test-plugin-context.d.ts +158 -0
  174. package/dist/testing/test-plugin-context.d.ts.map +1 -0
  175. package/dist/testing/test-plugin-context.js +139 -0
  176. package/dist/testing/test-plugin-context.js.map +1 -0
  177. package/dist/type-generator/cache.js.map +1 -1
  178. package/dist/type-generator/index.js.map +1 -1
  179. package/dist/type-generator/migration.js.map +1 -1
  180. package/dist/type-generator/mv-registry/config.js.map +1 -1
  181. package/dist/type-generator/query-registry.js.map +1 -1
  182. package/dist/type-generator/serving/cache.js.map +1 -1
  183. package/dist/type-generator/serving/generator.js.map +1 -1
  184. package/dist/type-generator/serving/server-file-extractor.d.ts.map +1 -1
  185. package/dist/type-generator/serving/server-file-extractor.js.map +1 -1
  186. package/dist/type-generator/serving/vite-plugin.d.ts.map +1 -1
  187. package/dist/type-generator/serving/vite-plugin.js.map +1 -1
  188. package/dist/type-generator/vite-plugin.d.ts.map +1 -1
  189. package/dist/type-generator/vite-plugin.js.map +1 -1
  190. package/dist/workspace-client/legacy.js.map +1 -1
  191. package/dist/workspace-client/types.d.ts.map +1 -1
  192. package/docs/plugins/testing.md +245 -0
  193. package/llms.txt +1 -0
  194. package/package.json +15 -2
  195. package/sbom.cdx.json +1 -1
  196. package/skills/appkit-ui-variants/SKILL.md +1 -1
@@ -0,0 +1,257 @@
1
+ import { IAppRouter } from "../shared/src/plugin.js";
2
+ import "../shared/src/index.js";
3
+ import { ITelemetry } from "../telemetry/types.js";
4
+ import { ServiceContext, ServiceContextState } from "../context/service-context.js";
5
+ import * as vitest from "vitest";
6
+
7
+ //#region src/testing/fixtures.d.ts
8
+ type Any = any;
9
+ /**
10
+ * Creates a mock telemetry provider for testing. Every span/meter/logger is a
11
+ * `vi.fn()` no-op, so plugins that trace, count, or log run without a live
12
+ * OpenTelemetry pipeline. Passed into {@link createTestPluginContext} as the one
13
+ * injectable production seam.
14
+ */
15
+ declare function createMockTelemetry(): ITelemetry;
16
+ /**
17
+ * Creates a mock Express router that captures registered handlers so a test
18
+ * can pull a handler back out by method + path and invoke it directly.
19
+ */
20
+ declare function createMockRouter(): {
21
+ router: IAppRouter;
22
+ handlers: Record<string, Any>;
23
+ getHandler: (method: string, path: string) => Any;
24
+ };
25
+ /**
26
+ * On-behalf-of shorthand for {@link createMockRequest}. `true` uses the default
27
+ * test user; an object picks the identity. Sets the forwarded headers the real
28
+ * `Plugin.asUser` reads (`x-forwarded-access-token`, `x-forwarded-user`, and —
29
+ * when given — `x-forwarded-email`), so an OBO test is one flag instead of
30
+ * hand-rolled headers.
31
+ */
32
+ type OboOption = boolean | {
33
+ /** `x-forwarded-user` — defaults to `"test-user"`. */userId?: string; /** `x-forwarded-access-token` — defaults to `"test-user-token"`. */
34
+ token?: string; /** `x-forwarded-email` — omitted unless provided. */
35
+ email?: string;
36
+ };
37
+ /**
38
+ * Creates a mock Express request. Pass `overrides` to set `params`, `query`,
39
+ * `body`, `headers`, etc.
40
+ *
41
+ * For on-behalf-of tests, pass `obo` instead of hand-adding forwarded headers —
42
+ * `createMockRequest({ obo: true })` sets the identity headers the real
43
+ * `asUser` requires. Any explicit `headers` you also pass win over the ones
44
+ * `obo` generates, so you can override a single field.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * createMockRequest({ obo: true }); // default test user + token
49
+ * createMockRequest({ obo: { userId: "alice" } }); // pick the user
50
+ * ```
51
+ */
52
+ declare function createMockRequest(overrides?: Any): any;
53
+ /**
54
+ * Creates a mock Express response object. `write`/`send`/`setHeader` flip
55
+ * `headersSent`, `end` flips `writableEnded` and fires any `close` listener —
56
+ * enough for streaming handlers that branch on those flags.
57
+ *
58
+ * Every chunk passed to `write` (and a final chunk to `end`) is captured, so a
59
+ * streaming route's real SSE output can be replayed: pass the response straight
60
+ * to {@link expectStream}, or call `sseResponse()` for a real `Response`.
61
+ *
62
+ * @example Assert what a streaming route emitted
63
+ * ```ts
64
+ * const res = createMockResponse();
65
+ * await plugin._handleStream(req, res);
66
+ * await expectStream(res).toEmit("status", "result");
67
+ * ```
68
+ */
69
+ declare function createMockResponse(): {
70
+ headersSent: boolean;
71
+ status: vitest.Mock<(...args: any[]) => any>;
72
+ json: vitest.Mock<(...args: any[]) => any>;
73
+ send: vitest.Mock<(this: Any) => any>;
74
+ sendStatus: vitest.Mock<(...args: any[]) => any>;
75
+ end: vitest.Mock<(this: Any, chunk?: unknown) => any>;
76
+ write: vitest.Mock<(this: Any, chunk?: unknown) => any>;
77
+ setHeader: vitest.Mock<(this: Any) => any>;
78
+ flushHeaders: vitest.Mock<(...args: any[]) => any>;
79
+ destroy: vitest.Mock<(...args: any[]) => any>;
80
+ on: vitest.Mock<(this: Any, event: string, handler: (...args: Any[]) => void) => any>;
81
+ off: vitest.Mock<(this: Any, event: string, handler: (...args: Any[]) => void) => any>;
82
+ writableEnded: boolean;
83
+ /**
84
+ * The SSE body captured so far, as a real `Response` — the bridge from a
85
+ * `res.write`-based handler into {@link expectStream}. `expectStream`
86
+ * detects this method and calls it for you, so `expectStream(res)` and
87
+ * `expectStream(res.sseResponse())` are equivalent.
88
+ */
89
+ sseResponse(): Response;
90
+ };
91
+ /**
92
+ * Sets up common environment variables for Databricks testing so code that
93
+ * reads `DATABRICKS_HOST` / `DATABRICKS_WAREHOUSE_ID` finds test values.
94
+ */
95
+ declare function setupDatabricksEnv(overrides?: Record<string, string>): void;
96
+ /**
97
+ * Clears AppKit's process-wide cache singleton so cached values don't leak
98
+ * between tests in the same file.
99
+ *
100
+ * The cache `attach()` seeds is shared by every test in a file (Vitest isolates
101
+ * files, not tests within a file). Call this in `beforeEach` when one test's
102
+ * cached value must not be seen by the next, or mid-test to force a cache miss
103
+ * before asserting a subsequent hit.
104
+ *
105
+ * No-ops when the cache has not been initialized yet, so it is safe to call
106
+ * before any `attach()`.
107
+ *
108
+ * @example
109
+ * ```ts
110
+ * beforeEach(async () => {
111
+ * await resetTestCache();
112
+ * });
113
+ * ```
114
+ */
115
+ declare function resetTestCache(): Promise<void>;
116
+ /**
117
+ * Context options for running tests with mocked service/user context
118
+ */
119
+ interface TestContextOptions {
120
+ /** Mock WorkspaceClient for service principal operations */
121
+ serviceDatabricksClient?: Any;
122
+ /** Mock WorkspaceClient for user operations */
123
+ userDatabricksClient?: Any;
124
+ /** User ID for user context */
125
+ userId?: string;
126
+ /** Service user ID */
127
+ serviceUserId?: string;
128
+ /** Warehouse ID */
129
+ warehouseId?: string;
130
+ /** Workspace ID */
131
+ workspaceId?: string;
132
+ }
133
+ /**
134
+ * Creates a default mock WorkspaceClient for testing (SQL succeeds, warehouse
135
+ * RUNNING).
136
+ */
137
+ declare function createMockWorkspaceClient(): {
138
+ statementExecution: {
139
+ executeStatement: vitest.Mock<(...args: any[]) => any>;
140
+ };
141
+ warehouses: {
142
+ get: vitest.Mock<(...args: any[]) => any>;
143
+ start: vitest.Mock<(...args: any[]) => any>;
144
+ };
145
+ };
146
+ /**
147
+ * Mocks the `ServiceContext` singleton for testing — spies `get`,
148
+ * `initialize`, `isInitialized`, and `createUserContext` so code that resolves
149
+ * the service principal or an on-behalf-of user context gets test doubles.
150
+ * Call in `beforeEach`; call the returned `restore()` in `afterEach`.
151
+ *
152
+ * @returns The mock context plus the spies and a `restore()` helper.
153
+ */
154
+ declare function mockServiceContext(options?: TestContextOptions): {
155
+ serviceContext: ServiceContextState;
156
+ getSpy: vitest.MockInstance<typeof ServiceContext.get>;
157
+ initSpy: vitest.MockInstance<typeof ServiceContext.initialize>;
158
+ isInitializedSpy: vitest.MockInstance<typeof ServiceContext.isInitialized>;
159
+ createUserContextSpy: vitest.MockInstance<typeof ServiceContext.createUserContext>;
160
+ restore: () => void;
161
+ };
162
+ /** The handle {@link mockServiceContext} returns (spies + `restore`). */
163
+ type ServiceContextMock = ReturnType<typeof mockServiceContext>;
164
+ /**
165
+ * Registers a fresh {@link mockServiceContext} before each test and restores it
166
+ * after — the whole `beforeEach`/`afterEach` dance in one line.
167
+ *
168
+ * Call it at the top of a `describe` block (or module top-level), NOT inside a
169
+ * test: Vitest's `beforeEach`/`afterEach` only register during collection, so a
170
+ * call from within a test body registers nothing for that test.
171
+ *
172
+ * Returns a **live** accessor, not the handle: each `beforeEach` builds fresh
173
+ * spies, so reading `.current` inside a test always sees that test's mock. A
174
+ * handle captured once would go stale after the first hook runs.
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * describe("my plugin", () => {
179
+ * const ctx = useServiceContextMock({ warehouseId: "wh-1" });
180
+ *
181
+ * test("resolves the warehouse", async () => {
182
+ * await myHandler(req, res);
183
+ * expect(ctx.current.getSpy).toHaveBeenCalled();
184
+ * });
185
+ * });
186
+ * ```
187
+ *
188
+ * @returns `{ current }` — the active {@link ServiceContextMock} for the test.
189
+ */
190
+ declare function useServiceContextMock(options?: TestContextOptions): {
191
+ readonly current: ServiceContextMock;
192
+ };
193
+ /**
194
+ * Runs a test function within a mocked service context: installs the mock,
195
+ * runs `fn`, and restores the singleton afterward.
196
+ */
197
+ declare function runWithRequestContext<T>(fn: () => T | Promise<T>, context?: TestContextOptions): Promise<T>;
198
+ /**
199
+ * Builds a SUCCEEDED SQL statement response with a synthetic statement id,
200
+ * `data_array` rows, and a manifest schema derived from `columns`.
201
+ */
202
+ declare function createSuccessfulSQLResponse(data: Any[][], columns: Array<{
203
+ name: string;
204
+ type_name?: string;
205
+ }>): {
206
+ status: {
207
+ state: string;
208
+ };
209
+ statement_id: string;
210
+ result: {
211
+ data_array: any[][];
212
+ };
213
+ manifest: {
214
+ schema: {
215
+ columns: {
216
+ name: string;
217
+ type_name: string;
218
+ }[];
219
+ };
220
+ };
221
+ };
222
+ /** Builds a FAILED SQL statement response carrying `errorMessage`. */
223
+ declare function createFailedSQLResponse(errorMessage: string): {
224
+ status: {
225
+ state: string;
226
+ error: {
227
+ message: string;
228
+ };
229
+ };
230
+ statement_id: string;
231
+ };
232
+ /**
233
+ * A WorkspaceClient whose `executeStatement`/`getStatement` are bare `vi.fn()`s
234
+ * (no default resolution) so a test can script exactly what SQL returns.
235
+ * `warehouses.get` defaults to RUNNING.
236
+ */
237
+ declare function createConfigurableMockWorkspaceClient(): {
238
+ client: {
239
+ statementExecution: {
240
+ executeStatement: vitest.Mock<(...args: any[]) => any>;
241
+ getStatement: vitest.Mock<(...args: any[]) => any>;
242
+ };
243
+ warehouses: {
244
+ get: vitest.Mock<(...args: any[]) => any>;
245
+ start: vitest.Mock<(...args: any[]) => any>;
246
+ };
247
+ };
248
+ mocks: {
249
+ executeStatement: vitest.Mock<(...args: any[]) => any>;
250
+ getStatement: vitest.Mock<(...args: any[]) => any>;
251
+ warehousesGet: vitest.Mock<(...args: any[]) => any>;
252
+ warehousesStart: vitest.Mock<(...args: any[]) => any>;
253
+ };
254
+ };
255
+ //#endregion
256
+ export { OboOption, ServiceContextMock, TestContextOptions, createConfigurableMockWorkspaceClient, createFailedSQLResponse, createMockRequest, createMockResponse, createMockRouter, createMockTelemetry, createMockWorkspaceClient, createSuccessfulSQLResponse, mockServiceContext, resetTestCache, runWithRequestContext, setupDatabricksEnv, useServiceContextMock };
257
+ //# sourceMappingURL=fixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.d.ts","names":[],"sources":["../../src/testing/fixtures.ts"],"mappings":";;;;;;;KAWK,GAAA;;;AAJkE;;;;iBAYvD,mBAAA,CAAA,GAAuB,UAAA;AAAvC;;;;AAAA,iBAqDgB,gBAAA,CAAA;EACd,MAAA,EAAQ,UAAA;EACR,QAAA,EAAU,MAAA,SAAe,GAAA;EACzB,UAAA,GAAa,MAAA,UAAgB,IAAA,aAAiB,GAAA;AAAA;;;;;;;;KAqCpC,SAAA;EAtCA,sDA0CN,MAAA,WAzCJ;EA2CI,KAAA,WA3CyB;EA6CzB,KAAA;AAAA;;AARN;;;;;;;;;AAqCA;;;;;iBAAgB,iBAAA,CAAkB,SAAA,GAAW,GAAA;;;;;;;;;;;;;;;;;iBA+C7B,kBAAA,CAAA;;UAAkB,MAAA,CAAA,IAAA;;QAWC,MAAA,CAAA,IAAA,QAAH,GAAA;;OAKE,MAAA,CAAA,IAAA,QAAH,GAAA,EAAG,KAAA;SAcE,MAAA,CAAA,IAAA,QAAH,GAAA,EAAG,KAAA;aAOI,MAAA,CAAA,IAAA,QAAH,GAAA;;;MAST,MAAA,CAAA,IAAA,QAFhB,GAAA,EAAG,KAAA,UACI,OAAA,MACA,IAAA,EAAM,GAAA;OAWG,MAAA,CAAA,IAAA,QAFhB,GAAA,EAAG,KAAA,UACI,OAAA,MACA,IAAA,EAAM,GAAA;;;;;;;;iBAgBN,QAAA;AAAA;;;;;iBAWH,kBAAA,CAAmB,SAAA,GAAW,MAAA;;;;;;;;;;;;;;;;;;;;iBAyBxB,cAAA,CAAA,GAAkB,OAAA;;;;UAcvB,kBAAA;;EAEf,uBAAA,GAA0B,GAAA;;EAE1B,oBAAA,GAAuB,GAAA;EAtEN;EAwEjB,MAAA;EAzEiB;EA2EjB,aAAA;;EAEA,WAAA;EA5DyB;EA8DzB,WAAA;AAAA;;;;;iBAOc,yBAAA,CAAA;;sBAAyB,MAAA,CAAA,IAAA;;;;;;;;;;;;;;;iBA2CzB,kBAAA,CAAmB,OAAA,GAAS,kBAAA;;;;;;;;;KA6ChC,kBAAA,GAAqB,UAAA,QAAkB,kBAAA;;;;;;;;;;;;;;AA7CnD;;;;;;;;;;;;;iBAyEgB,qBAAA,CAAsB,OAAA,GAAS,kBAAA;EAAA,SACpC,OAAA,EAAS,kBAAA;AAAA;;;;;iBA+BE,qBAAA,GAAA,CACpB,EAAA,QAAU,CAAA,GAAI,OAAA,CAAQ,CAAA,GACtB,OAAA,GAAU,kBAAA,GACT,OAAA,CAAQ,CAAA;;;;;iBAcK,2BAAA,CACd,IAAA,EAAM,GAAA,MACN,OAAA,EAAS,KAAA;EAAQ,IAAA;EAAc,SAAA;AAAA;;;;;;;;;;;;;;;;;;iBAoBjB,uBAAA,CAAwB,YAAA;;;;;;;;;;;;;;iBAiBxB,qCAAA,CAAA;;;wBAAqC,MAAA,CAAA,IAAA"}
@@ -0,0 +1,385 @@
1
+ import { ServiceContext } from "../context/service-context.js";
2
+ import { CacheManager } from "../cache/index.js";
3
+ import { afterEach, beforeEach, vi } from "vitest";
4
+
5
+ //#region src/testing/fixtures.ts
6
+ /**
7
+ * Creates a mock telemetry provider for testing. Every span/meter/logger is a
8
+ * `vi.fn()` no-op, so plugins that trace, count, or log run without a live
9
+ * OpenTelemetry pipeline. Passed into {@link createTestPluginContext} as the one
10
+ * injectable production seam.
11
+ */
12
+ function createMockTelemetry() {
13
+ const mockSpan = {
14
+ addLink: vi.fn(),
15
+ addLinks: vi.fn(),
16
+ end: vi.fn(),
17
+ setAttribute: vi.fn(),
18
+ setAttributes: vi.fn(),
19
+ setStatus: vi.fn(),
20
+ recordException: vi.fn(),
21
+ updateName: vi.fn(),
22
+ addEvent: vi.fn(),
23
+ isRecording: vi.fn().mockReturnValue(false),
24
+ spanContext: vi.fn()
25
+ };
26
+ return {
27
+ getTracer: vi.fn().mockReturnValue({ startActiveSpan: vi.fn().mockImplementation((...args) => {
28
+ const fn = args[args.length - 1];
29
+ if (typeof fn === "function") return fn(mockSpan);
30
+ }) }),
31
+ getMeter: vi.fn().mockReturnValue({
32
+ createCounter: vi.fn().mockReturnValue({ add: vi.fn() }),
33
+ createHistogram: vi.fn().mockReturnValue({ record: vi.fn() })
34
+ }),
35
+ getLogger: vi.fn().mockReturnValue({ emit: vi.fn() }),
36
+ emit: vi.fn(),
37
+ startActiveSpan: vi.fn().mockImplementation(async (_name, _options, fn, _tracerOptions) => {
38
+ return await fn(mockSpan);
39
+ }),
40
+ registerInstrumentations: vi.fn()
41
+ };
42
+ }
43
+ /**
44
+ * Creates a mock Express router that captures registered handlers so a test
45
+ * can pull a handler back out by method + path and invoke it directly.
46
+ */
47
+ function createMockRouter() {
48
+ const handlers = {};
49
+ return {
50
+ router: {
51
+ get: vi.fn((path, handler) => {
52
+ handlers[`GET:${path}`] = handler;
53
+ }),
54
+ post: vi.fn((path, handler) => {
55
+ handlers[`POST:${path}`] = handler;
56
+ }),
57
+ put: vi.fn((path, handler) => {
58
+ handlers[`PUT:${path}`] = handler;
59
+ }),
60
+ delete: vi.fn((path, handler) => {
61
+ handlers[`DELETE:${path}`] = handler;
62
+ }),
63
+ patch: vi.fn((path, handler) => {
64
+ handlers[`PATCH:${path}`] = handler;
65
+ })
66
+ },
67
+ handlers,
68
+ getHandler: (method, path) => handlers[`${method.toUpperCase()}:${path}`]
69
+ };
70
+ }
71
+ /** Build the forwarded identity headers an `obo` option implies. */
72
+ function oboHeaders(obo) {
73
+ const opts = obo === true ? {} : obo;
74
+ const headers = {
75
+ "x-forwarded-access-token": opts.token ?? "test-user-token",
76
+ "x-forwarded-user": opts.userId ?? "test-user"
77
+ };
78
+ if (opts.email) headers["x-forwarded-email"] = opts.email;
79
+ return headers;
80
+ }
81
+ /**
82
+ * Creates a mock Express request. Pass `overrides` to set `params`, `query`,
83
+ * `body`, `headers`, etc.
84
+ *
85
+ * For on-behalf-of tests, pass `obo` instead of hand-adding forwarded headers —
86
+ * `createMockRequest({ obo: true })` sets the identity headers the real
87
+ * `asUser` requires. Any explicit `headers` you also pass win over the ones
88
+ * `obo` generates, so you can override a single field.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * createMockRequest({ obo: true }); // default test user + token
93
+ * createMockRequest({ obo: { userId: "alice" } }); // pick the user
94
+ * ```
95
+ */
96
+ function createMockRequest(overrides = {}) {
97
+ const { obo, headers: headerOverrides, ...rest } = overrides;
98
+ const headers = Object.fromEntries(Object.entries({
99
+ ...obo ? oboHeaders(obo) : {},
100
+ ...headerOverrides
101
+ }).map(([name, value]) => [name.toLowerCase(), value]));
102
+ return {
103
+ params: {},
104
+ query: {},
105
+ body: {},
106
+ header: function(name) {
107
+ return this.headers[name.toLowerCase()];
108
+ },
109
+ ...rest,
110
+ headers
111
+ };
112
+ }
113
+ /**
114
+ * Creates a mock Express response object. `write`/`send`/`setHeader` flip
115
+ * `headersSent`, `end` flips `writableEnded` and fires any `close` listener —
116
+ * enough for streaming handlers that branch on those flags.
117
+ *
118
+ * Every chunk passed to `write` (and a final chunk to `end`) is captured, so a
119
+ * streaming route's real SSE output can be replayed: pass the response straight
120
+ * to {@link expectStream}, or call `sseResponse()` for a real `Response`.
121
+ *
122
+ * @example Assert what a streaming route emitted
123
+ * ```ts
124
+ * const res = createMockResponse();
125
+ * await plugin._handleStream(req, res);
126
+ * await expectStream(res).toEmit("status", "result");
127
+ * ```
128
+ */
129
+ function createMockResponse() {
130
+ const eventListeners = {};
131
+ const chunks = [];
132
+ return {
133
+ headersSent: false,
134
+ status: vi.fn().mockReturnThis(),
135
+ json: vi.fn().mockReturnThis(),
136
+ send: vi.fn(function() {
137
+ this.headersSent = true;
138
+ return this;
139
+ }),
140
+ sendStatus: vi.fn().mockReturnThis(),
141
+ end: vi.fn(function(chunk) {
142
+ if (chunk != null && typeof chunk !== "function") chunks.push(String(chunk));
143
+ this.writableEnded = true;
144
+ if (eventListeners.close) for (const handler of eventListeners.close) handler();
145
+ return this;
146
+ }),
147
+ write: vi.fn(function(chunk) {
148
+ this.headersSent = true;
149
+ if (chunk != null) chunks.push(String(chunk));
150
+ return this;
151
+ }),
152
+ setHeader: vi.fn(function() {
153
+ this.headersSent = true;
154
+ return this;
155
+ }),
156
+ flushHeaders: vi.fn().mockReturnThis(),
157
+ destroy: vi.fn().mockReturnThis(),
158
+ on: vi.fn(function(event, handler) {
159
+ if (!eventListeners[event]) eventListeners[event] = [];
160
+ eventListeners[event].push(handler);
161
+ return this;
162
+ }),
163
+ off: vi.fn(function(event, handler) {
164
+ if (eventListeners[event]) eventListeners[event] = eventListeners[event].filter((h) => h !== handler);
165
+ return this;
166
+ }),
167
+ writableEnded: false,
168
+ sseResponse() {
169
+ return new Response(chunks.join(""));
170
+ }
171
+ };
172
+ }
173
+ /**
174
+ * Sets up common environment variables for Databricks testing so code that
175
+ * reads `DATABRICKS_HOST` / `DATABRICKS_WAREHOUSE_ID` finds test values.
176
+ */
177
+ function setupDatabricksEnv(overrides = {}) {
178
+ process.env.DATABRICKS_HOST = "https://test.databricks.com";
179
+ process.env.DATABRICKS_WAREHOUSE_ID = "test-warehouse-id";
180
+ Object.assign(process.env, overrides);
181
+ }
182
+ /**
183
+ * Clears AppKit's process-wide cache singleton so cached values don't leak
184
+ * between tests in the same file.
185
+ *
186
+ * The cache `attach()` seeds is shared by every test in a file (Vitest isolates
187
+ * files, not tests within a file). Call this in `beforeEach` when one test's
188
+ * cached value must not be seen by the next, or mid-test to force a cache miss
189
+ * before asserting a subsequent hit.
190
+ *
191
+ * No-ops when the cache has not been initialized yet, so it is safe to call
192
+ * before any `attach()`.
193
+ *
194
+ * @example
195
+ * ```ts
196
+ * beforeEach(async () => {
197
+ * await resetTestCache();
198
+ * });
199
+ * ```
200
+ */
201
+ async function resetTestCache() {
202
+ let cache;
203
+ try {
204
+ cache = CacheManager.getInstanceSync();
205
+ } catch {
206
+ return;
207
+ }
208
+ await cache.clear();
209
+ }
210
+ /**
211
+ * Creates a default mock WorkspaceClient for testing (SQL succeeds, warehouse
212
+ * RUNNING).
213
+ */
214
+ function createMockWorkspaceClient() {
215
+ return {
216
+ statementExecution: { executeStatement: vi.fn().mockResolvedValue({
217
+ status: { state: "SUCCEEDED" },
218
+ result: { data: [] }
219
+ }) },
220
+ warehouses: {
221
+ get: vi.fn().mockResolvedValue({ state: "RUNNING" }),
222
+ start: vi.fn().mockResolvedValue(void 0)
223
+ }
224
+ };
225
+ }
226
+ /**
227
+ * Builds a {@link ServiceContextState} value for testing without touching the
228
+ * singleton. Internal building block for {@link mockServiceContext}, which
229
+ * installs the state as spies — that installer is the public entry point.
230
+ */
231
+ function buildServiceContextState(options = {}) {
232
+ return {
233
+ client: options.serviceDatabricksClient || createMockWorkspaceClient(),
234
+ serviceUserId: options.serviceUserId || "test-service-user",
235
+ warehouseId: Promise.resolve(options.warehouseId || "test-warehouse-id"),
236
+ workspaceId: Promise.resolve(options.workspaceId || "test-workspace-id")
237
+ };
238
+ }
239
+ /**
240
+ * Mocks the `ServiceContext` singleton for testing — spies `get`,
241
+ * `initialize`, `isInitialized`, and `createUserContext` so code that resolves
242
+ * the service principal or an on-behalf-of user context gets test doubles.
243
+ * Call in `beforeEach`; call the returned `restore()` in `afterEach`.
244
+ *
245
+ * @returns The mock context plus the spies and a `restore()` helper.
246
+ */
247
+ function mockServiceContext(options = {}) {
248
+ const serviceContext = buildServiceContextState(options);
249
+ const getSpy = vi.spyOn(ServiceContext, "get").mockReturnValue(serviceContext);
250
+ const initSpy = vi.spyOn(ServiceContext, "initialize").mockResolvedValue(serviceContext);
251
+ const isInitializedSpy = vi.spyOn(ServiceContext, "isInitialized").mockReturnValue(true);
252
+ const createUserContextSpy = vi.spyOn(ServiceContext, "createUserContext").mockImplementation((_token, userId, userName) => {
253
+ return {
254
+ client: options.userDatabricksClient || createMockWorkspaceClient(),
255
+ userId,
256
+ userName,
257
+ warehouseId: serviceContext.warehouseId,
258
+ workspaceId: serviceContext.workspaceId,
259
+ isUserContext: true
260
+ };
261
+ });
262
+ return {
263
+ serviceContext,
264
+ getSpy,
265
+ initSpy,
266
+ isInitializedSpy,
267
+ createUserContextSpy,
268
+ restore: () => {
269
+ getSpy.mockRestore();
270
+ initSpy.mockRestore();
271
+ isInitializedSpy.mockRestore();
272
+ createUserContextSpy.mockRestore();
273
+ }
274
+ };
275
+ }
276
+ /**
277
+ * Registers a fresh {@link mockServiceContext} before each test and restores it
278
+ * after — the whole `beforeEach`/`afterEach` dance in one line.
279
+ *
280
+ * Call it at the top of a `describe` block (or module top-level), NOT inside a
281
+ * test: Vitest's `beforeEach`/`afterEach` only register during collection, so a
282
+ * call from within a test body registers nothing for that test.
283
+ *
284
+ * Returns a **live** accessor, not the handle: each `beforeEach` builds fresh
285
+ * spies, so reading `.current` inside a test always sees that test's mock. A
286
+ * handle captured once would go stale after the first hook runs.
287
+ *
288
+ * @example
289
+ * ```ts
290
+ * describe("my plugin", () => {
291
+ * const ctx = useServiceContextMock({ warehouseId: "wh-1" });
292
+ *
293
+ * test("resolves the warehouse", async () => {
294
+ * await myHandler(req, res);
295
+ * expect(ctx.current.getSpy).toHaveBeenCalled();
296
+ * });
297
+ * });
298
+ * ```
299
+ *
300
+ * @returns `{ current }` — the active {@link ServiceContextMock} for the test.
301
+ */
302
+ function useServiceContextMock(options = {}) {
303
+ let handle;
304
+ beforeEach(() => {
305
+ handle = mockServiceContext(options);
306
+ });
307
+ afterEach(() => {
308
+ handle?.restore();
309
+ handle = void 0;
310
+ });
311
+ return { get current() {
312
+ if (!handle) throw new Error("useServiceContextMock: no active mock. Call useServiceContextMock() at the top of a describe block (not inside a test), and read `.current` from within a test.");
313
+ return handle;
314
+ } };
315
+ }
316
+ /**
317
+ * Runs a test function within a mocked service context: installs the mock,
318
+ * runs `fn`, and restores the singleton afterward.
319
+ */
320
+ async function runWithRequestContext(fn, context) {
321
+ const mocks = mockServiceContext(context);
322
+ try {
323
+ return await fn();
324
+ } finally {
325
+ mocks.restore();
326
+ }
327
+ }
328
+ /**
329
+ * Builds a SUCCEEDED SQL statement response with a synthetic statement id,
330
+ * `data_array` rows, and a manifest schema derived from `columns`.
331
+ */
332
+ function createSuccessfulSQLResponse(data, columns) {
333
+ return {
334
+ status: { state: "SUCCEEDED" },
335
+ statement_id: `stmt-${Date.now()}`,
336
+ result: { data_array: data },
337
+ manifest: { schema: { columns: columns.map((col) => ({
338
+ name: col.name,
339
+ type_name: col.type_name ?? "STRING"
340
+ })) } }
341
+ };
342
+ }
343
+ /** Builds a FAILED SQL statement response carrying `errorMessage`. */
344
+ function createFailedSQLResponse(errorMessage) {
345
+ return {
346
+ status: {
347
+ state: "FAILED",
348
+ error: { message: errorMessage }
349
+ },
350
+ statement_id: `stmt-${Date.now()}`
351
+ };
352
+ }
353
+ /**
354
+ * A WorkspaceClient whose `executeStatement`/`getStatement` are bare `vi.fn()`s
355
+ * (no default resolution) so a test can script exactly what SQL returns.
356
+ * `warehouses.get` defaults to RUNNING.
357
+ */
358
+ function createConfigurableMockWorkspaceClient() {
359
+ const executeStatement = vi.fn();
360
+ const getStatement = vi.fn();
361
+ const warehousesGet = vi.fn().mockResolvedValue({ state: "RUNNING" });
362
+ const warehousesStart = vi.fn().mockResolvedValue(void 0);
363
+ return {
364
+ client: {
365
+ statementExecution: {
366
+ executeStatement,
367
+ getStatement
368
+ },
369
+ warehouses: {
370
+ get: warehousesGet,
371
+ start: warehousesStart
372
+ }
373
+ },
374
+ mocks: {
375
+ executeStatement,
376
+ getStatement,
377
+ warehousesGet,
378
+ warehousesStart
379
+ }
380
+ };
381
+ }
382
+
383
+ //#endregion
384
+ export { createConfigurableMockWorkspaceClient, createFailedSQLResponse, createMockRequest, createMockResponse, createMockRouter, createMockTelemetry, createMockWorkspaceClient, createSuccessfulSQLResponse, mockServiceContext, resetTestCache, runWithRequestContext, setupDatabricksEnv, useServiceContextMock };
385
+ //# sourceMappingURL=fixtures.js.map