wrangler 2.7.0 → 2.8.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 (34) hide show
  1. package/package.json +1 -1
  2. package/src/__tests__/helpers/mock-dialogs.ts +2 -0
  3. package/src/__tests__/helpers/mock-get-zone-from-host.ts +8 -5
  4. package/src/__tests__/helpers/mock-known-routes.ts +7 -2
  5. package/src/__tests__/helpers/mock-kv.ts +29 -16
  6. package/src/__tests__/helpers/mock-oauth-flow.ts +90 -98
  7. package/src/__tests__/helpers/msw/handlers/deployments.ts +18 -0
  8. package/src/__tests__/helpers/msw/index.ts +30 -1
  9. package/src/__tests__/init.test.ts +3 -14
  10. package/src/__tests__/jest.setup.ts +0 -23
  11. package/src/__tests__/pages-deployment-tail.test.ts +72 -1
  12. package/src/__tests__/pages.test.ts +18 -16
  13. package/src/__tests__/publish.test.ts +744 -522
  14. package/src/__tests__/secret.test.ts +1 -9
  15. package/src/__tests__/tail.test.ts +66 -1
  16. package/src/__tests__/tsconfig.tsbuildinfo +1 -1
  17. package/src/__tests__/user.test.ts +5 -15
  18. package/src/api/dev.ts +17 -5
  19. package/src/cfetch/internal.ts +0 -3
  20. package/src/d1/backups.tsx +1 -5
  21. package/src/dev/remote.tsx +2 -0
  22. package/src/docs/index.ts +2 -1
  23. package/src/init.ts +1 -1
  24. package/src/pages/dev.ts +57 -62
  25. package/src/pages/functions/buildPlugin.ts +2 -20
  26. package/src/pages/functions/buildWorker.ts +136 -20
  27. package/src/pages/functions/tsconfig.tsbuildinfo +1 -1
  28. package/src/pages/publish.tsx +36 -9
  29. package/src/publish/publish.ts +29 -4
  30. package/src/tail/createTail.ts +28 -1
  31. package/src/tail/printing.ts +15 -0
  32. package/templates/checked-fetch.js +1 -3
  33. package/wrangler-dist/cli.js +2935 -2824
  34. package/src/__tests__/helpers/mock-cfetch.ts +0 -278
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wrangler",
3
- "version": "2.7.0",
3
+ "version": "2.8.0",
4
4
  "description": "Command-line interface for all things Cloudflare Workers",
5
5
  "keywords": [
6
6
  "wrangler",
@@ -1,4 +1,5 @@
1
1
  import prompts from "prompts";
2
+
2
3
  /**
3
4
  * The expected values for a confirmation request.
4
5
  */
@@ -30,6 +31,7 @@ export function mockConfirm(...expectations: ConfirmExpectation[]) {
30
31
  if (expectation.options) {
31
32
  expect(initial).toStrictEqual(expectation.options?.defaultValue);
32
33
  }
34
+
33
35
  return Promise.resolve({ value: expectation.result });
34
36
  }
35
37
  );
@@ -1,8 +1,11 @@
1
- import { setMockResponse } from "./mock-cfetch";
1
+ import { rest } from "msw";
2
+ import { msw, createFetchResult } from "./msw";
2
3
 
3
4
  export function mockGetZoneFromHostRequest(host: string, zone?: string) {
4
- setMockResponse("/zones", (_uri, _init, queryParams) => {
5
- expect(queryParams.get("name")).toEqual(host);
6
- return zone ? [{ id: zone }] : [];
7
- });
5
+ msw.use(
6
+ rest.get("*/zones", (req, res, ctx) => {
7
+ expect(req.url.searchParams.get("name")).toEqual(host);
8
+ return res(ctx.json(createFetchResult(zone ? [{ id: zone }] : [])));
9
+ })
10
+ );
8
11
  }
@@ -1,7 +1,12 @@
1
- import { setMockResponse } from "./mock-cfetch";
1
+ import { rest } from "msw";
2
+ import { createFetchResult, msw } from "./msw";
2
3
 
3
4
  export function mockCollectKnownRoutesRequest(
4
5
  routes: { pattern: string; script: string }[]
5
6
  ) {
6
- setMockResponse(`/zones/:zoneId/workers/routes`, "GET", () => routes);
7
+ msw.use(
8
+ rest.get(`*/zones/:zoneId/workers/routes`, (_, res, ctx) =>
9
+ res.once(ctx.json(createFetchResult(routes)))
10
+ )
11
+ );
7
12
  }
@@ -1,4 +1,5 @@
1
- import { createFetchResult, setMockRawResponse } from "./mock-cfetch";
1
+ import { rest } from "msw";
2
+ import { createFetchResult, msw } from "./msw";
2
3
  import type { NamespaceKeyInfo } from "../../kv/helpers";
3
4
 
4
5
  export function mockKeyListRequest(
@@ -9,25 +10,37 @@ export function mockKeyListRequest(
9
10
  ) {
10
11
  const requests = { count: 0 };
11
12
  // See https://api.cloudflare.com/#workers-kv-namespace-list-a-namespace-s-keys
13
+ msw.use(
14
+ rest.get(
15
+ "*/accounts/:accountId/storage/kv/namespaces/:namespaceId/keys",
16
+ (req, res, ctx) => {
17
+ requests.count++;
18
+ expect(req.params.accountId).toEqual("some-account-id");
19
+ expect(req.params.namespaceId).toEqual(expectedNamespaceId);
20
+ let response: undefined | NamespaceKeyInfo[];
21
+ if (expectedKeys.length <= keysPerRequest) {
22
+ response = expectedKeys;
23
+ }
12
24
 
13
- setMockRawResponse(
14
- "/accounts/:accountId/storage/kv/namespaces/:namespaceId/keys",
15
- "GET",
16
- ([_url, accountId, namespaceId], _init, query) => {
17
- requests.count++;
18
- expect(accountId).toEqual("some-account-id");
19
- expect(namespaceId).toEqual(expectedNamespaceId);
20
- if (expectedKeys.length <= keysPerRequest) {
21
- return createFetchResult(expectedKeys);
22
- } else {
23
- const start = parseInt(query.get("cursor") ?? "0") || 0;
25
+ const start = parseInt(req.url.searchParams.get("cursor") ?? "0") || 0;
24
26
  const end = start + keysPerRequest;
25
27
  const cursor = end < expectedKeys.length ? end : blankCursorValue;
26
- return createFetchResult(expectedKeys.slice(start, end), true, [], [], {
27
- cursor,
28
- });
28
+
29
+ return res(
30
+ ctx.json(
31
+ createFetchResult(
32
+ response ? response : expectedKeys.slice(start, end),
33
+ true,
34
+ [],
35
+ [],
36
+ {
37
+ cursor,
38
+ }
39
+ )
40
+ )
41
+ );
29
42
  }
30
- }
43
+ )
31
44
  );
32
45
  return requests;
33
46
  }
@@ -1,30 +1,34 @@
1
- import fetchMock from "jest-fetch-mock";
1
+ import { rest } from "msw";
2
2
  import { Request } from "undici";
3
3
  import openInBrowser from "../../open-in-browser";
4
- import { setMockResponse } from "./mock-cfetch";
5
4
  import { mockHttpServer } from "./mock-http-server";
5
+ import { createFetchResult, msw } from "./msw";
6
6
 
7
7
  export function mockGetMemberships(
8
8
  accounts: { id: string; account: { id: string; name: string } }[]
9
9
  ) {
10
- setMockResponse("/memberships", "GET", () => {
11
- return accounts;
12
- });
10
+ msw.use(
11
+ rest.get("*/memberships", (_, res, ctx) => {
12
+ return res.once(ctx.json(createFetchResult(accounts)));
13
+ })
14
+ );
15
+ }
16
+ export function mockGetMembershipsFail() {
17
+ msw.use(
18
+ rest.get("*/memberships", (_, res, ctx) => {
19
+ return res.once(ctx.json(createFetchResult([], false)));
20
+ })
21
+ );
13
22
  }
14
-
15
- // the response to send when wrangler wants an oauth grant
16
- let oauthGrantResponse: GrantResponseOptions | "timeout" = {};
17
23
 
18
24
  /**
19
25
  * Functions to help with mocking various parts of the OAuth Flow
20
26
  */
21
27
  export const mockOAuthFlow = () => {
28
+ // the response to send when wrangler wants an oauth grant
29
+ let oauthGrantResponse: GrantResponseOptions | "timeout" = {};
22
30
  const fetchHttp = mockHttpServer();
23
31
 
24
- afterEach(() => {
25
- fetchMock.resetMocks();
26
- });
27
-
28
32
  /**
29
33
  * Mock out the callback from a browser to our HttpServer.
30
34
  *
@@ -61,7 +65,7 @@ export const mockOAuthFlow = () => {
61
65
  });
62
66
  };
63
67
 
64
- //TODO: this can just handled in `mockOAuthServerCallback`
68
+ // Handled in `mockOAuthServerCallback`
65
69
  const mockGrantAuthorization = ({
66
70
  respondWith,
67
71
  }: {
@@ -82,25 +86,6 @@ export const mockOAuthFlow = () => {
82
86
  }
83
87
  };
84
88
 
85
- const mockRevokeAuthorization = ({
86
- domain = "dash.cloudflare.com",
87
- }: { domain?: string } = {}) => {
88
- const outcome = {
89
- actual: new Request("https://example.org"),
90
- expected: new Request(`https://${domain}/oauth2/revoke`, {
91
- method: "POST",
92
- }),
93
- };
94
-
95
- fetchMock.mockIf(outcome.expected.url, async (req) => {
96
- // TODO: update Miniflare typings to match full undici Request
97
- outcome.actual = req as unknown as Request;
98
- return "";
99
- });
100
-
101
- return outcome;
102
- };
103
-
104
89
  const mockGrantAccessToken = ({
105
90
  respondWith,
106
91
  domain = "dash.cloudflare.com",
@@ -109,94 +94,101 @@ export const mockOAuthFlow = () => {
109
94
  domain?: string;
110
95
  }) => {
111
96
  const outcome = {
112
- actual: new Request("https://example.org"),
113
- expected: new Request(`https://${domain}/oauth2/token`, {
114
- method: "POST",
115
- }),
97
+ actual: "https://example.org",
98
+ expected: `https://${domain}/oauth2/token`,
116
99
  };
117
-
118
- fetchMock.mockOnceIf(outcome.expected.url, async (req) => {
119
- // TODO: update Miniflare typings to match full undici Request
120
- outcome.actual = req as unknown as Request;
121
- return makeTokenResponse(respondWith);
122
- });
100
+ msw.use(
101
+ rest.post(outcome.expected, async (req, res, ctx) => {
102
+ // TODO: update Miniflare typings to match full undici Request
103
+ outcome.actual = req.url.toString();
104
+ return res.once(ctx.json(makeTokenResponse(respondWith)));
105
+ })
106
+ );
123
107
 
124
108
  return outcome;
125
109
  };
126
110
 
127
- const mockExchangeRefreshTokenForAccessToken = ({
128
- respondWith,
129
- domain = "dash.cloudlfare.com",
111
+ function mockDomainUsesAccess({
112
+ usesAccess,
113
+ domain = "dash.cloudflare.com",
130
114
  }: {
131
- respondWith: "refreshSuccess" | "refreshError" | "badResponse";
115
+ usesAccess: boolean;
132
116
  domain?: string;
133
- }) => {
134
- fetchMock.mockOnceIf(`https://${domain}/oauth2/token`, async () => {
117
+ }) {
118
+ // If the domain relies upon Cloudflare Access, then a request to the domain
119
+ // will result in a redirect to the `cloudflareaccess.com` domain.
120
+ msw.use(
121
+ rest.get(`https://${domain}/`, (_, res, ctx) => {
122
+ let status = 200;
123
+ let headers: Record<string, string> = {
124
+ "Content-Type": "application/json",
125
+ };
126
+ if (usesAccess) {
127
+ status = 302;
128
+ headers = { location: "cloudflareaccess.com" };
129
+ }
130
+ return res.once(ctx.status(status), ctx.set(headers));
131
+ })
132
+ );
133
+ }
134
+
135
+ return {
136
+ mockHttpServer,
137
+ mockDomainUsesAccess,
138
+ mockGrantAccessToken,
139
+ mockOAuthServerCallback,
140
+ mockExchangeRefreshTokenForAccessToken,
141
+ };
142
+ };
143
+
144
+ export function mockExchangeRefreshTokenForAccessToken({
145
+ respondWith,
146
+ domain = "dash.cloudflare.com",
147
+ }: {
148
+ respondWith: "refreshSuccess" | "refreshError" | "badResponse";
149
+ domain?: string;
150
+ }) {
151
+ msw.use(
152
+ rest.post(`https://${domain}/oauth2/token`, async (_, res, ctx) => {
135
153
  switch (respondWith) {
136
154
  case "refreshSuccess":
137
- return {
138
- status: 200,
139
- body: JSON.stringify({
155
+ return res.once(
156
+ ctx.status(200),
157
+ ctx.json({
140
158
  access_token: "access_token_success_mock",
141
159
  expires_in: 1701,
142
160
  refresh_token: "refresh_token_success_mock",
143
161
  scope: "scope_success_mock",
144
162
  token_type: "bearer",
145
- }),
146
- };
163
+ })
164
+ );
147
165
  case "refreshError":
148
- return {
149
- status: 400,
150
- body: JSON.stringify({
166
+ return res.once(
167
+ ctx.status(400),
168
+ ctx.json({
151
169
  error: "invalid_request",
152
170
  error_description: "error_description_mock",
153
171
  error_hint: "error_hint_mock",
154
172
  error_verbose: "error_verbose_mock",
155
173
  status_code: 400,
156
- }),
157
- };
174
+ })
175
+ );
158
176
  case "badResponse":
159
- return {
160
- status: 400,
161
- body: `<html> <body> This shouldn't be sent, but should be handled </body> </html>`,
162
- };
177
+ return res.once(
178
+ ctx.status(400),
179
+ ctx.text(
180
+ `<html> <body> This shouldn't be sent, but should be handled </body> </html>`
181
+ )
182
+ );
163
183
 
164
184
  default:
165
- return "Not a respondWith option for `mockExchangeRefreshTokenForAccessToken`";
166
- }
167
- });
168
- };
169
-
170
- const mockDomainUsesAccess = ({
171
- usesAccess,
172
- domain = "dash.cloudflare.com",
173
- }: {
174
- usesAccess: boolean;
175
- domain?: string;
176
- }) => {
177
- // If the domain relies upon Cloudflare Access, then a request to the domain
178
- // will result in a redirect to the `cloudflareaccess.com` domain.
179
- fetchMock.mockOnceIf(`https://${domain}/`, async () => {
180
- if (usesAccess) {
181
- return {
182
- status: 302,
183
- headers: { location: "cloudflareaccess.com" },
184
- };
185
- } else {
186
- return { status: 200 };
185
+ throw new Error(
186
+ "Not a respondWith option for `mockExchangeRefreshTokenForAccessToken`"
187
+ );
187
188
  }
188
- });
189
- };
190
-
191
- return {
192
- mockDomainUsesAccess,
193
- mockGrantAccessToken,
194
- mockGrantAuthorization,
195
- mockOAuthServerCallback,
196
- mockRevokeAuthorization,
197
- mockExchangeRefreshTokenForAccessToken,
198
- };
199
- };
189
+ })
190
+ );
191
+ }
200
192
 
201
193
  type GrantResponseOptions = {
202
194
  code?: string;
@@ -249,7 +241,7 @@ type MockTokenResponse =
249
241
  error: ErrorType;
250
242
  };
251
243
 
252
- const makeTokenResponse = (partialResponse: MockTokenResponse): string => {
244
+ const makeTokenResponse = (partialResponse: MockTokenResponse) => {
253
245
  let fullResponse: MockTokenResponse;
254
246
 
255
247
  if (partialResponse === "ok") {
@@ -267,5 +259,5 @@ const makeTokenResponse = (partialResponse: MockTokenResponse): string => {
267
259
  fullResponse = partialResponse;
268
260
  }
269
261
 
270
- return JSON.stringify(fullResponse);
262
+ return fullResponse;
271
263
  };
@@ -57,3 +57,21 @@ export const mswSuccessDeployments = [
57
57
  )
58
58
  ),
59
59
  ];
60
+
61
+ export const mswSuccessLastDeployment = [
62
+ rest.get(
63
+ "*/accounts/:accountId/workers/services/:scriptName",
64
+ (req, res, ctx) => {
65
+ return res.once(
66
+ ctx.json({
67
+ success: true,
68
+ messages: [],
69
+ errors: [],
70
+ result: {
71
+ default_environment: { script: { last_deployed_from: "wrangler" } },
72
+ },
73
+ })
74
+ );
75
+ }
76
+ ),
77
+ ];
@@ -1,15 +1,43 @@
1
1
  import { setupServer } from "msw/node";
2
2
  import { default as mswAccessHandlers } from "./handlers/access";
3
- import { mswSuccessDeployments } from "./handlers/deployments";
3
+ import {
4
+ mswSuccessDeployments,
5
+ mswSuccessLastDeployment,
6
+ } from "./handlers/deployments";
4
7
  import { mswSuccessNamespacesHandlers } from "./handlers/namespaces";
5
8
  import { mswSuccessOauthHandlers } from "./handlers/oauth";
6
9
  import { mswSuccessR2handlers } from "./handlers/r2";
7
10
  import { default as mswSucessScriptHandlers } from "./handlers/script";
8
11
  import { mswSuccessUserHandlers } from "./handlers/user";
9
12
  import { default as mswZoneHandlers } from "./handlers/zones";
13
+
10
14
  export const msw = setupServer();
11
15
 
16
+ function createFetchResult(
17
+ result: unknown,
18
+ success = true,
19
+ errors: unknown[] = [],
20
+ messages: unknown[] = [],
21
+ result_info?: Record<string, unknown>
22
+ ) {
23
+ return result_info
24
+ ? {
25
+ result,
26
+ success,
27
+ errors,
28
+ messages,
29
+ result_info,
30
+ }
31
+ : {
32
+ result,
33
+ success,
34
+ errors,
35
+ messages,
36
+ };
37
+ }
38
+
12
39
  export {
40
+ createFetchResult,
13
41
  mswSuccessUserHandlers,
14
42
  mswSuccessR2handlers,
15
43
  mswSuccessOauthHandlers,
@@ -18,4 +46,5 @@ export {
18
46
  mswZoneHandlers,
19
47
  mswSuccessDeployments,
20
48
  mswAccessHandlers,
49
+ mswSuccessLastDeployment,
21
50
  };
@@ -4,9 +4,7 @@ import * as TOML from "@iarna/toml";
4
4
  import { execa, execaSync } from "execa";
5
5
  import { rest } from "msw";
6
6
  import { parseConfigFileTextToJson } from "typescript";
7
- import { FormData } from "undici";
8
7
  import { version as wranglerVersion } from "../../package.json";
9
- import { fetchDashboardScript } from "../cfetch/internal";
10
8
  import { getPackageManager } from "../package-manager";
11
9
  import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
12
10
  import { mockConsoleMethods } from "./helpers/mock-console";
@@ -46,8 +44,6 @@ describe("init", () => {
46
44
 
47
45
  afterEach(() => {
48
46
  clearDialogs();
49
- msw.resetHandlers();
50
- msw.restoreHandlers();
51
47
  });
52
48
 
53
49
  const std = mockConsoleMethods();
@@ -2798,6 +2794,7 @@ describe("init", () => {
2798
2794
  "isolinear-optical-chip/wrangler.toml": wranglerToml({
2799
2795
  ...mockConfigExpected,
2800
2796
  name: "isolinear-optical-chip",
2797
+ main: "src/index.js",
2801
2798
  }),
2802
2799
  },
2803
2800
  });
@@ -3070,19 +3067,11 @@ export function setMockFetchDashScript(mockResponse: string) {
3070
3067
  msw.use(
3071
3068
  rest.get(
3072
3069
  `*/accounts/:accountId/workers/services/:fromDashScriptName/environments/:environment/content`,
3073
- (req, res, ctx) => {
3074
- // This is a fake FormData object, until we can get MSW (beta) that supports FormData
3075
- const mockForm = new FormData();
3076
- mockForm.append("name", mockResponse);
3077
- // Sending it as JSON will result in empty object, but cause the fetchDashboardScript to execute its code
3078
- return res(ctx.status(200), ctx.json(mockForm));
3070
+ (_, res, ctx) => {
3071
+ return res(ctx.text(mockResponse));
3079
3072
  }
3080
3073
  )
3081
3074
  );
3082
- // Mock the actual return value that FormData would return
3083
- (fetchDashboardScript as jest.Mock).mockImplementationOnce(() => {
3084
- return mockResponse;
3085
- });
3086
3075
  }
3087
3076
 
3088
3077
  /**
@@ -1,16 +1,4 @@
1
1
  import fetchMock from "jest-fetch-mock";
2
- import {
3
- fetchDashboardScript,
4
- fetchInternal,
5
- fetchKVGetValue,
6
- fetchR2Objects,
7
- performApiFetch,
8
- } from "../cfetch/internal";
9
- import {
10
- mockFetchInternal,
11
- mockFetchR2Objects,
12
- mockPerformApiFetch,
13
- } from "./helpers/mock-cfetch";
14
2
  import { MockWebSocket } from "./helpers/mock-web-socket";
15
3
  import { msw } from "./helpers/msw";
16
4
 
@@ -89,17 +77,6 @@ afterEach(() => {
89
77
  });
90
78
  afterAll(() => msw.close());
91
79
 
92
- jest.mock("../cfetch/internal");
93
- (fetchDashboardScript as jest.Mock).mockImplementation(
94
- jest.requireActual("../cfetch/internal")["fetchDashboardScript"]
95
- );
96
- (fetchInternal as jest.Mock).mockImplementation(mockFetchInternal);
97
- (fetchKVGetValue as jest.Mock).mockImplementation(
98
- jest.requireActual("../cfetch/internal").fetchKVGetValue
99
- );
100
- (fetchR2Objects as jest.Mock).mockImplementation(mockFetchR2Objects);
101
- (performApiFetch as jest.Mock).mockImplementation(mockPerformApiFetch);
102
-
103
80
  jest.mock("../dev/dev", () => {
104
81
  const { useApp } = jest.requireActual("ink");
105
82
  const { useEffect } = jest.requireActual("react");
@@ -12,6 +12,7 @@ import type {
12
12
  RequestEvent,
13
13
  ScheduledEvent,
14
14
  AlarmEvent,
15
+ EmailEvent,
15
16
  } from "../tail/createTail";
16
17
  import type { RequestInit } from "undici";
17
18
  import type WebSocket from "ws";
@@ -355,6 +356,20 @@ describe("pages deployment tail", () => {
355
356
  expect(std.out).toMatch(deserializeToJson(serializedMessage));
356
357
  });
357
358
 
359
+ it("logs email messages in json format", async () => {
360
+ const api = mockTailAPIs();
361
+ await runWrangler(
362
+ "pages deployment tail mock-deployment-id --project-name mock-project --format json"
363
+ );
364
+
365
+ const event = generateMockEmailEvent();
366
+ const message = generateMockEventMessage({ event });
367
+ const serializedMessage = serialize(message);
368
+
369
+ api.ws.send(serializedMessage);
370
+ expect(std.out).toMatch(deserializeToJson(serializedMessage));
371
+ });
372
+
358
373
  it("logs request messages in pretty format", async () => {
359
374
  const api = mockTailAPIs();
360
375
  await runWrangler(
@@ -436,6 +451,33 @@ describe("pages deployment tail", () => {
436
451
  `);
437
452
  });
438
453
 
454
+ it("logs email messages in pretty format", async () => {
455
+ const api = mockTailAPIs();
456
+ await runWrangler(
457
+ "pages deployment tail mock-deployment-id --project-name mock-project --format pretty"
458
+ );
459
+
460
+ const event = generateMockEmailEvent();
461
+ const message = generateMockEventMessage({ event });
462
+ const serializedMessage = serialize(message);
463
+
464
+ api.ws.send(serializedMessage);
465
+ expect(
466
+ std.out
467
+ .replace(
468
+ new Date(mockEventTimestamp).toLocaleString(),
469
+ "[mock event timestamp]"
470
+ )
471
+ .replace(
472
+ mockTailExpiration.toLocaleString(),
473
+ "[mock expiration date]"
474
+ )
475
+ ).toMatchInlineSnapshot(`
476
+ "Connected to deployment mock-deployment-id, waiting for logs...
477
+ Email from:${mockEmailEventFrom} to:${mockEmailEventTo} size:${mockEmailEventSize} @ [mock event timestamp] - Ok"
478
+ `);
479
+ });
480
+
439
481
  it("should not crash when the tail message has a void event", async () => {
440
482
  const api = mockTailAPIs();
441
483
  await runWrangler(
@@ -608,7 +650,13 @@ function serialize(message: TailEventMessage): WebSocket.RawData {
608
650
  * @returns true if `event` is a RequestEvent
609
651
  */
610
652
  function isRequest(
611
- event: ScheduledEvent | RequestEvent | AlarmEvent | undefined | null
653
+ event:
654
+ | ScheduledEvent
655
+ | RequestEvent
656
+ | AlarmEvent
657
+ | EmailEvent
658
+ | undefined
659
+ | null
612
660
  ): event is RequestEvent {
613
661
  return Boolean(event && "request" in event);
614
662
  }
@@ -742,6 +790,21 @@ const mockEventTimestamp = 1645454470467;
742
790
  */
743
791
  const mockEventScheduledTime = new Date(mockEventTimestamp).toISOString();
744
792
 
793
+ /**
794
+ * Default value for email event from
795
+ */
796
+ const mockEmailEventFrom = "from@example.com";
797
+
798
+ /**
799
+ * Default value for email event to
800
+ */
801
+ const mockEmailEventTo = "to@example.com";
802
+
803
+ /**
804
+ * Default value for email event mail size
805
+ */
806
+ const mockEmailEventSize = 45416;
807
+
745
808
  /**
746
809
  * Mock out the API hit during Tail deletion
747
810
  *
@@ -882,3 +945,11 @@ function generateMockAlarmEvent(opts?: Partial<AlarmEvent>): AlarmEvent {
882
945
  scheduledTime: opts?.scheduledTime || mockEventScheduledTime,
883
946
  };
884
947
  }
948
+
949
+ function generateMockEmailEvent(opts?: Partial<EmailEvent>): EmailEvent {
950
+ return {
951
+ mailFrom: opts?.mailFrom || mockEmailEventFrom,
952
+ rcptTo: opts?.rcptTo || mockEmailEventTo,
953
+ rawSize: opts?.rawSize || mockEmailEventSize,
954
+ };
955
+ }