Skip to content

Commit 00dc81b

Browse files
committed
msw: Implement GET /api/v1/crates request handler
1 parent 29cb8ea commit 00dc81b

File tree

4 files changed

+314
-0
lines changed

4 files changed

+314
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import listCrates from './crates/list.js';
2+
3+
export default [listCrates];
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { http, HttpResponse } from 'msw';
2+
3+
import { db } from '../../index.js';
4+
import { serializeCrate } from '../../serializers/crate.js';
5+
import { pageParams } from '../../utils/handlers.js';
6+
import { getSession } from '../../utils/session.js';
7+
8+
export default http.get('/api/v1/crates', async ({ request }) => {
9+
let url = new URL(request.url);
10+
11+
const { start, end } = pageParams(request);
12+
13+
let crates = db.crate.findMany({});
14+
15+
if (url.searchParams.get('following') === '1') {
16+
let { user } = getSession();
17+
if (!user) {
18+
return HttpResponse.json({ errors: [{ detail: 'must be logged in to perform that action' }] }, { status: 403 });
19+
}
20+
21+
crates = user.followedCrates;
22+
}
23+
24+
let letter = url.searchParams.get('letter');
25+
if (letter) {
26+
letter = letter.toLowerCase();
27+
crates = crates.filter(crate => crate.name[0].toLowerCase() === letter);
28+
}
29+
30+
let q = url.searchParams.get('q');
31+
if (q) {
32+
q = q.toLowerCase();
33+
crates = crates.filter(crate => crate.name.toLowerCase().includes(q));
34+
}
35+
36+
let userId = url.searchParams.get('user_id');
37+
if (userId) {
38+
userId = parseInt(userId, 10);
39+
crates = crates.filter(crate =>
40+
db.crateOwnership.findFirst({
41+
where: {
42+
crate: { id: { equals: crate.id } },
43+
user: { id: { equals: userId } },
44+
},
45+
}),
46+
);
47+
}
48+
49+
let teamId = url.searchParams.get('team_id');
50+
if (teamId) {
51+
teamId = parseInt(teamId, 10);
52+
crates = crates.filter(crate =>
53+
db.crateOwnership.findFirst({
54+
where: {
55+
crate: { id: { equals: crate.id } },
56+
team: { id: { equals: teamId } },
57+
},
58+
}),
59+
);
60+
}
61+
62+
let ids = url.searchParams.getAll('ids[]');
63+
if (ids.length !== 0) {
64+
crates = crates.filter(crate => ids.includes(crate.name));
65+
}
66+
67+
let sort = url.searchParams.get('sort');
68+
if (sort === 'alpha') {
69+
crates = crates.sort((a, b) => compare(a.name.toLowerCase(), b.name.toLowerCase()));
70+
} else if (sort === 'recent-downloads') {
71+
crates = crates.sort((a, b) => b.recent_downloads - a.recent_downloads);
72+
}
73+
74+
let total = crates.length;
75+
crates = crates.slice(start, end);
76+
crates = crates.map(c => ({ ...serializeCrate(c), exact_match: c.name.toLowerCase() === q }));
77+
78+
return HttpResponse.json({ crates, meta: { total } });
79+
});
80+
81+
export function compare(a, b) {
82+
return a < b ? -1 : a > b ? 1 : 0;
83+
}
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
import { assert, test } from 'vitest';
2+
3+
import { db } from '../../index.js';
4+
5+
test('empty case', async function () {
6+
let response = await fetch('/api/v1/crates');
7+
assert.strictEqual(response.status, 200);
8+
assert.deepEqual(await response.json(), {
9+
crates: [],
10+
meta: {
11+
total: 0,
12+
},
13+
});
14+
});
15+
16+
test('returns a paginated crates list', async function () {
17+
let crate = db.crate.create({ name: 'rand' });
18+
db.version.create({
19+
crate,
20+
created_at: '2020-11-06T12:34:56Z',
21+
num: '1.0.0',
22+
updated_at: '2020-11-06T12:34:56Z',
23+
});
24+
db.version.create({
25+
crate,
26+
created_at: '2020-12-25T12:34:56Z',
27+
num: '2.0.0-beta.1',
28+
updated_at: '2020-12-25T12:34:56Z',
29+
});
30+
31+
let response = await fetch('/api/v1/crates');
32+
// assert.strictEqual(response.status, 200);
33+
assert.deepEqual(await response.json(), {
34+
crates: [
35+
{
36+
id: 'rand',
37+
badges: [],
38+
categories: null,
39+
created_at: '2010-06-16T21:30:45Z',
40+
default_version: '1.0.0',
41+
description: 'This is the description for the crate called "rand"',
42+
documentation: null,
43+
downloads: 37_035,
44+
exact_match: false,
45+
homepage: null,
46+
keywords: null,
47+
links: {
48+
owner_team: '/api/v1/crates/rand/owner_team',
49+
owner_user: '/api/v1/crates/rand/owner_user',
50+
reverse_dependencies: '/api/v1/crates/rand/reverse_dependencies',
51+
version_downloads: '/api/v1/crates/rand/downloads',
52+
versions: '/api/v1/crates/rand/versions',
53+
},
54+
max_version: '2.0.0-beta.1',
55+
max_stable_version: '1.0.0',
56+
name: 'rand',
57+
newest_version: '2.0.0-beta.1',
58+
repository: null,
59+
recent_downloads: 321,
60+
updated_at: '2017-02-24T12:34:56Z',
61+
versions: null,
62+
yanked: false,
63+
},
64+
],
65+
meta: {
66+
total: 1,
67+
},
68+
});
69+
});
70+
71+
test('never returns more than 10 results', async function () {
72+
let crates = Array.from({ length: 25 }, () => db.crate.create());
73+
crates.forEach(crate => db.version.create({ crate }));
74+
75+
let response = await fetch('/api/v1/crates');
76+
assert.strictEqual(response.status, 200);
77+
78+
let responsePayload = await response.json();
79+
assert.strictEqual(responsePayload.crates.length, 10);
80+
assert.strictEqual(responsePayload.meta.total, 25);
81+
});
82+
83+
test('supports `page` and `per_page` parameters', async function () {
84+
let crates = Array.from({ length: 25 }, (_, i) =>
85+
db.crate.create({ name: `crate-${String(i + 1).padStart(2, '0')}` }),
86+
);
87+
crates.forEach(crate => db.version.create({ crate }));
88+
89+
let response = await fetch('/api/v1/crates?page=2&per_page=5');
90+
assert.strictEqual(response.status, 200);
91+
92+
let responsePayload = await response.json();
93+
assert.strictEqual(responsePayload.crates.length, 5);
94+
assert.deepEqual(
95+
responsePayload.crates.map(it => it.id),
96+
['crate-06', 'crate-07', 'crate-08', 'crate-09', 'crate-10'],
97+
);
98+
assert.strictEqual(responsePayload.meta.total, 25);
99+
});
100+
101+
test('supports a `letter` parameter', async function () {
102+
let foo = db.crate.create({ name: 'foo' });
103+
db.version.create({ crate: foo });
104+
let bar = db.crate.create({ name: 'bar' });
105+
db.version.create({ crate: bar });
106+
let baz = db.crate.create({ name: 'BAZ' });
107+
db.version.create({ crate: baz });
108+
109+
let response = await fetch('/api/v1/crates?letter=b');
110+
assert.strictEqual(response.status, 200);
111+
112+
let responsePayload = await response.json();
113+
assert.strictEqual(responsePayload.crates.length, 2);
114+
assert.deepEqual(
115+
responsePayload.crates.map(it => it.id),
116+
['bar', 'BAZ'],
117+
);
118+
assert.strictEqual(responsePayload.meta.total, 2);
119+
});
120+
121+
test('supports a `q` parameter', async function () {
122+
let crate1 = db.crate.create({ name: '123456' });
123+
db.version.create({ crate: crate1 });
124+
let crate2 = db.crate.create({ name: '123' });
125+
db.version.create({ crate: crate2 });
126+
let crate3 = db.crate.create({ name: '87654' });
127+
db.version.create({ crate: crate3 });
128+
129+
let response = await fetch('/api/v1/crates?q=123');
130+
assert.strictEqual(response.status, 200);
131+
132+
let responsePayload = await response.json();
133+
assert.strictEqual(responsePayload.crates.length, 2);
134+
assert.deepEqual(
135+
responsePayload.crates.map(it => it.id),
136+
['123456', '123'],
137+
);
138+
assert.deepEqual(
139+
responsePayload.crates.map(it => it.exact_match),
140+
[false, true],
141+
);
142+
assert.strictEqual(responsePayload.meta.total, 2);
143+
});
144+
145+
test('supports a `user_id` parameter', async function () {
146+
let user1 = db.user.create();
147+
let user2 = db.user.create();
148+
149+
let foo = db.crate.create({ name: 'foo' });
150+
db.version.create({ crate: foo });
151+
let bar = db.crate.create({ name: 'bar' });
152+
db.crateOwnership.create({ crate: bar, user: user1 });
153+
db.version.create({ crate: bar });
154+
let baz = db.crate.create({ name: 'baz' });
155+
db.crateOwnership.create({ crate: baz, user: user2 });
156+
db.version.create({ crate: baz });
157+
158+
let response = await fetch(`/api/v1/crates?user_id=${user1.id}`);
159+
assert.strictEqual(response.status, 200);
160+
161+
let responsePayload = await response.json();
162+
assert.strictEqual(responsePayload.crates.length, 1);
163+
assert.strictEqual(responsePayload.crates[0].id, 'bar');
164+
assert.strictEqual(responsePayload.meta.total, 1);
165+
});
166+
167+
test('supports a `team_id` parameter', async function () {
168+
let team1 = db.team.create();
169+
let team2 = db.team.create();
170+
171+
let foo = db.crate.create({ name: 'foo' });
172+
db.version.create({ crate: foo });
173+
let bar = db.crate.create({ name: 'bar' });
174+
db.crateOwnership.create({ crate: bar, team: team1 });
175+
db.version.create({ crate: bar });
176+
let baz = db.crate.create({ name: 'baz' });
177+
db.crateOwnership.create({ crate: baz, team: team2 });
178+
db.version.create({ crate: baz });
179+
180+
let response = await fetch(`/api/v1/crates?team_id=${team1.id}`);
181+
assert.strictEqual(response.status, 200);
182+
183+
let responsePayload = await response.json();
184+
assert.strictEqual(responsePayload.crates.length, 1);
185+
assert.strictEqual(responsePayload.crates[0].id, 'bar');
186+
assert.strictEqual(responsePayload.meta.total, 1);
187+
});
188+
189+
test('supports a `following` parameter', async function () {
190+
let foo = db.crate.create({ name: 'foo' });
191+
db.version.create({ crate: foo });
192+
let bar = db.crate.create({ name: 'bar' });
193+
db.version.create({ crate: bar });
194+
195+
let user = db.user.create({ followedCrates: [bar] });
196+
db.mswSession.create({ user });
197+
198+
let response = await fetch(`/api/v1/crates?following=1`);
199+
assert.strictEqual(response.status, 200);
200+
201+
let responsePayload = await response.json();
202+
assert.strictEqual(responsePayload.crates.length, 1);
203+
assert.strictEqual(responsePayload.crates[0].id, 'bar');
204+
assert.strictEqual(responsePayload.meta.total, 1);
205+
});
206+
207+
test('supports multiple `ids[]` parameters', async function () {
208+
let foo = db.crate.create({ name: 'foo' });
209+
db.version.create({ crate: foo });
210+
let bar = db.crate.create({ name: 'bar' });
211+
db.version.create({ crate: bar });
212+
let baz = db.crate.create({ name: 'baz' });
213+
db.version.create({ crate: baz });
214+
let other = db.crate.create({ name: 'other' });
215+
db.version.create({ crate: other });
216+
217+
let response = await fetch(`/api/v1/crates?ids[]=foo&ids[]=bar&ids[]=baz&ids[]=baz&ids[]=unknown`);
218+
assert.strictEqual(response.status, 200);
219+
220+
let responsePayload = await response.json();
221+
assert.strictEqual(responsePayload.crates.length, 3);
222+
assert.strictEqual(responsePayload.crates[0].id, 'foo');
223+
assert.strictEqual(responsePayload.crates[1].id, 'bar');
224+
assert.strictEqual(responsePayload.crates[2].id, 'baz');
225+
assert.strictEqual(responsePayload.meta.total, 3);
226+
});

packages/crates-io-msw/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import apiTokenHandlers from './handlers/api-tokens.js';
22
import categoryHandlers from './handlers/categories.js';
3+
import cratesHandlers from './handlers/crates.js';
34
import docsRsHandlers from './handlers/docs-rs.js';
45
import inviteHandlers from './handlers/invites.js';
56
import keywordHandlers from './handlers/keywords.js';
@@ -24,6 +25,7 @@ import { factory } from './utils/factory.js';
2425
export const handlers = [
2526
...apiTokenHandlers,
2627
...categoryHandlers,
28+
...cratesHandlers,
2729
...docsRsHandlers,
2830
...inviteHandlers,
2931
...keywordHandlers,

0 commit comments

Comments
 (0)