@vellumai/assistant 0.4.30 → 0.4.32

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 (194) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/Dockerfile +14 -8
  3. package/README.md +2 -2
  4. package/docs/architecture/memory.md +28 -29
  5. package/docs/runbook-trusted-contacts.md +1 -4
  6. package/package.json +1 -1
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -7
  8. package/src/__tests__/anthropic-provider.test.ts +86 -1
  9. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
  10. package/src/__tests__/checker.test.ts +37 -98
  11. package/src/__tests__/commit-message-enrichment-service.test.ts +15 -4
  12. package/src/__tests__/config-schema.test.ts +6 -14
  13. package/src/__tests__/conflict-policy.test.ts +76 -0
  14. package/src/__tests__/conflict-store.test.ts +14 -20
  15. package/src/__tests__/contacts-tools.test.ts +8 -61
  16. package/src/__tests__/contradiction-checker.test.ts +5 -1
  17. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  18. package/src/__tests__/daemon-server-session-init.test.ts +1 -19
  19. package/src/__tests__/followup-tools.test.ts +0 -30
  20. package/src/__tests__/gemini-provider.test.ts +79 -1
  21. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  22. package/src/__tests__/guardian-routing-invariants.test.ts +6 -4
  23. package/src/__tests__/ipc-snapshot.test.ts +0 -4
  24. package/src/__tests__/managed-proxy-context.test.ts +163 -0
  25. package/src/__tests__/memory-lifecycle-e2e.test.ts +13 -12
  26. package/src/__tests__/memory-regressions.test.ts +6 -6
  27. package/src/__tests__/openai-provider.test.ts +82 -0
  28. package/src/__tests__/provider-fail-open-selection.test.ts +134 -1
  29. package/src/__tests__/provider-managed-proxy-integration.test.ts +269 -0
  30. package/src/__tests__/recurrence-types.test.ts +0 -15
  31. package/src/__tests__/registry.test.ts +0 -10
  32. package/src/__tests__/schedule-tools.test.ts +28 -44
  33. package/src/__tests__/script-proxy-session-runtime.test.ts +6 -1
  34. package/src/__tests__/session-agent-loop.test.ts +0 -2
  35. package/src/__tests__/session-conflict-gate.test.ts +243 -388
  36. package/src/__tests__/session-profile-injection.test.ts +0 -2
  37. package/src/__tests__/session-runtime-assembly.test.ts +2 -3
  38. package/src/__tests__/session-skill-tools.test.ts +0 -49
  39. package/src/__tests__/session-workspace-cache-state.test.ts +0 -1
  40. package/src/__tests__/session-workspace-injection.test.ts +0 -1
  41. package/src/__tests__/session-workspace-tool-tracking.test.ts +0 -1
  42. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  43. package/src/__tests__/task-management-tools.test.ts +111 -0
  44. package/src/__tests__/tool-grant-request-escalation.test.ts +2 -1
  45. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -1
  46. package/src/__tests__/twilio-config.test.ts +0 -3
  47. package/src/amazon/session.ts +30 -91
  48. package/src/approvals/guardian-decision-primitive.ts +11 -7
  49. package/src/approvals/guardian-request-resolvers.ts +5 -3
  50. package/src/calls/call-controller.ts +423 -571
  51. package/src/calls/finalize-call.ts +20 -0
  52. package/src/calls/relay-access-wait.ts +340 -0
  53. package/src/calls/relay-server.ts +269 -899
  54. package/src/calls/relay-setup-router.ts +307 -0
  55. package/src/calls/relay-verification.ts +280 -0
  56. package/src/calls/twilio-config.ts +1 -8
  57. package/src/calls/voice-control-protocol.ts +184 -0
  58. package/src/calls/voice-session-bridge.ts +1 -8
  59. package/src/config/agent-schema.ts +1 -1
  60. package/src/config/bundled-skills/contacts/SKILL.md +7 -18
  61. package/src/config/bundled-skills/contacts/TOOLS.json +4 -20
  62. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +2 -4
  63. package/src/config/bundled-skills/contacts/tools/contact-search.ts +6 -12
  64. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +3 -24
  65. package/src/config/bundled-skills/followups/TOOLS.json +0 -4
  66. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  67. package/src/config/bundled-skills/schedule/TOOLS.json +2 -10
  68. package/src/config/bundled-tool-registry.ts +0 -5
  69. package/src/config/core-schema.ts +1 -1
  70. package/src/config/env.ts +0 -10
  71. package/src/config/feature-flag-registry.json +1 -1
  72. package/src/config/loader.ts +19 -0
  73. package/src/config/memory-schema.ts +0 -10
  74. package/src/config/schema.ts +2 -2
  75. package/src/config/system-prompt.ts +6 -0
  76. package/src/contacts/contact-store.ts +36 -62
  77. package/src/contacts/contacts-write.ts +14 -3
  78. package/src/contacts/types.ts +9 -4
  79. package/src/daemon/handlers/config-heartbeat.ts +1 -2
  80. package/src/daemon/handlers/contacts.ts +2 -2
  81. package/src/daemon/handlers/guardian-actions.ts +1 -1
  82. package/src/daemon/handlers/session-history.ts +398 -0
  83. package/src/daemon/handlers/session-user-message.ts +982 -0
  84. package/src/daemon/handlers/sessions.ts +9 -1337
  85. package/src/daemon/ipc-contract/contacts.ts +2 -2
  86. package/src/daemon/ipc-contract/sessions.ts +0 -6
  87. package/src/daemon/ipc-contract-inventory.json +0 -1
  88. package/src/daemon/lifecycle.ts +0 -29
  89. package/src/daemon/session-agent-loop.ts +1 -45
  90. package/src/daemon/session-conflict-gate.ts +21 -82
  91. package/src/daemon/session-memory.ts +7 -52
  92. package/src/daemon/session-process.ts +3 -1
  93. package/src/daemon/session-runtime-assembly.ts +18 -35
  94. package/src/heartbeat/heartbeat-service.ts +5 -1
  95. package/src/home-base/app-link-store.ts +0 -7
  96. package/src/memory/conflict-intent.ts +3 -6
  97. package/src/memory/conflict-policy.ts +34 -0
  98. package/src/memory/conflict-store.ts +10 -18
  99. package/src/memory/contradiction-checker.ts +2 -2
  100. package/src/memory/conversation-attention-store.ts +1 -1
  101. package/src/memory/conversation-store.ts +0 -51
  102. package/src/memory/db-init.ts +8 -0
  103. package/src/memory/job-handlers/conflict.ts +24 -7
  104. package/src/memory/migrations/105-contacts-and-triage.ts +4 -7
  105. package/src/memory/migrations/134-contacts-notes-column.ts +68 -0
  106. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +31 -0
  107. package/src/memory/migrations/index.ts +2 -0
  108. package/src/memory/migrations/registry.ts +6 -0
  109. package/src/memory/recall-cache.ts +0 -5
  110. package/src/memory/schema/calls.ts +274 -0
  111. package/src/memory/schema/contacts.ts +125 -0
  112. package/src/memory/schema/conversations.ts +129 -0
  113. package/src/memory/schema/guardian.ts +172 -0
  114. package/src/memory/schema/index.ts +8 -0
  115. package/src/memory/schema/infrastructure.ts +205 -0
  116. package/src/memory/schema/memory-core.ts +196 -0
  117. package/src/memory/schema/notifications.ts +191 -0
  118. package/src/memory/schema/tasks.ts +78 -0
  119. package/src/memory/schema.ts +1 -1402
  120. package/src/memory/slack-thread-store.ts +0 -69
  121. package/src/messaging/index.ts +0 -1
  122. package/src/messaging/types.ts +0 -38
  123. package/src/notifications/decisions-store.ts +2 -105
  124. package/src/notifications/deliveries-store.ts +0 -11
  125. package/src/notifications/preferences-store.ts +1 -58
  126. package/src/permissions/checker.ts +6 -17
  127. package/src/providers/anthropic/client.ts +6 -2
  128. package/src/providers/gemini/client.ts +13 -2
  129. package/src/providers/managed-proxy/constants.ts +55 -0
  130. package/src/providers/managed-proxy/context.ts +77 -0
  131. package/src/providers/registry.ts +112 -0
  132. package/src/runtime/auth/__tests__/guard-tests.test.ts +51 -23
  133. package/src/runtime/guardian-action-service.ts +3 -2
  134. package/src/runtime/guardian-outbound-actions.ts +3 -3
  135. package/src/runtime/guardian-reply-router.ts +4 -4
  136. package/src/runtime/http-server.ts +83 -710
  137. package/src/runtime/http-types.ts +0 -16
  138. package/src/runtime/middleware/auth.ts +0 -12
  139. package/src/runtime/routes/app-routes.ts +33 -0
  140. package/src/runtime/routes/approval-routes.ts +32 -0
  141. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +6 -3
  142. package/src/runtime/routes/attachment-routes.ts +32 -0
  143. package/src/runtime/routes/brain-graph-routes.ts +27 -0
  144. package/src/runtime/routes/call-routes.ts +41 -0
  145. package/src/runtime/routes/channel-readiness-routes.ts +20 -0
  146. package/src/runtime/routes/channel-routes.ts +70 -0
  147. package/src/runtime/routes/contact-routes.ts +371 -29
  148. package/src/runtime/routes/conversation-attention-routes.ts +15 -0
  149. package/src/runtime/routes/conversation-routes.ts +192 -194
  150. package/src/runtime/routes/debug-routes.ts +15 -0
  151. package/src/runtime/routes/events-routes.ts +16 -0
  152. package/src/runtime/routes/global-search-routes.ts +17 -2
  153. package/src/runtime/routes/guardian-action-routes.ts +23 -1
  154. package/src/runtime/routes/guardian-approval-interception.ts +2 -1
  155. package/src/runtime/routes/guardian-bootstrap-routes.ts +26 -1
  156. package/src/runtime/routes/guardian-refresh-routes.ts +20 -0
  157. package/src/runtime/routes/identity-routes.ts +20 -0
  158. package/src/runtime/routes/inbound-message-handler.ts +8 -0
  159. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +5 -1
  160. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +2 -1
  161. package/src/runtime/routes/integration-routes.ts +83 -0
  162. package/src/runtime/routes/invite-routes.ts +31 -0
  163. package/src/runtime/routes/migration-routes.ts +47 -17
  164. package/src/runtime/routes/pairing-routes.ts +18 -0
  165. package/src/runtime/routes/secret-routes.ts +20 -0
  166. package/src/runtime/routes/surface-action-routes.ts +26 -0
  167. package/src/runtime/routes/trust-rules-routes.ts +31 -0
  168. package/src/runtime/routes/twilio-routes.ts +79 -0
  169. package/src/schedule/recurrence-types.ts +1 -11
  170. package/src/tools/followups/followup_create.ts +9 -3
  171. package/src/tools/mcp/mcp-tool-factory.ts +0 -17
  172. package/src/tools/memory/definitions.ts +0 -6
  173. package/src/tools/network/script-proxy/session-manager.ts +38 -3
  174. package/src/tools/schedule/create.ts +1 -3
  175. package/src/tools/schedule/update.ts +9 -6
  176. package/src/twitter/session.ts +29 -77
  177. package/src/util/cookie-session.ts +114 -0
  178. package/src/workspace/git-service.ts +6 -4
  179. package/src/__tests__/conversation-routes.test.ts +0 -99
  180. package/src/__tests__/get-weather.test.ts +0 -393
  181. package/src/__tests__/task-tools.test.ts +0 -685
  182. package/src/__tests__/weather-skill-regression.test.ts +0 -276
  183. package/src/autonomy/autonomy-resolver.ts +0 -62
  184. package/src/autonomy/autonomy-store.ts +0 -138
  185. package/src/autonomy/disposition-mapper.ts +0 -31
  186. package/src/autonomy/index.ts +0 -11
  187. package/src/autonomy/types.ts +0 -43
  188. package/src/config/bundled-skills/weather/SKILL.md +0 -38
  189. package/src/config/bundled-skills/weather/TOOLS.json +0 -36
  190. package/src/config/bundled-skills/weather/icon.svg +0 -24
  191. package/src/config/bundled-skills/weather/tools/get-weather.ts +0 -12
  192. package/src/contacts/startup-migration.ts +0 -21
  193. package/src/messaging/triage-engine.ts +0 -344
  194. package/src/tools/weather/service.ts +0 -712
@@ -1,393 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import {
4
- executeGetWeather,
5
- weatherCodeToDescription,
6
- } from "../tools/weather/service.js";
7
-
8
- // ---------------------------------------------------------------------------
9
- // Helper: build a mock fetch that returns predefined geocoding & weather data
10
- // ---------------------------------------------------------------------------
11
-
12
- function createMockFetch(options?: {
13
- geoResults?: unknown[];
14
- geoStatus?: number;
15
- geoError?: Error;
16
- forecastData?: unknown;
17
- forecastStatus?: number;
18
- forecastError?: Error;
19
- }): typeof globalThis.fetch {
20
- const {
21
- geoResults,
22
- geoStatus = 200,
23
- geoError,
24
- forecastData,
25
- forecastStatus = 200,
26
- forecastError,
27
- } = options ?? {};
28
-
29
- const defaultGeoResults = [
30
- {
31
- name: "San Francisco",
32
- latitude: 37.7749,
33
- longitude: -122.4194,
34
- country: "United States",
35
- admin1: "California",
36
- },
37
- ];
38
-
39
- // Generate 48 hourly entries starting from 2025-01-15T00:00
40
- const hourlyTimes: string[] = [];
41
- const hourlyTemps: number[] = [];
42
- const hourlyCodes: number[] = [];
43
- const hourlyIsDay: number[] = [];
44
- for (let i = 0; i < 48; i++) {
45
- const h = i % 24;
46
- hourlyTimes.push(
47
- `2025-01-${15 + Math.floor(i / 24)}T${String(h).padStart(2, "0")}:00`,
48
- );
49
- hourlyTemps.push(10 + Math.sin(i / 4) * 5);
50
- hourlyCodes.push(i % 3 === 0 ? 0 : 2);
51
- hourlyIsDay.push(h >= 7 && h < 19 ? 1 : 0);
52
- }
53
-
54
- const defaultForecastData = {
55
- current: {
56
- time: "2025-01-15T08:00",
57
- temperature_2m: 15.0,
58
- relative_humidity_2m: 72,
59
- apparent_temperature: 13.5,
60
- weather_code: 2,
61
- wind_speed_10m: 18.0,
62
- wind_direction_10m: 270,
63
- },
64
- current_units: {
65
- temperature_2m: "\u00B0C",
66
- relative_humidity_2m: "%",
67
- apparent_temperature: "\u00B0C",
68
- wind_speed_10m: "km/h",
69
- wind_direction_10m: "\u00B0",
70
- },
71
- hourly: {
72
- time: hourlyTimes,
73
- temperature_2m: hourlyTemps,
74
- weather_code: hourlyCodes,
75
- is_day: hourlyIsDay,
76
- },
77
- hourly_units: {
78
- temperature_2m: "\u00B0C",
79
- weather_code: "wmo code",
80
- is_day: "",
81
- },
82
- daily: {
83
- time: [
84
- "2025-01-15",
85
- "2025-01-16",
86
- "2025-01-17",
87
- "2025-01-18",
88
- "2025-01-19",
89
- ],
90
- weather_code: [2, 61, 3, 0, 1],
91
- temperature_2m_max: [17.0, 14.0, 16.0, 19.0, 20.0],
92
- temperature_2m_min: [10.0, 8.0, 9.0, 11.0, 12.0],
93
- precipitation_probability_max: [10, 80, 30, 0, 5],
94
- },
95
- daily_units: {
96
- temperature_2m_max: "\u00B0C",
97
- temperature_2m_min: "\u00B0C",
98
- precipitation_probability_max: "%",
99
- },
100
- };
101
-
102
- return (async (url: string | URL | Request) => {
103
- const urlStr =
104
- typeof url === "string" ? url : url instanceof URL ? url.href : url.url;
105
-
106
- if (urlStr.includes("geocoding-api.open-meteo.com")) {
107
- if (geoError) throw geoError;
108
- return new Response(
109
- JSON.stringify({ results: geoResults ?? defaultGeoResults }),
110
- {
111
- status: geoStatus,
112
- headers: { "content-type": "application/json" },
113
- },
114
- );
115
- }
116
-
117
- if (urlStr.includes("api.open-meteo.com")) {
118
- if (forecastError) throw forecastError;
119
- return new Response(JSON.stringify(forecastData ?? defaultForecastData), {
120
- status: forecastStatus,
121
- headers: { "content-type": "application/json" },
122
- });
123
- }
124
-
125
- return new Response("Not found", { status: 404 });
126
- }) as typeof globalThis.fetch;
127
- }
128
-
129
- // ---------------------------------------------------------------------------
130
- // Weather code mapping
131
- // ---------------------------------------------------------------------------
132
-
133
- describe("weatherCodeToDescription", () => {
134
- test("maps clear sky (code 0)", () => {
135
- expect(weatherCodeToDescription(0)).toBe("Clear sky");
136
- });
137
-
138
- test("maps partly cloudy (code 2)", () => {
139
- expect(weatherCodeToDescription(2)).toBe("Partly cloudy");
140
- });
141
-
142
- test("maps moderate rain (code 63)", () => {
143
- expect(weatherCodeToDescription(63)).toBe("Moderate rain");
144
- });
145
-
146
- test("maps heavy snowfall (code 75)", () => {
147
- expect(weatherCodeToDescription(75)).toBe("Heavy snowfall");
148
- });
149
-
150
- test("maps thunderstorm (code 95)", () => {
151
- expect(weatherCodeToDescription(95)).toBe("Thunderstorm");
152
- });
153
-
154
- test("maps thunderstorm with heavy hail (code 99)", () => {
155
- expect(weatherCodeToDescription(99)).toBe("Thunderstorm with heavy hail");
156
- });
157
-
158
- test("returns Unknown for unrecognized codes", () => {
159
- expect(weatherCodeToDescription(42)).toBe("Unknown");
160
- expect(weatherCodeToDescription(100)).toBe("Unknown");
161
- expect(weatherCodeToDescription(-1)).toBe("Unknown");
162
- });
163
- });
164
-
165
- // ---------------------------------------------------------------------------
166
- // Geocoding parsing
167
- // ---------------------------------------------------------------------------
168
-
169
- describe("geocoding parsing", () => {
170
- test("extracts location name, admin1, and country from geocoding results", async () => {
171
- const mockFetch = createMockFetch();
172
- const result = await executeGetWeather(
173
- { location: "San Francisco" },
174
- mockFetch,
175
- );
176
-
177
- expect(result.isError).toBe(false);
178
- expect(result.content).toContain(
179
- "San Francisco, California, United States",
180
- );
181
- expect(result.content).toContain("37.7749");
182
- expect(result.content).toContain("-122.4194");
183
- });
184
-
185
- test("handles location without admin1", async () => {
186
- const mockFetch = createMockFetch({
187
- geoResults: [
188
- {
189
- name: "Tokyo",
190
- latitude: 35.6762,
191
- longitude: 139.6503,
192
- country: "Japan",
193
- },
194
- ],
195
- });
196
- const result = await executeGetWeather({ location: "Tokyo" }, mockFetch);
197
-
198
- expect(result.isError).toBe(false);
199
- expect(result.content).toContain("Tokyo, Japan");
200
- });
201
-
202
- test("handles location without country", async () => {
203
- const mockFetch = createMockFetch({
204
- geoResults: [{ name: "Somewhere", latitude: 0, longitude: 0 }],
205
- });
206
- const result = await executeGetWeather(
207
- { location: "Somewhere" },
208
- mockFetch,
209
- );
210
-
211
- expect(result.isError).toBe(false);
212
- expect(result.content).toContain("Weather for Somewhere");
213
- });
214
- });
215
-
216
- // ---------------------------------------------------------------------------
217
- // Weather output formatting
218
- // ---------------------------------------------------------------------------
219
-
220
- describe("weather output formatting", () => {
221
- test("returns current conditions in fahrenheit by default", async () => {
222
- const mockFetch = createMockFetch();
223
- const result = await executeGetWeather(
224
- { location: "San Francisco" },
225
- mockFetch,
226
- );
227
-
228
- expect(result.isError).toBe(false);
229
- // 15C = 59F
230
- expect(result.content).toContain("59\u00B0F");
231
- expect(result.content).toContain("Humidity: 72%");
232
- expect(result.content).toContain("Partly cloudy");
233
- });
234
-
235
- test("returns current conditions in celsius when requested", async () => {
236
- const mockFetch = createMockFetch();
237
- const result = await executeGetWeather(
238
- { location: "San Francisco", units: "celsius" },
239
- mockFetch,
240
- );
241
-
242
- expect(result.isError).toBe(false);
243
- expect(result.content).toContain("15\u00B0C");
244
- expect(result.content).toContain("Humidity: 72%");
245
- });
246
-
247
- test("includes 10-day forecast by default", async () => {
248
- const mockFetch = createMockFetch();
249
- const result = await executeGetWeather(
250
- { location: "San Francisco" },
251
- mockFetch,
252
- );
253
-
254
- expect(result.isError).toBe(false);
255
- expect(result.content).toContain("10-Day Forecast");
256
- expect(result.content).toContain("2025-01-15");
257
- expect(result.content).toContain("2025-01-19");
258
- expect(result.content).toContain("Precip");
259
- });
260
-
261
- test("wind speed is converted to mph in fahrenheit mode", async () => {
262
- const mockFetch = createMockFetch();
263
- const result = await executeGetWeather(
264
- { location: "San Francisco" },
265
- mockFetch,
266
- );
267
-
268
- expect(result.isError).toBe(false);
269
- // 18 km/h ~= 11 mph
270
- expect(result.content).toContain("11 mph");
271
- expect(result.content).toContain("W");
272
- });
273
-
274
- test("wind speed stays in km/h in celsius mode", async () => {
275
- const mockFetch = createMockFetch();
276
- const result = await executeGetWeather(
277
- { location: "San Francisco", units: "celsius" },
278
- mockFetch,
279
- );
280
-
281
- expect(result.isError).toBe(false);
282
- expect(result.content).toContain("18 km/h");
283
- });
284
- });
285
-
286
- // ---------------------------------------------------------------------------
287
- // Error handling
288
- // ---------------------------------------------------------------------------
289
-
290
- describe("error handling", () => {
291
- test("returns error for missing location", async () => {
292
- const result = await executeGetWeather({});
293
- expect(result.isError).toBe(true);
294
- expect(result.content).toContain("location is required");
295
- });
296
-
297
- test("returns error for empty location", async () => {
298
- const result = await executeGetWeather({ location: "" });
299
- expect(result.isError).toBe(true);
300
- expect(result.content).toContain("location is required");
301
- });
302
-
303
- test("returns error for whitespace-only location", async () => {
304
- const result = await executeGetWeather({ location: " " });
305
- expect(result.isError).toBe(true);
306
- expect(result.content).toContain("location is required");
307
- });
308
-
309
- test("returns error for invalid units", async () => {
310
- const result = await executeGetWeather({
311
- location: "NYC",
312
- units: "kelvin",
313
- });
314
- expect(result.isError).toBe(true);
315
- expect(result.content).toContain('units must be "celsius" or "fahrenheit"');
316
- });
317
-
318
- test("returns error when location is not found", async () => {
319
- const mockFetch = createMockFetch({ geoResults: [] });
320
- const result = await executeGetWeather(
321
- { location: "xyznonexistent" },
322
- mockFetch,
323
- );
324
-
325
- expect(result.isError).toBe(true);
326
- expect(result.content).toContain("Could not find location");
327
- });
328
-
329
- test("returns error when geocoding returns no results array", async () => {
330
- // Return empty object with no results key
331
- const emptyGeoFetch = (async () => {
332
- return new Response(JSON.stringify({}), {
333
- status: 200,
334
- headers: { "content-type": "application/json" },
335
- });
336
- }) as unknown as typeof globalThis.fetch;
337
-
338
- const result = await executeGetWeather(
339
- { location: "unknown" },
340
- emptyGeoFetch,
341
- );
342
- expect(result.isError).toBe(true);
343
- expect(result.content).toContain("Could not find location");
344
- });
345
-
346
- test("returns error when geocoding API returns non-200", async () => {
347
- const mockFetch = createMockFetch({ geoStatus: 500 });
348
- const result = await executeGetWeather(
349
- { location: "San Francisco" },
350
- mockFetch,
351
- );
352
-
353
- expect(result.isError).toBe(true);
354
- expect(result.content).toContain("Geocoding API returned HTTP 500");
355
- });
356
-
357
- test("returns error when weather API returns non-200", async () => {
358
- const mockFetch = createMockFetch({ forecastStatus: 503 });
359
- const result = await executeGetWeather(
360
- { location: "San Francisco" },
361
- mockFetch,
362
- );
363
-
364
- expect(result.isError).toBe(true);
365
- expect(result.content).toContain("Weather API returned HTTP 503");
366
- });
367
-
368
- test("returns error when geocoding fetch throws", async () => {
369
- const mockFetch = createMockFetch({ geoError: new Error("Network error") });
370
- const result = await executeGetWeather(
371
- { location: "San Francisco" },
372
- mockFetch,
373
- );
374
-
375
- expect(result.isError).toBe(true);
376
- expect(result.content).toContain("Geocoding request failed");
377
- expect(result.content).toContain("Network error");
378
- });
379
-
380
- test("returns error when weather fetch throws", async () => {
381
- const mockFetch = createMockFetch({
382
- forecastError: new Error("Connection refused"),
383
- });
384
- const result = await executeGetWeather(
385
- { location: "San Francisco" },
386
- mockFetch,
387
- );
388
-
389
- expect(result.isError).toBe(true);
390
- expect(result.content).toContain("Weather forecast request failed");
391
- expect(result.content).toContain("Connection refused");
392
- });
393
- });