html-renderer-api 1.0.83 → 1.0.84

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "html-renderer-api",
3
- "version": "1.0.83",
3
+ "version": "1.0.84",
4
4
  "description": "Server API renders DOM with puppeteer to get HTML & Bypass Cloudflare bot check.",
5
5
  "main": "src/scrapers/scraper-cloudflare.js",
6
6
  "type": "module",
@@ -0,0 +1,266 @@
1
+ /**
2
+ * @fileoverview Unit tests for request-parameter parsing and API-key auth.
3
+ */
4
+ import { describe, expect, it } from 'vitest';
5
+ import {
6
+ authenticateRequest,
7
+ parseRequestParams,
8
+ type Env,
9
+ type RequestParams,
10
+ } from '../src/utils/scraper-utils';
11
+
12
+ const env = (overrides: Partial<Env> = {}): Env => ({ MYBROWSER: {}, ...overrides }) as Env;
13
+
14
+ const emptyParams = {} as RequestParams;
15
+
16
+ describe('authenticateRequest', () => {
17
+ it('passes when no API key is configured', async () => {
18
+ const request = new Request('https://scraper.test/?url=https://example.com');
19
+
20
+ await expect(authenticateRequest(request, env(), emptyParams)).resolves.toEqual({
21
+ success: true,
22
+ });
23
+ });
24
+
25
+ it('accepts a matching Bearer token', async () => {
26
+ const request = new Request('https://scraper.test/', {
27
+ headers: { Authorization: 'Bearer secret' },
28
+ });
29
+
30
+ await expect(
31
+ authenticateRequest(request, env({ SCRAPER_API_KEY: 'secret' }), emptyParams)
32
+ ).resolves.toEqual({ success: true });
33
+ });
34
+
35
+ it('accepts a matching query parameter', async () => {
36
+ const request = new Request('https://scraper.test/?SCRAPER_API_KEY=secret');
37
+
38
+ await expect(
39
+ authenticateRequest(request, env({ SCRAPER_API_KEY: 'secret' }), emptyParams)
40
+ ).resolves.toEqual({ success: true });
41
+ });
42
+
43
+ it('accepts a matching body-sourced key', async () => {
44
+ const request = new Request('https://scraper.test/');
45
+
46
+ await expect(
47
+ authenticateRequest(request, env({ SCRAPER_API_KEY: 'secret' }), {
48
+ SCRAPER_API_KEY: 'secret',
49
+ } as RequestParams)
50
+ ).resolves.toEqual({ success: true });
51
+ });
52
+
53
+ it('rejects a wrong key', async () => {
54
+ const request = new Request('https://scraper.test/', {
55
+ headers: { Authorization: 'Bearer wrong' },
56
+ });
57
+
58
+ const result = await authenticateRequest(
59
+ request,
60
+ env({ SCRAPER_API_KEY: 'secret' }),
61
+ emptyParams
62
+ );
63
+
64
+ expect(result.success).toBe(false);
65
+ expect(JSON.parse(result.error!)).toEqual({ error: 'Invalid or missing API key' });
66
+ });
67
+
68
+ it('rejects a request with no key at all', async () => {
69
+ const request = new Request('https://scraper.test/');
70
+
71
+ const result = await authenticateRequest(
72
+ request,
73
+ env({ SCRAPER_API_KEY: 'secret' }),
74
+ emptyParams
75
+ );
76
+
77
+ expect(result.success).toBe(false);
78
+ });
79
+
80
+ it('ignores a non-Bearer Authorization header', async () => {
81
+ const request = new Request('https://scraper.test/', {
82
+ headers: { Authorization: 'Basic secret' },
83
+ });
84
+
85
+ const result = await authenticateRequest(
86
+ request,
87
+ env({ SCRAPER_API_KEY: 'secret' }),
88
+ emptyParams
89
+ );
90
+
91
+ expect(result.success).toBe(false);
92
+ });
93
+
94
+ it('prefers the Bearer token over the query parameter', async () => {
95
+ const request = new Request('https://scraper.test/?SCRAPER_API_KEY=secret', {
96
+ headers: { Authorization: 'Bearer wrong' },
97
+ });
98
+
99
+ const result = await authenticateRequest(
100
+ request,
101
+ env({ SCRAPER_API_KEY: 'secret' }),
102
+ emptyParams
103
+ );
104
+
105
+ expect(result.success).toBe(false);
106
+ });
107
+ });
108
+
109
+ describe('parseRequestParams defaults', () => {
110
+ it('applies the string defaults for a bare GET', async () => {
111
+ const params = await parseRequestParams(new Request('https://scraper.test/'));
112
+
113
+ expect(params).toMatchObject({
114
+ url: '',
115
+ blockImages: false,
116
+ sessionId: 'default',
117
+ waitUntil: 'networkidle2',
118
+ headers: {},
119
+ format: 'html',
120
+ bypassCaptcha: true,
121
+ });
122
+ });
123
+
124
+ it('yields NaN for the numeric fields when neither query nor body sets them', () => {
125
+ // Each numeric default is written as `String(bodyParams.x) ?? "0"`, and
126
+ // `String(undefined)` is the string "undefined" — never nullish — so the
127
+ // literal default is unreachable and parseInt returns NaN.
128
+ return parseRequestParams(new Request('https://scraper.test/')).then((params) => {
129
+ expect(Number.isNaN(params.wait)).toBe(true);
130
+ expect(Number.isNaN(params.timeout)).toBe(true);
131
+ expect(Number.isNaN(params.maxRetries)).toBe(true);
132
+ expect(Number.isNaN(params.challengeTimeout)).toBe(true);
133
+ });
134
+ });
135
+ });
136
+
137
+ describe('parseRequestParams from the query string', () => {
138
+ it('reads the scrape target and timings', async () => {
139
+ const params = await parseRequestParams(
140
+ new Request(
141
+ 'https://scraper.test/?url=https%3A%2F%2Fexample.com&wait=250&timeout=1000&challengeTimeout=99&maxRetries=2'
142
+ )
143
+ );
144
+
145
+ expect(params.url).toBe('https://example.com');
146
+ expect(params.wait).toBe(250);
147
+ expect(params.timeout).toBe(1000);
148
+ expect(params.challengeTimeout).toBe(99);
149
+ expect(params.maxRetries).toBe(2);
150
+ });
151
+
152
+ it('reads the boolean flags', async () => {
153
+ const params = await parseRequestParams(
154
+ new Request('https://scraper.test/?blockImages=true')
155
+ );
156
+
157
+ expect(params.blockImages).toBe(true);
158
+ });
159
+
160
+ it('treats a non-"true" flag value as false', async () => {
161
+ const params = await parseRequestParams(
162
+ new Request('https://scraper.test/?blockImages=yes')
163
+ );
164
+
165
+ expect(params.blockImages).toBe(false);
166
+ });
167
+
168
+ it('reads the session, format and waitUntil settings', async () => {
169
+ const params = await parseRequestParams(
170
+ new Request('https://scraper.test/?sessionId=s1&format=json&waitUntil=load')
171
+ );
172
+
173
+ expect(params.sessionId).toBe('s1');
174
+ expect(params.format).toBe('json');
175
+ expect(params.waitUntil).toBe('load');
176
+ });
177
+
178
+ it('reads the proxy and captcha settings', async () => {
179
+ const params = await parseRequestParams(
180
+ new Request(
181
+ 'https://scraper.test/?proxyUrl=http%3A%2F%2Fp.test&proxyUser=u&proxyPass=p&twoCaptchaKey=k&challengeMatch=Just+a+moment'
182
+ )
183
+ );
184
+
185
+ expect(params.proxyUrl).toBe('http://p.test');
186
+ expect(params.proxyUser).toBe('u');
187
+ expect(params.proxyPass).toBe('p');
188
+ expect(params.twoCaptchaKey).toBe('k');
189
+ expect(params.challengeMatch).toBe('Just a moment');
190
+ });
191
+
192
+ it('reads the cookie payload and the API key alias', async () => {
193
+ const params = await parseRequestParams(
194
+ new Request('https://scraper.test/?cookies=%5B%5D&SCRAPER_API_KEY=secret')
195
+ );
196
+
197
+ expect(params.cookies).toBe('[]');
198
+ expect(params.scraper_api_key).toBe('secret');
199
+ });
200
+ });
201
+
202
+ describe('parseRequestParams from a POST body', () => {
203
+ const post = (body: BodyInit, contentType: string) =>
204
+ new Request('https://scraper.test/', {
205
+ method: 'POST',
206
+ headers: { 'content-type': contentType },
207
+ body,
208
+ });
209
+
210
+ it('reads a JSON body', async () => {
211
+ const params = await parseRequestParams(
212
+ post(
213
+ JSON.stringify({
214
+ url: 'https://example.com',
215
+ wait: 500,
216
+ blockImages: true,
217
+ sessionId: 's2',
218
+ headers: { 'X-Test': '1' },
219
+ format: 'json',
220
+ }),
221
+ 'application/json'
222
+ )
223
+ );
224
+
225
+ expect(params.url).toBe('https://example.com');
226
+ expect(params.wait).toBe(500);
227
+ expect(params.blockImages).toBe(true);
228
+ expect(params.sessionId).toBe('s2');
229
+ expect(params.headers).toEqual({ 'X-Test': '1' });
230
+ expect(params.format).toBe('json');
231
+ });
232
+
233
+ it('reads a form-urlencoded body', async () => {
234
+ const params = await parseRequestParams(
235
+ post('url=https%3A%2F%2Fexample.com&sessionId=s3', 'application/x-www-form-urlencoded')
236
+ );
237
+
238
+ expect(params.url).toBe('https://example.com');
239
+ expect(params.sessionId).toBe('s3');
240
+ });
241
+
242
+ it('ignores an unparseable JSON body', async () => {
243
+ const params = await parseRequestParams(post('{not json', 'application/json'));
244
+
245
+ expect(params.url).toBe('');
246
+ expect(params.sessionId).toBe('default');
247
+ });
248
+
249
+ it('ignores a body with an unsupported content type', async () => {
250
+ const params = await parseRequestParams(post('url=x', 'text/plain'));
251
+
252
+ expect(params.url).toBe('');
253
+ });
254
+
255
+ it('lets the query string win over the body', async () => {
256
+ const request = new Request('https://scraper.test/?url=https%3A%2F%2Fquery.test', {
257
+ method: 'POST',
258
+ headers: { 'content-type': 'application/json' },
259
+ body: JSON.stringify({ url: 'https://body.test' }),
260
+ });
261
+
262
+ const params = await parseRequestParams(request);
263
+
264
+ expect(params.url).toBe('https://query.test');
265
+ });
266
+ });