Skip to content

Commit ac6a466

Browse files
committed
msw: Implement GET /api/private/crate_owner_invitations request handler
1 parent 642cf1c commit ac6a466

File tree

4 files changed

+281
-0
lines changed

4 files changed

+281
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import listInvites from './invites/list.js';
2+
3+
export default [listInvites];
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 { serializeInvite } from '../../serializers/invite.js';
5+
import { serializeUser } from '../../serializers/user.js';
6+
import { notFound } from '../../utils/handlers.js';
7+
import { getSession } from '../../utils/session.js';
8+
9+
export default http.get('/api/private/crate_owner_invitations', ({ request }) => {
10+
let url = new URL(request.url);
11+
12+
let { user } = getSession();
13+
if (!user) {
14+
return HttpResponse.json({ errors: [{ detail: 'must be logged in to perform that action' }] }, { status: 403 });
15+
}
16+
17+
let invites;
18+
if (url.searchParams.has('crate_name')) {
19+
let crate = db.crate.findFirst({ where: { name: { equals: url.searchParams.get('crate_name') } } });
20+
if (!crate) return notFound();
21+
22+
invites = db.crateOwnerInvitation.findMany({ where: { crate: { id: { equals: crate.id } } } });
23+
} else if (url.searchParams.has('invitee_id')) {
24+
let inviteeId = parseInt(url.searchParams.get('invitee_id'));
25+
if (inviteeId !== user.id) {
26+
return HttpResponse.json({ errors: [{ detail: 'must be logged in to perform that action' }] }, { status: 403 });
27+
}
28+
29+
invites = db.crateOwnerInvitation.findMany({ where: { invitee: { id: { equals: inviteeId } } } });
30+
} else {
31+
return HttpResponse.json({ errors: [{ detail: 'missing or invalid filter' }] }, { status: 400 });
32+
}
33+
34+
let perPage = 10;
35+
let start = parseInt(url.searchParams.get('__start__') ?? '0');
36+
let end = start + perPage;
37+
38+
let nextPage = null;
39+
if (invites.length > end) {
40+
url.searchParams.set('__start__', end);
41+
nextPage = url.search;
42+
}
43+
44+
invites = invites.slice(start, end);
45+
46+
let inviters = invites.map(invite => invite.inviter);
47+
let invitees = invites.map(invite => invite.invitee);
48+
let users = Array.from(new Set([...inviters, ...invitees])).sort((a, b) => a.id - b.id);
49+
50+
return HttpResponse.json({
51+
crate_owner_invitations: invites.map(invite => serializeInvite(invite)),
52+
users: users.map(user => serializeUser(user)),
53+
meta: { next_page: nextPage },
54+
});
55+
});
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
import { assert, test } from 'vitest';
2+
3+
import { db } from '../../index.js';
4+
5+
test('happy path (invitee_id)', async function () {
6+
let nanomsg = db.crate.create({ name: 'nanomsg' });
7+
db.version.create({ crate: nanomsg });
8+
9+
let ember = db.crate.create({ name: 'ember-rs' });
10+
db.version.create({ crate: ember });
11+
12+
let user = db.user.create();
13+
db.mswSession.create({ user });
14+
15+
let inviter = db.user.create({ name: 'janed' });
16+
db.crateOwnerInvitation.create({
17+
crate: nanomsg,
18+
createdAt: '2016-12-24T12:34:56Z',
19+
invitee: user,
20+
inviter,
21+
});
22+
23+
let inviter2 = db.user.create({ name: 'wycats' });
24+
db.crateOwnerInvitation.create({
25+
crate: ember,
26+
createdAt: '2020-12-31T12:34:56Z',
27+
invitee: user,
28+
inviter: inviter2,
29+
});
30+
31+
let response = await fetch(`/api/private/crate_owner_invitations?invitee_id=${user.id}`);
32+
assert.strictEqual(response.status, 200);
33+
assert.deepEqual(await response.json(), {
34+
crate_owner_invitations: [
35+
{
36+
crate_id: Number(nanomsg.id),
37+
crate_name: 'nanomsg',
38+
created_at: '2016-12-24T12:34:56Z',
39+
expires_at: '2017-01-24T12:34:56Z',
40+
invitee_id: Number(user.id),
41+
inviter_id: Number(inviter.id),
42+
},
43+
{
44+
crate_id: Number(ember.id),
45+
crate_name: 'ember-rs',
46+
created_at: '2020-12-31T12:34:56Z',
47+
expires_at: '2017-01-24T12:34:56Z',
48+
invitee_id: Number(user.id),
49+
inviter_id: Number(inviter2.id),
50+
},
51+
],
52+
users: [
53+
{
54+
avatar: user.avatar,
55+
id: Number(user.id),
56+
login: user.login,
57+
name: user.name,
58+
url: user.url,
59+
},
60+
{
61+
avatar: 'https://avatars1.githubusercontent.com/u/14631425?v=4',
62+
id: Number(inviter.id),
63+
login: 'janed',
64+
name: 'janed',
65+
url: 'https://github.com/janed',
66+
},
67+
{
68+
avatar: 'https://avatars1.githubusercontent.com/u/14631425?v=4',
69+
id: Number(inviter2.id),
70+
login: 'wycats',
71+
name: 'wycats',
72+
url: 'https://github.com/wycats',
73+
},
74+
],
75+
meta: {
76+
next_page: null,
77+
},
78+
});
79+
});
80+
81+
test('happy path with empty response (invitee_id)', async function () {
82+
let user = db.user.create();
83+
db.mswSession.create({ user });
84+
85+
let response = await fetch(`/api/private/crate_owner_invitations?invitee_id=${user.id}`);
86+
assert.strictEqual(response.status, 200);
87+
assert.deepEqual(await response.json(), {
88+
crate_owner_invitations: [],
89+
users: [],
90+
meta: {
91+
next_page: null,
92+
},
93+
});
94+
});
95+
96+
test('happy path with pagination (invitee_id)', async function () {
97+
let inviter = db.user.create();
98+
99+
let user = db.user.create();
100+
db.mswSession.create({ user });
101+
102+
for (let i = 0; i < 15; i++) {
103+
let crate = db.crate.create();
104+
db.version.create({ crate });
105+
db.crateOwnerInvitation.create({ crate, invitee: user, inviter });
106+
}
107+
108+
let response = await fetch(`/api/private/crate_owner_invitations?invitee_id=${user.id}`);
109+
assert.strictEqual(response.status, 200);
110+
let responseJSON = await response.json();
111+
assert.strictEqual(responseJSON['crate_owner_invitations'].length, 10);
112+
assert.ok(responseJSON.meta['next_page']);
113+
114+
response = await fetch(`/api/private/crate_owner_invitations${responseJSON.meta['next_page']}`);
115+
assert.strictEqual(response.status, 200);
116+
responseJSON = await response.json();
117+
assert.strictEqual(responseJSON['crate_owner_invitations'].length, 5);
118+
assert.strictEqual(responseJSON.meta['next_page'], null);
119+
});
120+
121+
test('happy path (crate_name)', async function () {
122+
let nanomsg = db.crate.create({ name: 'nanomsg' });
123+
db.version.create({ crate: nanomsg });
124+
125+
let ember = db.crate.create({ name: 'ember-rs' });
126+
db.version.create({ crate: ember });
127+
128+
let user = db.user.create();
129+
db.mswSession.create({ user });
130+
131+
let inviter = db.user.create({ name: 'janed' });
132+
db.crateOwnerInvitation.create({
133+
crate: nanomsg,
134+
createdAt: '2016-12-24T12:34:56Z',
135+
invitee: user,
136+
inviter,
137+
});
138+
139+
let inviter2 = db.user.create({ name: 'wycats' });
140+
db.crateOwnerInvitation.create({
141+
crate: ember,
142+
createdAt: '2020-12-31T12:34:56Z',
143+
invitee: user,
144+
inviter: inviter2,
145+
});
146+
147+
let response = await fetch(`/api/private/crate_owner_invitations?crate_name=ember-rs`);
148+
assert.strictEqual(response.status, 200);
149+
assert.deepEqual(await response.json(), {
150+
crate_owner_invitations: [
151+
{
152+
crate_id: Number(ember.id),
153+
crate_name: 'ember-rs',
154+
created_at: '2020-12-31T12:34:56Z',
155+
expires_at: '2017-01-24T12:34:56Z',
156+
invitee_id: Number(user.id),
157+
inviter_id: Number(inviter2.id),
158+
},
159+
],
160+
users: [
161+
{
162+
avatar: user.avatar,
163+
id: Number(user.id),
164+
login: user.login,
165+
name: user.name,
166+
url: user.url,
167+
},
168+
{
169+
avatar: 'https://avatars1.githubusercontent.com/u/14631425?v=4',
170+
id: Number(inviter2.id),
171+
login: 'wycats',
172+
name: 'wycats',
173+
url: 'https://github.com/wycats',
174+
},
175+
],
176+
meta: {
177+
next_page: null,
178+
},
179+
});
180+
});
181+
182+
test('returns 403 if unauthenticated', async function () {
183+
let response = await fetch(`/api/private/crate_owner_invitations?invitee_id=42`);
184+
assert.strictEqual(response.status, 403);
185+
assert.deepEqual(await response.json(), {
186+
errors: [{ detail: 'must be logged in to perform that action' }],
187+
});
188+
});
189+
190+
test('returns 400 if query params are missing', async function () {
191+
let user = db.user.create();
192+
db.mswSession.create({ user });
193+
194+
let response = await fetch(`/api/private/crate_owner_invitations`);
195+
assert.strictEqual(response.status, 400);
196+
assert.deepEqual(await response.json(), {
197+
errors: [{ detail: 'missing or invalid filter' }],
198+
});
199+
});
200+
201+
test("returns 404 if crate can't be found", async function () {
202+
let user = db.user.create();
203+
db.mswSession.create({ user });
204+
205+
let response = await fetch(`/api/private/crate_owner_invitations?crate_name=foo`);
206+
assert.strictEqual(response.status, 404);
207+
assert.deepEqual(await response.json(), {
208+
errors: [{ detail: 'Not Found' }],
209+
});
210+
});
211+
212+
test('returns 403 if requesting for other user', async function () {
213+
let user = db.user.create();
214+
db.mswSession.create({ user });
215+
216+
let response = await fetch(`/api/private/crate_owner_invitations?invitee_id=${user.id + 1}`);
217+
assert.strictEqual(response.status, 403);
218+
assert.deepEqual(await response.json(), {
219+
errors: [{ detail: 'must be logged in to perform that action' }],
220+
});
221+
});

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 categoryHandlers from './handlers/categories.js';
22
import docsRsHandlers from './handlers/docs-rs.js';
3+
import inviteHandlers from './handlers/invites.js';
34
import keywordHandlers from './handlers/keywords.js';
45
import metadataHandlers from './handlers/metadata.js';
56
import sessionHandlers from './handlers/sessions.js';
@@ -22,6 +23,7 @@ import { factory } from './utils/factory.js';
2223
export const handlers = [
2324
...categoryHandlers,
2425
...docsRsHandlers,
26+
...inviteHandlers,
2527
...keywordHandlers,
2628
...metadataHandlers,
2729
...sessionHandlers,

0 commit comments

Comments
 (0)