Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
parkpow
GitHub Repository: parkpow/deep-license-plate-recognition
Path: blob/master/webhooks/stream-parkpow-webhook-worker/test/index.spec.ts
1094 views
1
import { createExecutionContext, waitOnExecutionContext } from "cloudflare:test";
2
import { describe, expect, it } from "vitest";
3
import worker from "../src/index";
4
5
const requestPayload = JSON.stringify({ foo: "bar" });
6
const testEnv = {
7
PARKPOW_ENDPOINT: "http://example.com/parkpow-webhook",
8
PARKPOW_TOKEN: "parkpow-token-456",
9
STREAM_TOKEN: "stream-token-123",
10
};
11
12
async function executeWorkerRequest(request: Request, env: typeof testEnv = testEnv) {
13
const ctx = createExecutionContext();
14
const response = await worker.fetch(request, env, ctx);
15
await waitOnExecutionContext(ctx);
16
return response;
17
}
18
19
async function mockFetchAndExecute(
20
request: Request,
21
mockResponse: Response,
22
env: typeof testEnv = testEnv,
23
): Promise<{ response: Response; capturedRequest?: Request }> {
24
const ctx = createExecutionContext();
25
const originalFetch = globalThis.fetch;
26
let capturedRequest: Request | undefined;
27
28
globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
29
capturedRequest = new Request(input as string, init);
30
return mockResponse;
31
};
32
33
const response = await worker.fetch(request, env, ctx);
34
await waitOnExecutionContext(ctx);
35
globalThis.fetch = originalFetch;
36
37
return { response, capturedRequest };
38
}
39
40
function createRequest(
41
method: string,
42
headers: Record<string, string>,
43
body?: BodyInit | Uint8Array,
44
): Request {
45
return new Request("http://example.com/", {
46
method,
47
headers,
48
body: body as BodyInit,
49
});
50
}
51
52
function createAuthorizedPostRequest(body?: BodyInit): Request {
53
return createRequest(
54
"POST",
55
{
56
"Content-Type": "application/json",
57
Authorization: `Token ${testEnv.STREAM_TOKEN}`,
58
},
59
body,
60
);
61
}
62
63
describe("Stream to ParkPow webhook worker", () => {
64
it("should reject non-POST requests", async () => {
65
const request = createRequest("GET", {});
66
const response = await executeWorkerRequest(request);
67
68
expect(response.status).toBe(401);
69
expect(await response.text()).toBe("Unauthorized");
70
});
71
72
it("should forward POST request with correct incoming Authorization header and multipart/form-data body with mock image as-is", async () => {
73
const boundary = "37dd8504ce9387f0e07b86f03a25ab0a"; // pragma: allowlist secret
74
const imageBuffer = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
75
const multipartBody = createMultipartBody(boundary, imageBuffer);
76
77
const request = createRequest(
78
"POST",
79
{
80
"Content-Type": `multipart/form-data; boundary=${boundary}`,
81
Authorization: `Token ${testEnv.STREAM_TOKEN}`,
82
},
83
multipartBody,
84
);
85
86
const mockResponse = new Response(JSON.stringify({ success: true }), {
87
status: 200,
88
headers: { "Content-Type": "application/json" },
89
});
90
91
const { response, capturedRequest } = await mockFetchAndExecute(
92
request,
93
mockResponse,
94
);
95
96
expect(response.status).toBe(200);
97
expect(capturedRequest).toBeDefined();
98
if (capturedRequest) {
99
expect(capturedRequest.headers.get("Authorization")).toBe(
100
`Token ${testEnv.PARKPOW_TOKEN}`,
101
);
102
expect(capturedRequest.headers.get("Content-Type")).toBe(
103
`multipart/form-data; boundary=${boundary}`,
104
);
105
const forwardedBody = new Uint8Array(await capturedRequest.arrayBuffer());
106
expect(forwardedBody.length).toBe(multipartBody.length);
107
expect(forwardedBody.every((v, i) => v === multipartBody[i])).toBe(true);
108
}
109
});
110
111
it("should reject POST request with missing Authorization header", async () => {
112
const request = createRequest(
113
"POST",
114
{ "Content-Type": "application/json" },
115
requestPayload,
116
);
117
const response = await executeWorkerRequest(request);
118
119
expect(response.status).toBe(401);
120
expect(await response.text()).toBe("Unauthorized");
121
});
122
123
it("should reject POST request with invalid Authorization header", async () => {
124
const request = createRequest(
125
"POST",
126
{
127
"Content-Type": "application/json",
128
Authorization: "Token wrong-token",
129
},
130
requestPayload,
131
);
132
const response = await executeWorkerRequest(request);
133
134
expect(response.status).toBe(401);
135
expect(await response.text()).toBe("Unauthorized");
136
});
137
138
it("should handle fetch errors gracefully", async () => {
139
const request = createAuthorizedPostRequest(requestPayload);
140
141
const ctx = createExecutionContext();
142
const originalFetch = globalThis.fetch;
143
globalThis.fetch = async () => {
144
throw new Error("Network error");
145
};
146
147
const response = await worker.fetch(request, testEnv, ctx);
148
await waitOnExecutionContext(ctx);
149
globalThis.fetch = originalFetch;
150
151
expect(response.status).toBe(502);
152
expect(await response.text()).toBe("Network error");
153
});
154
155
it("should preserve ParkPow response status and headers", async () => {
156
const request = createAuthorizedPostRequest(requestPayload);
157
const mockResponse = new Response(JSON.stringify({ error: "Bad request" }), {
158
status: 400,
159
statusText: "Bad Request",
160
headers: { "X-Custom-Header": "test-value" },
161
});
162
163
const { response } = await mockFetchAndExecute(request, mockResponse);
164
165
expect(response.status).toBe(400);
166
expect(response.statusText).toBe("Bad Request");
167
expect(response.headers.get("X-Custom-Header")).toBe("test-value");
168
});
169
170
it("should handle missing PARKPOW_TOKEN", async () => {
171
const request = createAuthorizedPostRequest(requestPayload);
172
const envWithoutToken = {
173
...testEnv,
174
PARKPOW_TOKEN: "",
175
};
176
177
const mockResponse = new Response(JSON.stringify({ success: true }), {
178
status: 200,
179
});
180
181
const { response, capturedRequest } = await mockFetchAndExecute(
182
request,
183
mockResponse,
184
envWithoutToken,
185
);
186
187
expect(response.status).toBe(400);
188
expect(capturedRequest).toBeUndefined();
189
});
190
191
it("should handle ParkPow 401 Unauthorized response", async () => {
192
const request = createAuthorizedPostRequest(requestPayload);
193
const mockResponse = new Response(JSON.stringify({ detail: "Invalid token" }), {
194
status: 401,
195
statusText: "Unauthorized",
196
});
197
198
const { response } = await mockFetchAndExecute(request, mockResponse);
199
200
expect(response.status).toBe(401);
201
});
202
203
it("should handle ParkPow 403 Forbidden response", async () => {
204
const request = createAuthorizedPostRequest(requestPayload);
205
const mockResponse = new Response(
206
JSON.stringify({ detail: "Token not authorized for this resource" }),
207
{
208
status: 403,
209
statusText: "Forbidden",
210
},
211
);
212
213
const { response } = await mockFetchAndExecute(request, mockResponse);
214
215
expect(response.status).toBe(403);
216
});
217
});
218
219
function createMultipartBody(boundary: string, imageBuffer: Uint8Array): Uint8Array {
220
const encoder = new TextEncoder();
221
const CRLF = "\r\n";
222
const bodyParts: (string | Uint8Array)[] = [
223
`--${boundary}${CRLF}` +
224
`Content-Disposition: form-data; name="json"${CRLF}` +
225
`Content-Type: application/json${CRLF}${CRLF}` +
226
`{"foo":"bar"}${CRLF}` +
227
`--${boundary}${CRLF}` +
228
`Content-Disposition: form-data; name="upload"; filename="taxi.jpg"${CRLF}` +
229
`Content-Type: image/jpeg${CRLF}${CRLF}`,
230
imageBuffer,
231
`${CRLF}--${boundary}--${CRLF}`,
232
];
233
234
const encodedParts: Uint8Array[] = bodyParts.map((part) =>
235
typeof part === "string" ? encoder.encode(part) : part,
236
);
237
238
const totalLength = encodedParts.reduce(
239
(acc: number, curr: Uint8Array) => acc + curr.length,
240
0,
241
);
242
const result = new Uint8Array(totalLength);
243
let offset = 0;
244
for (const arr of encodedParts) {
245
result.set(arr, offset);
246
offset += arr.length;
247
}
248
return result;
249
}
250
251