Skip to content

Commit fbacd24

Browse files
committed
msw: Implement GET /api/v1/crates/:name request handler
1 parent 9ae5dee commit fbacd24

File tree

3 files changed

+385
-1
lines changed

3 files changed

+385
-1
lines changed
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import getCrate from './crates/get.js';
12
import listCrates from './crates/list.js';
23

3-
export default [listCrates];
4+
export default [listCrates, getCrate];
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { http, HttpResponse } from 'msw';
2+
3+
import { db } from '../../index.js';
4+
import { serializeCategory } from '../../serializers/category.js';
5+
import { serializeCrate } from '../../serializers/crate.js';
6+
import { serializeKeyword } from '../../serializers/keyword.js';
7+
import { serializeVersion } from '../../serializers/version.js';
8+
import { notFound } from '../../utils/handlers.js';
9+
10+
const DEFAULT_INCLUDES = ['versions', 'keywords', 'categories'];
11+
12+
export default http.get('/api/v1/crates/:name', async ({ request, params }) => {
13+
let { name } = params;
14+
let canonicalName = toCanonicalName(name);
15+
let crate = db.crate.findMany({}).find(it => toCanonicalName(it.name) === canonicalName);
16+
if (!crate) return notFound();
17+
18+
let versions = db.version.findMany({ where: { crate: { id: { equals: crate.id } } } });
19+
versions.sort((a, b) => b.id - a.id);
20+
21+
let url = new URL(request.url);
22+
let include = url.searchParams.get('include');
23+
let includes = include == null || include === 'full' ? DEFAULT_INCLUDES : include.split(',');
24+
25+
let includeCategories = includes.includes('categories');
26+
let includeKeywords = includes.includes('keywords');
27+
let includeVersions = includes.includes('versions');
28+
let includeDefaultVersion = includes.includes('default_version');
29+
30+
let serializedCrate = serializeCrate(crate, {
31+
calculateVersions: includeVersions,
32+
includeCategories,
33+
includeKeywords,
34+
includeVersions,
35+
});
36+
37+
let serializedVersions = null;
38+
if (includeVersions) {
39+
serializedVersions = versions.map(v => serializeVersion(v));
40+
} else if (includeDefaultVersion) {
41+
let defaultVersion = versions.find(v => v.num === serializedCrate.default_version);
42+
serializedVersions = [serializeVersion(defaultVersion)];
43+
}
44+
45+
return HttpResponse.json({
46+
crate: serializedCrate,
47+
categories: includeCategories ? crate.categories.map(c => serializeCategory(c)) : null,
48+
keywords: includeKeywords ? crate.keywords.map(k => serializeKeyword(k)) : null,
49+
versions: serializedVersions,
50+
});
51+
});
52+
53+
function toCanonicalName(name) {
54+
return name.toLowerCase().replace(/-/g, '_');
55+
}
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
import { assert, test } from 'vitest';
2+
3+
import { db } from '../../index.js';
4+
5+
test('returns 404 for unknown crates', async function () {
6+
let response = await fetch('/api/v1/crates/foo');
7+
assert.strictEqual(response.status, 404);
8+
assert.deepEqual(await response.json(), { errors: [{ detail: 'Not Found' }] });
9+
});
10+
11+
test('returns a crate object for known crates', async function () {
12+
let crate = db.crate.create({ name: 'rand' });
13+
db.version.create({ crate, num: '1.0.0-beta.1' });
14+
15+
let response = await fetch('/api/v1/crates/rand');
16+
assert.strictEqual(response.status, 200);
17+
assert.deepEqual(await response.json(), {
18+
categories: [],
19+
crate: {
20+
badges: [],
21+
categories: [],
22+
created_at: '2010-06-16T21:30:45Z',
23+
default_version: '1.0.0-beta.1',
24+
description: 'This is the description for the crate called "rand"',
25+
documentation: null,
26+
downloads: 37_035,
27+
homepage: null,
28+
id: 'rand',
29+
keywords: [],
30+
links: {
31+
owner_team: '/api/v1/crates/rand/owner_team',
32+
owner_user: '/api/v1/crates/rand/owner_user',
33+
reverse_dependencies: '/api/v1/crates/rand/reverse_dependencies',
34+
version_downloads: '/api/v1/crates/rand/downloads',
35+
versions: '/api/v1/crates/rand/versions',
36+
},
37+
max_version: '1.0.0-beta.1',
38+
max_stable_version: null,
39+
name: 'rand',
40+
newest_version: '1.0.0-beta.1',
41+
repository: null,
42+
updated_at: '2017-02-24T12:34:56Z',
43+
versions: [1],
44+
yanked: false,
45+
},
46+
keywords: [],
47+
versions: [
48+
{
49+
id: 1,
50+
crate: 'rand',
51+
crate_size: 162_963,
52+
created_at: '2010-06-16T21:30:45Z',
53+
dl_path: '/api/v1/crates/rand/1.0.0-beta.1/download',
54+
downloads: 3702,
55+
features: {},
56+
license: 'MIT',
57+
links: {
58+
dependencies: '/api/v1/crates/rand/1.0.0-beta.1/dependencies',
59+
version_downloads: '/api/v1/crates/rand/1.0.0-beta.1/downloads',
60+
},
61+
num: '1.0.0-beta.1',
62+
published_by: null,
63+
readme_path: '/api/v1/crates/rand/1.0.0-beta.1/readme',
64+
rust_version: null,
65+
updated_at: '2017-02-24T12:34:56Z',
66+
yanked: false,
67+
yank_message: null,
68+
},
69+
],
70+
});
71+
});
72+
73+
test('works for non-canonical names', async function () {
74+
let crate = db.crate.create({ name: 'foo-bar' });
75+
db.version.create({ crate, num: '1.0.0-beta.1' });
76+
77+
let response = await fetch('/api/v1/crates/foo_bar');
78+
assert.strictEqual(response.status, 200);
79+
assert.deepEqual(await response.json(), {
80+
categories: [],
81+
crate: {
82+
badges: [],
83+
categories: [],
84+
created_at: '2010-06-16T21:30:45Z',
85+
default_version: '1.0.0-beta.1',
86+
description: 'This is the description for the crate called "foo-bar"',
87+
documentation: null,
88+
downloads: 37_035,
89+
homepage: null,
90+
id: 'foo-bar',
91+
keywords: [],
92+
links: {
93+
owner_team: '/api/v1/crates/foo-bar/owner_team',
94+
owner_user: '/api/v1/crates/foo-bar/owner_user',
95+
reverse_dependencies: '/api/v1/crates/foo-bar/reverse_dependencies',
96+
version_downloads: '/api/v1/crates/foo-bar/downloads',
97+
versions: '/api/v1/crates/foo-bar/versions',
98+
},
99+
max_version: '1.0.0-beta.1',
100+
max_stable_version: null,
101+
name: 'foo-bar',
102+
newest_version: '1.0.0-beta.1',
103+
repository: null,
104+
updated_at: '2017-02-24T12:34:56Z',
105+
versions: [1],
106+
yanked: false,
107+
},
108+
keywords: [],
109+
versions: [
110+
{
111+
id: 1,
112+
crate: 'foo-bar',
113+
crate_size: 162_963,
114+
created_at: '2010-06-16T21:30:45Z',
115+
dl_path: '/api/v1/crates/foo-bar/1.0.0-beta.1/download',
116+
downloads: 3702,
117+
features: {},
118+
license: 'MIT',
119+
links: {
120+
dependencies: '/api/v1/crates/foo-bar/1.0.0-beta.1/dependencies',
121+
version_downloads: '/api/v1/crates/foo-bar/1.0.0-beta.1/downloads',
122+
},
123+
num: '1.0.0-beta.1',
124+
published_by: null,
125+
readme_path: '/api/v1/crates/foo-bar/1.0.0-beta.1/readme',
126+
rust_version: null,
127+
updated_at: '2017-02-24T12:34:56Z',
128+
yanked: false,
129+
yank_message: null,
130+
},
131+
],
132+
});
133+
});
134+
135+
test('includes related versions', async function () {
136+
let crate = db.crate.create({ name: 'rand' });
137+
db.version.create({ crate, num: '1.0.0' });
138+
db.version.create({ crate, num: '1.1.0' });
139+
db.version.create({ crate, num: '1.2.0' });
140+
141+
let response = await fetch('/api/v1/crates/rand');
142+
assert.strictEqual(response.status, 200);
143+
144+
let responsePayload = await response.json();
145+
assert.deepEqual(responsePayload.crate.versions, [1, 2, 3]);
146+
assert.deepEqual(responsePayload.versions, [
147+
{
148+
id: 3,
149+
crate: 'rand',
150+
crate_size: 488_889,
151+
created_at: '2010-06-16T21:30:45Z',
152+
dl_path: '/api/v1/crates/rand/1.2.0/download',
153+
downloads: 11_106,
154+
features: {},
155+
license: 'MIT/Apache-2.0',
156+
links: {
157+
dependencies: '/api/v1/crates/rand/1.2.0/dependencies',
158+
version_downloads: '/api/v1/crates/rand/1.2.0/downloads',
159+
},
160+
num: '1.2.0',
161+
published_by: null,
162+
readme_path: '/api/v1/crates/rand/1.2.0/readme',
163+
rust_version: null,
164+
updated_at: '2017-02-24T12:34:56Z',
165+
yanked: false,
166+
yank_message: null,
167+
},
168+
{
169+
id: 2,
170+
crate: 'rand',
171+
crate_size: 325_926,
172+
created_at: '2010-06-16T21:30:45Z',
173+
dl_path: '/api/v1/crates/rand/1.1.0/download',
174+
downloads: 7404,
175+
features: {},
176+
license: 'Apache-2.0',
177+
links: {
178+
dependencies: '/api/v1/crates/rand/1.1.0/dependencies',
179+
version_downloads: '/api/v1/crates/rand/1.1.0/downloads',
180+
},
181+
num: '1.1.0',
182+
published_by: null,
183+
readme_path: '/api/v1/crates/rand/1.1.0/readme',
184+
rust_version: null,
185+
updated_at: '2017-02-24T12:34:56Z',
186+
yanked: false,
187+
yank_message: null,
188+
},
189+
{
190+
id: 1,
191+
crate: 'rand',
192+
crate_size: 162_963,
193+
created_at: '2010-06-16T21:30:45Z',
194+
dl_path: '/api/v1/crates/rand/1.0.0/download',
195+
downloads: 3702,
196+
features: {},
197+
license: 'MIT',
198+
links: {
199+
dependencies: '/api/v1/crates/rand/1.0.0/dependencies',
200+
version_downloads: '/api/v1/crates/rand/1.0.0/downloads',
201+
},
202+
num: '1.0.0',
203+
published_by: null,
204+
readme_path: '/api/v1/crates/rand/1.0.0/readme',
205+
rust_version: null,
206+
updated_at: '2017-02-24T12:34:56Z',
207+
yanked: false,
208+
yank_message: null,
209+
},
210+
]);
211+
});
212+
213+
test('includes related categories', async function () {
214+
let noStd = db.category.create({ category: 'no-std' });
215+
db.category.create({ category: 'cli' });
216+
let crate = db.crate.create({ name: 'rand', categories: [noStd] });
217+
db.version.create({ crate });
218+
219+
let response = await fetch('/api/v1/crates/rand');
220+
assert.strictEqual(response.status, 200);
221+
222+
let responsePayload = await response.json();
223+
assert.deepEqual(responsePayload.crate.categories, ['no-std']);
224+
assert.deepEqual(responsePayload.categories, [
225+
{
226+
id: 'no-std',
227+
category: 'no-std',
228+
crates_cnt: 1,
229+
created_at: '2010-06-16T21:30:45Z',
230+
description: 'This is the description for the category called "no-std"',
231+
slug: 'no-std',
232+
},
233+
]);
234+
});
235+
236+
test('includes related keywords', async function () {
237+
let noStd = db.keyword.create({ keyword: 'no-std' });
238+
db.keyword.create({ keyword: 'cli' });
239+
let crate = db.crate.create({ name: 'rand', keywords: [noStd] });
240+
db.version.create({ crate });
241+
242+
let response = await fetch('/api/v1/crates/rand');
243+
assert.strictEqual(response.status, 200);
244+
245+
let responsePayload = await response.json();
246+
assert.deepEqual(responsePayload.crate.keywords, ['no-std']);
247+
assert.deepEqual(responsePayload.keywords, [
248+
{
249+
crates_cnt: 1,
250+
id: 'no-std',
251+
keyword: 'no-std',
252+
},
253+
]);
254+
});
255+
256+
test('without versions included', async function () {
257+
db.category.create({ category: 'no-std' });
258+
db.category.create({ category: 'cli' });
259+
db.keyword.create({ keyword: 'no-std' });
260+
db.keyword.create({ keyword: 'cli' });
261+
let crate = db.crate.create({ name: 'rand', categoryIds: ['no-std'], keywordIds: ['no-std'] });
262+
db.version.create({ crate, num: '1.0.0' });
263+
db.version.create({ crate, num: '1.1.0' });
264+
db.version.create({ crate, num: '1.2.0' });
265+
266+
let req = await fetch('/api/v1/crates/rand');
267+
let expected = await req.json();
268+
269+
let response = await fetch('/api/v1/crates/rand?include=keywords,categories');
270+
assert.strictEqual(response.status, 200);
271+
272+
let responsePayload = await response.json();
273+
assert.deepEqual(responsePayload, {
274+
...expected,
275+
crate: {
276+
...expected.crate,
277+
max_version: '0.0.0',
278+
newest_version: '0.0.0',
279+
max_stable_version: null,
280+
versions: null,
281+
},
282+
versions: null,
283+
});
284+
});
285+
286+
test('includes default_version', async function () {
287+
let crate = db.crate.create({ name: 'rand' });
288+
db.version.create({ crate, num: '1.0.0' });
289+
db.version.create({ crate, num: '1.1.0' });
290+
db.version.create({ crate, num: '1.2.0' });
291+
292+
let req = await fetch('/api/v1/crates/rand');
293+
let expected = await req.json();
294+
295+
let response = await fetch('/api/v1/crates/rand?include=default_version');
296+
assert.strictEqual(response.status, 200);
297+
298+
let responsePayload = await response.json();
299+
let default_version = expected.versions.find(it => it.num === responsePayload.crate.default_version);
300+
assert.deepEqual(responsePayload, {
301+
...expected,
302+
crate: {
303+
...expected.crate,
304+
categories: null,
305+
keywords: null,
306+
max_version: '0.0.0',
307+
newest_version: '0.0.0',
308+
max_stable_version: null,
309+
versions: null,
310+
},
311+
categories: null,
312+
keywords: null,
313+
versions: [default_version],
314+
});
315+
316+
let resp_both = await fetch('/api/v1/crates/rand?include=versions,default_version');
317+
assert.strictEqual(response.status, 200);
318+
assert.deepEqual(await resp_both.json(), {
319+
...expected,
320+
crate: {
321+
...expected.crate,
322+
categories: null,
323+
keywords: null,
324+
},
325+
categories: null,
326+
keywords: null,
327+
});
328+
});

0 commit comments

Comments
 (0)