Skip to content

Commit c21bb46

Browse files
committed
Resolve serviceId case-insensitively
1 parent eb24e39 commit c21bb46

4 files changed

Lines changed: 101 additions & 23 deletions

File tree

src/collection-api/routes/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default async function apiRouter(basePath) {
3737

3838
router.use(await metadataRouter(collection, services));
3939
router.use(servicesRouter(services));
40-
router.use(versionsRouter);
40+
router.use(versionsRouter(services));
4141
router.use(feedRouter(services));
4242

4343
return router;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { expect } from 'chai';
2+
3+
import { findServiceCaseInsensitive } from './utils.js';
4+
5+
describe('findServiceCaseInsensitive', () => {
6+
const services = {
7+
'42Corp': { id: '42Corp' },
8+
ACMEco: { id: 'ACMEco' },
9+
'example.org': { id: 'example.org' },
10+
'Foo Bar': { id: 'Foo Bar' },
11+
'service-b': { id: 'service-b' },
12+
service·A: { id: 'service·A' },
13+
};
14+
15+
it('returns the service when the id matches exactly', () => {
16+
expect(findServiceCaseInsensitive(services, '42Corp')).to.equal(services['42Corp']);
17+
expect(findServiceCaseInsensitive(services, 'ACMEco')).to.equal(services.ACMEco);
18+
expect(findServiceCaseInsensitive(services, 'example.org')).to.equal(services['example.org']);
19+
expect(findServiceCaseInsensitive(services, 'Foo Bar')).to.equal(services['Foo Bar']);
20+
expect(findServiceCaseInsensitive(services, 'service-b')).to.equal(services['service-b']);
21+
expect(findServiceCaseInsensitive(services, 'service·A')).to.equal(services['service·A']);
22+
});
23+
24+
it('returns the service when the id casing differs', () => {
25+
expect(findServiceCaseInsensitive(services, '42CORP')).to.equal(services['42Corp']);
26+
expect(findServiceCaseInsensitive(services, 'acmeco')).to.equal(services.ACMEco);
27+
expect(findServiceCaseInsensitive(services, 'EXAMPLE.ORG')).to.equal(services['example.org']);
28+
expect(findServiceCaseInsensitive(services, 'foo bar')).to.equal(services['Foo Bar']);
29+
expect(findServiceCaseInsensitive(services, 'SERVICE-B')).to.equal(services['service-b']);
30+
expect(findServiceCaseInsensitive(services, 'SERVICE·A')).to.equal(services['service·A']);
31+
});
32+
33+
it('returns null when no service matches', () => {
34+
expect(findServiceCaseInsensitive(services, 'Unknown')).to.be.null;
35+
});
36+
37+
it('returns null when serviceId is undefined', () => {
38+
expect(findServiceCaseInsensitive(services, undefined)).to.be.null;
39+
});
40+
41+
it('returns null when services is empty', () => {
42+
expect(findServiceCaseInsensitive({}, 'Foo Bar')).to.be.null;
43+
});
44+
});

src/collection-api/routes/versions.js

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import express from 'express';
33
import { toISODateWithoutMilliseconds } from '../../archivist/utils/date.js';
44

55
import versionsRepository from './versionsRepository.js';
6+
import { findServiceCaseInsensitive } from './utils.js';
67

78
/**
89
* @private
@@ -27,9 +28,10 @@ import versionsRepository from './versionsRepository.js';
2728
* type: string
2829
* description: The JSON-escaped Markdown content of the version
2930
*/
30-
const router = express.Router();
31+
export default function versionsRouter(services) {
32+
const router = express.Router();
3133

32-
/**
34+
/**
3335
* @private
3436
* @swagger
3537
* /version/{serviceId}/{termsType}/{date}:
@@ -86,25 +88,32 @@ const router = express.Router();
8688
* type: string
8789
* description: Error message indicating that the requested date is in the future.
8890
*/
89-
router.get('/version/:serviceId/:termsType/:date', async (req, res) => {
90-
const { serviceId, termsType, date } = req.params;
91-
const requestedDate = new Date(date);
91+
router.get('/version/:serviceId/:termsType/:date', async (req, res) => {
92+
const { termsType, date } = req.params;
93+
const requestedDate = new Date(date);
94+
95+
if (requestedDate > new Date()) {
96+
return res.status(416).json({ error: 'Requested version is in the future' });
97+
}
98+
99+
const service = findServiceCaseInsensitive(services, req.params.serviceId);
92100

93-
if (requestedDate > new Date()) {
94-
return res.status(416).json({ error: 'Requested version is in the future' });
95-
}
101+
if (!service) {
102+
return res.status(404).json({ error: 'Service not found' });
103+
}
96104

97-
const version = await versionsRepository.findByDate(serviceId, termsType, requestedDate);
105+
const version = await versionsRepository.findByDate(service.id, termsType, requestedDate);
98106

99-
if (!version) {
100-
return res.status(404).json({ error: `No version found for date ${date}` });
101-
}
107+
if (!version) {
108+
return res.status(404).json({ error: `No version found for date ${date}` });
109+
}
102110

103-
return res.status(200).json({
104-
id: version.id,
105-
fetchDate: toISODateWithoutMilliseconds(version.fetchDate),
106-
content: version.content,
111+
return res.status(200).json({
112+
id: version.id,
113+
fetchDate: toISODateWithoutMilliseconds(version.fetchDate),
114+
content: version.content,
115+
});
107116
});
108-
});
109117

110-
export default router;
118+
return router;
119+
}

src/collection-api/routes/versions.test.js

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ describe('Versions API', () => {
1717
let versionsRepository;
1818
const FETCH_DATE = new Date('2023-01-01T12:00:00Z');
1919
const VERSION_COMMON_ATTRIBUTES = {
20-
serviceId: 'service-1',
20+
serviceId: 'service·A',
2121
termsType: 'Terms of Service',
2222
snapshotId: ['snapshot_id'],
2323
};
@@ -62,7 +62,7 @@ describe('Versions API', () => {
6262

6363
context('when a version is found', () => {
6464
before(async () => {
65-
response = await request.get(`${basePath}/v1/version/service-1/Terms%20of%20Service/${encodeURIComponent(toISODateWithoutMilliseconds(FETCH_DATE))}`);
65+
response = await request.get(`${basePath}/v1/version/service·A/Terms%20of%20Service/${encodeURIComponent(toISODateWithoutMilliseconds(FETCH_DATE))}`);
6666
});
6767

6868
it('responds with 200 status code', () => {
@@ -80,7 +80,7 @@ describe('Versions API', () => {
8080

8181
context('when the requested date is anterior to the first available version', () => {
8282
before(async () => {
83-
response = await request.get(`${basePath}/v1/version/service-1/Terms%20of%20Service/2000-01-01T12:00:00Z`);
83+
response = await request.get(`${basePath}/v1/version/service·A/Terms%20of%20Service/2000-01-01T12:00:00Z`);
8484
});
8585

8686
it('responds with 404 status code', () => {
@@ -96,11 +96,36 @@ describe('Versions API', () => {
9696
});
9797
});
9898

99+
context('when the serviceId uses different casing', () => {
100+
before(async () => {
101+
response = await request.get(`${basePath}/v1/version/SERVICE·A/Terms%20of%20Service/${encodeURIComponent(toISODateWithoutMilliseconds(FETCH_DATE))}`);
102+
});
103+
104+
it('still resolves to the service (case-insensitive)', () => {
105+
expect(response.status).to.equal(200);
106+
expect(response.body).to.deep.equal(expectedResult);
107+
});
108+
});
109+
110+
context('when the service does not exist', () => {
111+
before(async () => {
112+
response = await request.get(`${basePath}/v1/version/DoesNotExist/Terms%20of%20Service/${encodeURIComponent(toISODateWithoutMilliseconds(FETCH_DATE))}`);
113+
});
114+
115+
it('responds with 404 status code', () => {
116+
expect(response.status).to.equal(404);
117+
});
118+
119+
it('returns an error message', () => {
120+
expect(response.body.error).to.equal('Service not found');
121+
});
122+
});
123+
99124
context('when the requested date is in the future', () => {
100125
before(async () => {
101126
const dateInTheFuture = new Date(Date.now() + 60000); // 1 minute in the future
102127

103-
response = await request.get(`${basePath}/v1/version/service-1/Terms%20of%20Service/${encodeURIComponent(toISODateWithoutMilliseconds(dateInTheFuture))}`);
128+
response = await request.get(`${basePath}/v1/version/service·A/Terms%20of%20Service/${encodeURIComponent(toISODateWithoutMilliseconds(dateInTheFuture))}`);
104129
});
105130

106131
it('responds with 416 status code', () => {

0 commit comments

Comments
 (0)