Skip to content

Commit eeac924

Browse files
feat: Make technology, geo, rank, and category parameters optional with 'ALL' default and enhance API filter tests. (#83)
Signed-off-by: Max Ostapenko <1611259+max-ostapenko@users.noreply.github.com>
1 parent f0568b3 commit eeac924

File tree

7 files changed

+157
-70
lines changed

7 files changed

+157
-70
lines changed

README.md

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Lists available categories.
5454

5555
#### Categories Parameters
5656

57-
- `category` (optional): Filter by category name(s) - comma-separated list
57+
- `category` (optional): Filter by category name(s) - comma-separated list (defaults to 'ALL')
5858
- `onlyname` (optional): If present, returns only category names
5959
- `fields` (optional): Comma-separated list of fields to include in the response (see [Field Selection API Documentation](#field-selection-api-documentation) for details)
6060

@@ -117,7 +117,7 @@ Lists available technologies with optional filtering.
117117

118118
#### Parameters
119119

120-
- `technology` (optional): Filter by technology name(s) - comma-separated list
120+
- `technology` (optional): Filter by technology name(s) - comma-separated list (defaults to 'ALL')
121121
- `category` (optional): Filter by category - comma-separated list
122122
- `onlyname` (optional): If present, returns only technology names
123123
- `fields` (optional): Comma-separated list of fields to include in the response (see [Field Selection API Documentation](#field-selection-api-documentation) for details)
@@ -173,7 +173,7 @@ Lists available versions.
173173
174174
#### Versions Parameters
175175
176-
- `technology` (optional): Filter by technology name(s) - comma-separated list
176+
- `technology` (optional): Filter by technology name(s) - comma-separated list (defaults to 'ALL')
177177
- `category` (optional): Filter by category - comma-separated list
178178
- `version` (optional): Filter by version name(s) - comma-separated list
179179
- `onlyname` (optional): If present, returns only version names
@@ -207,9 +207,9 @@ Provides technology adoption data.
207207
208208
#### Adoption Parameters
209209
210-
- `technology` (required): Filter by technology name(s) - comma-separated list
211-
- `geo` (required): Filter by geographic location
212-
- `rank` (required): Filter by rank
210+
- `technology` (optional): Filter by technology name(s) - comma-separated list (defaults to 'ALL')
211+
- `geo` (optional): Filter by geographic location (defaults to 'ALL')
212+
- `rank` (optional): Filter by rank (defaults to 'ALL')
213213
- `start` (optional): Filter by date range start (YYYY-MM-DD or 'latest')
214214
- `end` (optional): Filter by date range end (YYYY-MM-DD)
215215
@@ -242,9 +242,9 @@ Provides Core Web Vitals metrics for technologies.
242242
243243
#### CWV Parameters
244244
245-
- `technology` (required): Filter by technology name(s) - comma-separated list
246-
- `geo` (required): Filter by geographic location
247-
- `rank` (required): Filter by rank
245+
- `technology` (optional): Filter by technology name(s) - comma-separated list (defaults to 'ALL')
246+
- `geo` (optional): Filter by geographic location (defaults to 'ALL')
247+
- `rank` (optional): Filter by rank (defaults to 'ALL')
248248
- `start` (optional): Filter by date range start (YYYY-MM-DD or 'latest')
249249
- `end` (optional): Filter by date range end (YYYY-MM-DD)
250250
@@ -285,9 +285,9 @@ Provides Lighthouse scores for technologies.
285285
286286
#### Lighthouse Parameters
287287
288-
- `technology` (required): Filter by technology name(s) - comma-separated list
289-
- `geo` (required): Filter by geographic location
290-
- `rank` (required): Filter by rank
288+
- `technology` (optional): Filter by technology name(s) - comma-separated list (defaults to 'ALL')
289+
- `geo` (optional): Filter by geographic location (defaults to 'ALL')
290+
- `rank` (optional): Filter by rank (defaults to 'ALL')
291291
- `start` (optional): Filter by date range start (YYYY-MM-DD or 'latest')
292292
- `end` (optional): Filter by date range end (YYYY-MM-DD)
293293
@@ -332,9 +332,9 @@ Provides Page Weight metrics for technologies.
332332
333333
#### Page Weight Parameters
334334
335-
- `technology` (required): Filter by technology name(s) - comma-separated list
336-
- `geo` (required): Filter by geographic location
337-
- `rank` (required): Filter by rank
335+
- `technology` (optional): Filter by technology name(s) - comma-separated list (defaults to 'ALL')
336+
- `geo` (optional): Filter by geographic location (defaults to 'ALL')
337+
- `rank` (optional): Filter by rank (defaults to 'ALL')
338338
- `start` (optional): Filter by date range start (YYYY-MM-DD or 'latest')
339339
- `end` (optional): Filter by date range end (YYYY-MM-DD)
340340
@@ -393,9 +393,9 @@ Provides Lighthouse audits for technologies.
393393
394394
#### Audits Parameters
395395
396-
- `technology` (required): Filter by technology name(s) - comma-separated list
397-
- `geo` (required): Filter by geographic location
398-
- `rank` (required): Filter by rank
396+
- `technology` (optional): Filter by technology name(s) - comma-separated list (defaults to 'ALL')
397+
- `geo` (optional): Filter by geographic location (defaults to 'ALL')
398+
- `rank` (optional): Filter by rank (defaults to 'ALL')
399399
- `start` (optional): Filter by date range start (YYYY-MM-DD or 'latest')
400400
- `end` (optional): Filter by date range end (YYYY-MM-DD)
401401
@@ -513,14 +513,14 @@ Proxy endpoint to serve files from the private Google Cloud Storage bucket. The
513513
514514
#### Supported File Types
515515
516-
| Extension | MIME Type |
517-
|-----------|-----------|
518-
| `.json` | `application/json` |
519-
| `.js` | `application/javascript` |
520-
| `.png` | `image/png` |
521-
| `.svg` | `image/svg+xml` |
522-
| `.csv` | `text/csv` |
523-
| `.pdf` | `application/pdf` |
516+
| Extension | MIME Type |
517+
| --------- | ------------------------ |
518+
| `.json` | `application/json` |
519+
| `.js` | `application/javascript` |
520+
| `.png` | `image/png` |
521+
| `.svg` | `image/svg+xml` |
522+
| `.csv` | `text/csv` |
523+
| `.pdf` | `application/pdf` |
524524
525525
#### Static File Response
526526

src/controllers/categoriesController.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ const listCategories = async (req, res) => {
2525
let query = firestore.collection('categories').orderBy('category', 'asc');
2626

2727
// Apply category filter with validation
28-
if (params.category) {
29-
const categories = validateArrayParameter(params.category, 'category');
28+
const categoryParam = params.category || 'ALL';
29+
if (categoryParam !== 'ALL') {
30+
const categories = validateArrayParameter(categoryParam, 'category');
3031
if (categories.length > 0) {
3132
query = query.where('category', 'in', categories);
3233
}

src/controllers/reportController.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,20 @@ const createReportController = (reportType) => {
6464
*/
6565

6666
// Validate required parameters using shared utility
67-
const errors = validateRequiredParams(params, [
68-
REQUIRED_PARAMS.GEO,
69-
REQUIRED_PARAMS.RANK,
70-
REQUIRED_PARAMS.TECHNOLOGY
71-
]);
67+
const errors = validateRequiredParams(params, []);
7268

7369
if (errors) {
7470
sendValidationError(res, errors);
7571
return;
7672
}
7773

74+
// Default technology, geo, and rank to 'ALL' if missing or empty
75+
const technologyParam = params.technology || 'ALL';
76+
const geoParam = params.geo || 'ALL';
77+
const rankParam = params.rank || 'ALL';
78+
7879
// Validate and process technology array
79-
const techArray = validateArrayParameter(params.technology, 'technology');
80+
const techArray = validateArrayParameter(technologyParam, 'technology');
8081

8182
// Handle 'latest' date substitution
8283
let startDate = params.start;
@@ -88,8 +89,8 @@ const createReportController = (reportType) => {
8889
let query = firestore.collection(config.table);
8990

9091
// Apply required filters
91-
query = query.where('geo', '==', params.geo);
92-
query = query.where('rank', '==', params.rank);
92+
query = query.where('geo', '==', geoParam);
93+
query = query.where('rank', '==', rankParam);
9394

9495
// Apply technology filter with batch processing
9596
query = query.where('technology', 'in', techArray);

src/controllers/technologiesController.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ const listTechnologies = async (req, res) => {
2525
let query = firestore.collection('technologies').orderBy('technology', 'asc');
2626

2727
// Apply technology filter with validation
28-
if (params.technology) {
29-
const technologies = validateTechnologyArray(params.technology);
28+
const technologyParam = params.technology || 'ALL';
29+
if (technologyParam !== 'ALL') {
30+
const technologies = validateTechnologyArray(technologyParam);
3031
if (technologies === null) {
3132
throw new Error(`Too many technologies specified. Maximum ${FIRESTORE_IN_LIMIT} allowed.`);
3233
}

src/controllers/versionsController.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ const listVersions = async (req, res) => {
2222
let query = firestore.collection('versions');
2323

2424
// Apply technology filter with validation
25-
if (params.technology) {
26-
const technologies = validateTechnologyArray(params.technology);
25+
const technologyParam = params.technology || 'ALL';
26+
if (technologyParam !== 'ALL') {
27+
const technologies = validateTechnologyArray(technologyParam);
2728
if (technologies === null) {
2829
throw new Error(`Too many technologies specified. Maximum ${FIRESTORE_IN_LIMIT} allowed.`);
2930
}

src/tests/routes.test.js

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,18 @@ describe('API Routes', () => {
106106
});
107107

108108
describe('GET /v1/technologies', () => {
109-
it('should return technologies', async () => {
109+
it('should return technologies (defaults to ALL technology)', async () => {
110110
const res = await request(app).get('/v1/technologies');
111111
expect(res.statusCode).toEqual(200);
112112
expect(Array.isArray(res.body)).toBe(true);
113113
});
114114

115+
it('should handle empty technology parameter', async () => {
116+
const res = await request(app).get('/v1/technologies?technology=');
117+
expect(res.statusCode).toEqual(200);
118+
expect(Array.isArray(res.body)).toBe(true);
119+
});
120+
115121
it('should filter technologies by name', async () => {
116122
const res = await request(app).get('/v1/technologies?technology=WordPress');
117123
expect(res.statusCode).toEqual(200);
@@ -224,12 +230,18 @@ describe('API Routes', () => {
224230
});
225231

226232
describe('GET /v1/versions', () => {
227-
it('should return versions', async () => {
233+
it('should return versions (defaults to ALL technology)', async () => {
228234
const res = await request(app).get('/v1/versions');
229235
expect(res.statusCode).toEqual(200);
230236
expect(Array.isArray(res.body)).toBe(true);
231237
});
232238

239+
it('should handle empty technology parameter', async () => {
240+
const res = await request(app).get('/v1/versions?technology=');
241+
expect(res.statusCode).toEqual(200);
242+
expect(Array.isArray(res.body)).toBe(true);
243+
});
244+
233245
it('should filter versions by technology', async () => {
234246
const res = await request(app).get('/v1/versions?technology=WordPress');
235247
expect(res.statusCode).toEqual(200);
@@ -255,10 +267,21 @@ describe('API Routes', () => {
255267
expect(Array.isArray(res.body)).toBe(true);
256268
});
257269

258-
it('should handle missing required parameters', async () => {
259-
const res = await request(app).get('/v1/adoption');
260-
expect(res.statusCode).toEqual(400);
261-
expect(res.body).toHaveProperty('errors');
270+
it('should handle missing or empty parameters (defaults to ALL)', async () => {
271+
// All missing
272+
const res1 = await request(app).get('/v1/adoption');
273+
expect(res1.statusCode).toEqual(200);
274+
expect(Array.isArray(res1.body)).toBe(true);
275+
276+
// Technology empty, others missing
277+
const res2 = await request(app).get('/v1/adoption?technology=');
278+
expect(res2.statusCode).toEqual(200);
279+
expect(Array.isArray(res2.body)).toBe(true);
280+
281+
// Geo and Rank empty
282+
const res3 = await request(app).get('/v1/adoption?technology=WordPress&geo=&rank=');
283+
expect(res3.statusCode).toEqual(200);
284+
expect(Array.isArray(res3.body)).toBe(true);
262285
});
263286

264287
it('should handle CORS preflight requests', async () => {
@@ -280,10 +303,10 @@ describe('API Routes', () => {
280303
expect(Array.isArray(res.body)).toBe(true);
281304
});
282305

283-
it('should handle missing required parameters', async () => {
306+
it('should handle optional parameters being omitted', async () => {
284307
const res = await request(app).get('/v1/cwv');
285-
expect(res.statusCode).toEqual(400);
286-
expect(res.body).toHaveProperty('errors');
308+
expect(res.statusCode).toEqual(200);
309+
expect(Array.isArray(res.body)).toBe(true);
287310
});
288311

289312
it('should handle CORS preflight requests', async () => {
@@ -305,10 +328,10 @@ describe('API Routes', () => {
305328
expect(Array.isArray(res.body)).toBe(true);
306329
});
307330

308-
it('should handle missing required parameters', async () => {
331+
it('should handle optional parameters being omitted', async () => {
309332
const res = await request(app).get('/v1/lighthouse');
310-
expect(res.statusCode).toEqual(400);
311-
expect(res.body).toHaveProperty('errors');
333+
expect(res.statusCode).toEqual(200);
334+
expect(Array.isArray(res.body)).toBe(true);
312335
});
313336

314337
it('should handle CORS preflight requests', async () => {
@@ -330,10 +353,10 @@ describe('API Routes', () => {
330353
expect(Array.isArray(res.body)).toBe(true);
331354
});
332355

333-
it('should handle missing required parameters', async () => {
356+
it('should handle optional parameters being omitted', async () => {
334357
const res = await request(app).get('/v1/page-weight');
335-
expect(res.statusCode).toEqual(400);
336-
expect(res.body).toHaveProperty('errors');
358+
expect(res.statusCode).toEqual(200);
359+
expect(Array.isArray(res.body)).toBe(true);
337360
});
338361

339362
it('should handle CORS preflight requests', async () => {

test-api.sh

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,46 @@ test_endpoint() {
2424
echo ""
2525
}
2626

27+
# Function to test an endpoint with filter verification
28+
test_filter() {
29+
local endpoint=$1
30+
local params=$2
31+
local filter_check=$3
32+
local description=$4
33+
local url="http://localhost:8080${endpoint}${params}"
34+
35+
echo "Testing filter: ${description}"
36+
echo "URL: ${url}"
37+
38+
response=$(curl -s -w "\n%{http_code}" "${url}")
39+
http_code=$(echo "$response" | tail -n1)
40+
body=$(echo "$response" | sed '$d')
41+
42+
if [[ $http_code -ne 200 ]]; then
43+
echo "Error: Endpoint returned status code $http_code"
44+
echo "$body" | jq .
45+
exit 1
46+
fi
47+
48+
# Run the verification check using jq
49+
# The check should return "true" if it passes
50+
check_result=$(echo "$body" | jq "${filter_check}")
51+
52+
if [[ "$check_result" != "true" ]]; then
53+
echo "Error: Filter verification failed for ${description}"
54+
echo "Verification expression: ${filter_check}"
55+
echo "Actual result: ${check_result}"
56+
echo "Sample data:"
57+
echo "$body" | jq . | head -20
58+
exit 1
59+
fi
60+
61+
echo "✓ Filter verification passed"
62+
echo ""
63+
echo "----------------------"
64+
echo ""
65+
}
66+
2767
# Function to test CORS preflight with OPTIONS request
2868
test_cors_preflight() {
2969
local endpoint=$1
@@ -101,19 +141,39 @@ test_endpoint "/v1/ranks" ""
101141
# Test geos endpoint
102142
test_endpoint "/v1/geos" ""
103143

104-
# Test adoption endpoint
105-
test_endpoint "/v1/adoption" "?technology=WordPress&geo=ALL&rank=ALL&start=latest"
106-
107-
# Test cwv endpoint
108-
test_endpoint "/v1/cwv" "?technology=WordPress,Drupal&geo=ALL&rank=ALL&start=latest"
109-
110-
# Test lighthouse endpoint
111-
test_endpoint "/v1/lighthouse" "?technology=WordPress&geo=ALL&rank=ALL&start=latest"
112-
113-
# Test page-weight endpoint
114-
test_endpoint "/v1/page-weight" "?technology=WordPress&geo=ALL&rank=ALL&start=latest"
115-
116-
# Test audits endpoint
117-
test_endpoint "/v1/audits" "?technology=WordPress&geo=ALL&rank=ALL&start=latest"
144+
# Test filter correspondences
145+
echo "Testing Filter Correspondences..."
146+
echo "----------------------"
147+
echo ""
118148

119-
echo "API tests complete! All endpoints returned 200 status code and CORS is properly configured."
149+
# Test adoption defaults (tech=ALL)
150+
test_filter "/v1/adoption" "" \
151+
"all(.[]; .technology == \"ALL\") and length > 0" \
152+
"Adoption defaults (technology=ALL)"
153+
154+
# Test adoption specific technology
155+
test_filter "/v1/adoption" "?technology=WordPress" \
156+
"all(.[]; .technology == \"WordPress\") and length > 0" \
157+
"Adoption specific technology (WordPress)"
158+
159+
# Test adoption specific geo and rank (verifying it returns data)
160+
test_filter "/v1/adoption" "?technology=WordPress&geo=Mexico&rank=Top%201M" \
161+
"all(.[]; .technology == \"WordPress\") and length > 0" \
162+
"Adoption specific geo and rank (returns WordPress data)"
163+
164+
# Test CWV defaults (tech=ALL)
165+
test_filter "/v1/cwv" "" \
166+
"all(.[]; .technology == \"ALL\") and length > 0" \
167+
"CWV defaults (technology=ALL)"
168+
169+
# Test technologies default
170+
test_filter "/v1/technologies" "" \
171+
"length > 0" \
172+
"Technologies list is not empty"
173+
174+
# Test categories default
175+
test_filter "/v1/categories" "" \
176+
"length > 0" \
177+
"Categories list is not empty"
178+
179+
echo "API tests complete! All endpoints returned 200 and data corresponds to filters."

0 commit comments

Comments
 (0)