Skip to content

Commit 89ac89f

Browse files
committed
msw: Implement GET /api/v1/crates/versions request handler
1 parent fbacd24 commit 89ac89f

File tree

5 files changed

+206
-0
lines changed

5 files changed

+206
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import listVersionsForCrate from './versions/list-for-crate.js';
2+
3+
export default [listVersionsForCrate];
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { http, HttpResponse } from 'msw';
2+
3+
import { db } from '../../index.js';
4+
import { serializeVersion } from '../../serializers/version.js';
5+
import { notFound } from '../../utils/handlers.js';
6+
import { calculateReleaseTracks } from '../../utils/release-tracks.js';
7+
8+
export default http.get('/api/v1/crates/:name/versions', async ({ request, params }) => {
9+
let { name } = params;
10+
let crate = db.crate.findFirst({ where: { name: { equals: name } } });
11+
if (!crate) return notFound();
12+
13+
let versions = db.version.findMany({ where: { crate: { id: { equals: crate.id } } } });
14+
15+
let url = new URL(request.url);
16+
let nums = url.searchParams.getAll('nums[]');
17+
if (nums.length !== 0) {
18+
versions = versions.filter(v => nums.includes(v.num));
19+
}
20+
21+
versions.sort((a, b) => b.id - a.id);
22+
let total = versions.length;
23+
24+
let include = url.searchParams.get('include') ?? '';
25+
let includes = include ? include.split(',') : [];
26+
27+
let serializedVersions = versions.map(v => serializeVersion(v, { includePublishedBy: true }));
28+
let meta = { total, next_page: null };
29+
30+
if (includes.includes('release_tracks')) {
31+
meta.release_tracks = calculateReleaseTracks(versions);
32+
}
33+
34+
return HttpResponse.json({ versions: serializedVersions, meta });
35+
});
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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/versions');
7+
assert.strictEqual(response.status, 404);
8+
assert.deepEqual(await response.json(), { errors: [{ detail: 'Not Found' }] });
9+
});
10+
11+
test('empty case', async function () {
12+
db.crate.create({ name: 'rand' });
13+
14+
let response = await fetch('/api/v1/crates/rand/versions');
15+
assert.strictEqual(response.status, 200);
16+
assert.deepEqual(await response.json(), {
17+
versions: [],
18+
meta: { total: 0, next_page: null },
19+
});
20+
});
21+
22+
test('returns all versions belonging to the specified crate', async function () {
23+
let user = db.user.create();
24+
let crate = db.crate.create({ name: 'rand' });
25+
db.version.create({ crate, num: '1.0.0' });
26+
db.version.create({ crate, num: '1.1.0', publishedBy: user });
27+
db.version.create({ crate, num: '1.2.0', rust_version: '1.69' });
28+
29+
let response = await fetch('/api/v1/crates/rand/versions');
30+
// assert.strictEqual(response.status, 200);
31+
assert.deepEqual(await response.json(), {
32+
versions: [
33+
{
34+
id: 3,
35+
crate: 'rand',
36+
crate_size: 488_889,
37+
created_at: '2010-06-16T21:30:45Z',
38+
dl_path: '/api/v1/crates/rand/1.2.0/download',
39+
downloads: 11_106,
40+
features: {},
41+
license: 'MIT/Apache-2.0',
42+
links: {
43+
dependencies: '/api/v1/crates/rand/1.2.0/dependencies',
44+
version_downloads: '/api/v1/crates/rand/1.2.0/downloads',
45+
},
46+
num: '1.2.0',
47+
published_by: null,
48+
readme_path: '/api/v1/crates/rand/1.2.0/readme',
49+
rust_version: '1.69',
50+
updated_at: '2017-02-24T12:34:56Z',
51+
yanked: false,
52+
yank_message: null,
53+
},
54+
{
55+
id: 2,
56+
crate: 'rand',
57+
crate_size: 325_926,
58+
created_at: '2010-06-16T21:30:45Z',
59+
dl_path: '/api/v1/crates/rand/1.1.0/download',
60+
downloads: 7404,
61+
features: {},
62+
license: 'Apache-2.0',
63+
links: {
64+
dependencies: '/api/v1/crates/rand/1.1.0/dependencies',
65+
version_downloads: '/api/v1/crates/rand/1.1.0/downloads',
66+
},
67+
num: '1.1.0',
68+
published_by: {
69+
id: 1,
70+
avatar: 'https://avatars1.githubusercontent.com/u/14631425?v=4',
71+
login: 'user-1',
72+
name: 'User 1',
73+
url: 'https://github.com/user-1',
74+
},
75+
readme_path: '/api/v1/crates/rand/1.1.0/readme',
76+
rust_version: null,
77+
updated_at: '2017-02-24T12:34:56Z',
78+
yanked: false,
79+
yank_message: null,
80+
},
81+
{
82+
id: 1,
83+
crate: 'rand',
84+
crate_size: 162_963,
85+
created_at: '2010-06-16T21:30:45Z',
86+
dl_path: '/api/v1/crates/rand/1.0.0/download',
87+
downloads: 3702,
88+
features: {},
89+
license: 'MIT',
90+
links: {
91+
dependencies: '/api/v1/crates/rand/1.0.0/dependencies',
92+
version_downloads: '/api/v1/crates/rand/1.0.0/downloads',
93+
},
94+
num: '1.0.0',
95+
published_by: null,
96+
readme_path: '/api/v1/crates/rand/1.0.0/readme',
97+
rust_version: null,
98+
updated_at: '2017-02-24T12:34:56Z',
99+
yanked: false,
100+
yank_message: null,
101+
},
102+
],
103+
meta: { total: 3, next_page: null },
104+
});
105+
});
106+
107+
test('supports multiple `ids[]` parameters', async function () {
108+
let user = db.user.create();
109+
let crate = db.crate.create({ name: 'rand' });
110+
db.version.create({ crate, num: '1.0.0' });
111+
db.version.create({ crate, num: '1.1.0', publishedBy: user });
112+
db.version.create({ crate, num: '1.2.0', rust_version: '1.69' });
113+
let response = await fetch('/api/v1/crates/rand/versions?nums[]=1.0.0&nums[]=1.2.0');
114+
assert.strictEqual(response.status, 200);
115+
let json = await response.json();
116+
assert.deepEqual(
117+
json.versions.map(v => v.num),
118+
['1.2.0', '1.0.0'],
119+
);
120+
});
121+
122+
test('include `release_tracks` meta', async function () {
123+
let user = db.user.create();
124+
let crate = db.crate.create({ name: 'rand' });
125+
db.version.create({ crate, num: '0.0.1' });
126+
db.version.create({ crate, num: '0.0.2', yanked: true });
127+
db.version.create({ crate, num: '1.0.0' });
128+
db.version.create({ crate, num: '1.1.0', publishedBy: user });
129+
db.version.create({ crate, num: '1.2.0', rust_version: '1.69', yanked: true });
130+
131+
let req = await fetch('/api/v1/crates/rand/versions');
132+
let expected = await req.json();
133+
134+
let response = await fetch('/api/v1/crates/rand/versions?include=release_tracks');
135+
// assert.strictEqual(response.status, 200);
136+
assert.deepEqual(await response.json(), {
137+
...expected,
138+
meta: {
139+
...expected.meta,
140+
release_tracks: {
141+
'0.0': {
142+
highest: '0.0.1',
143+
},
144+
1: {
145+
highest: '1.1.0',
146+
},
147+
},
148+
},
149+
});
150+
});

packages/crates-io-msw/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import metadataHandlers from './handlers/metadata.js';
88
import sessionHandlers from './handlers/sessions.js';
99
import teamHandlers from './handlers/teams.js';
1010
import userHandlers from './handlers/users.js';
11+
import versionHandlers from './handlers/versions.js';
1112
import apiToken from './models/api-token.js';
1213
import category from './models/category.js';
1314
import crateOwnerInvitation from './models/crate-owner-invitation.js';
@@ -33,6 +34,7 @@ export const handlers = [
3334
...sessionHandlers,
3435
...teamHandlers,
3536
...userHandlers,
37+
...versionHandlers,
3638
];
3739

3840
export const db = factory({
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import semverParse from 'semver/functions/parse';
2+
import semverSort from 'semver/functions/rsort';
3+
4+
export function calculateReleaseTracks(versions) {
5+
let versionNums = versions.filter(it => !it.yanked).map(it => it.num);
6+
semverSort(versionNums, { loose: true });
7+
let tracks = {};
8+
for (let num of versionNums) {
9+
let semver = semverParse(num, { loose: true });
10+
if (!semver || semver.prerelease.length !== 0) continue;
11+
let name = semver.major == 0 ? `0.${semver.minor}` : `${semver.major}`;
12+
if (name in tracks) continue;
13+
tracks[name] = { highest: num };
14+
}
15+
return tracks;
16+
}

0 commit comments

Comments
 (0)