Skip to content

Commit d547935

Browse files
committed
Auto merge of #3707 - Turbo87:invites, r=locks
mirage: Implement `crate_owner_invitations` endpoints This should simplify implementing #2868 in the future :)
2 parents 9c040cf + 9ab26af commit d547935

File tree

8 files changed

+256
-73
lines changed

8 files changed

+256
-73
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Factory } from 'ember-cli-mirage';
2+
3+
export default Factory.extend({
4+
createdAt: '2016-12-24T12:34:56Z',
5+
token: i => `secret-token-${i}`,
6+
7+
afterCreate(invite) {
8+
if (!invite.crateId) {
9+
throw new Error(`Missing \`crate\` relationship on \`crate-owner-invitation:${invite.id}\``);
10+
}
11+
if (!invite.inviteeId) {
12+
throw new Error(`Missing \`invitee\` relationship on \`crate-owner-invitation:${invite.id}\``);
13+
}
14+
if (!invite.inviterId) {
15+
throw new Error(`Missing \`inviter\` relationship on \`crate-owner-invitation:${invite.id}\``);
16+
}
17+
},
18+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { belongsTo, Model } from 'ember-cli-mirage';
2+
3+
export default Model.extend({
4+
crate: belongsTo(),
5+
invitee: belongsTo('user'),
6+
inviter: belongsTo('user'),
7+
});

mirage/models/crate-owner-invite.js

Lines changed: 0 additions & 3 deletions
This file was deleted.

mirage/route-handlers/me.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,50 @@ export function register(server) {
9898

9999
return { ok: true };
100100
});
101+
102+
server.get('/api/v1/me/crate_owner_invitations', function (schema) {
103+
let { user } = getSession(schema);
104+
if (!user) {
105+
return new Response(403, {}, { errors: [{ detail: 'must be logged in to perform that action' }] });
106+
}
107+
108+
return schema.crateOwnerInvitations.where({ inviteeId: user.id });
109+
});
110+
111+
server.put('/api/v1/me/crate_owner_invitations/:crate_id', (schema, request) => {
112+
let { user } = getSession(schema);
113+
if (!user) {
114+
return new Response(403, {}, { errors: [{ detail: 'must be logged in to perform that action' }] });
115+
}
116+
117+
let body = JSON.parse(request.requestBody);
118+
let { accepted, crate_id: crateId } = body.crate_owner_invite;
119+
120+
let invite = schema.crateOwnerInvitations.findBy({ crateId, inviteeId: user.id });
121+
if (!invite) {
122+
return new Response(404);
123+
}
124+
125+
if (accepted) {
126+
server.create('crate-ownership', { crate: invite.crate, user });
127+
}
128+
129+
invite.destroy();
130+
131+
return { crate_owner_invitation: { crate_id: crateId, accepted } };
132+
});
133+
134+
server.put('/api/v1/me/crate_owner_invitations/accept/:token', (schema, request) => {
135+
let { token } = request.params;
136+
137+
let invite = schema.crateOwnerInvitations.findBy({ token });
138+
if (!invite) {
139+
return new Response(404);
140+
}
141+
142+
server.create('crate-ownership', { crate: invite.crate, user: invite.invitee });
143+
invite.destroy();
144+
145+
return { crate_owner_invitation: { crate_id: invite.crateId, accepted: true } };
146+
});
101147
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import BaseSerializer from './application';
2+
3+
export default BaseSerializer.extend({
4+
// eslint-disable-next-line ember/avoid-leaking-state-in-ember-objects
5+
include: ['inviter'],
6+
7+
getHashForResource() {
8+
let [hash, addToIncludes] = BaseSerializer.prototype.getHashForResource.apply(this, arguments);
9+
10+
if (Array.isArray(hash)) {
11+
for (let resource of hash) {
12+
this._adjust(resource);
13+
}
14+
} else {
15+
this._adjust(hash);
16+
}
17+
18+
return [hash, addToIncludes];
19+
},
20+
21+
_adjust(hash) {
22+
delete hash.id;
23+
delete hash.token;
24+
25+
let crate = this.schema.crates.find(hash.crate_id);
26+
hash.crate_name = crate.name;
27+
28+
hash.invitee_id = Number(hash.invitee_id);
29+
hash.inviter_id = Number(hash.inviter_id);
30+
31+
let inviter = this.schema.users.find(hash.inviter_id);
32+
hash.invited_by_username = inviter.login;
33+
},
34+
});

tests/acceptance/invites-test.js

Lines changed: 41 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -12,36 +12,32 @@ module('Acceptance | /me/pending-invites', function (hooks) {
1212
setupApplicationTest(hooks);
1313

1414
function prepare(context) {
15-
let user = context.server.create('user');
16-
context.authenticateAs(user);
17-
1815
let inviter = context.server.create('user', { name: 'janed' });
1916
let inviter2 = context.server.create('user', { name: 'wycats' });
20-
context.server.get('/api/v1/me/crate_owner_invitations', function () {
21-
let users = [this.serialize(inviter, 'user').user, this.serialize(inviter2, 'user').user];
22-
23-
return {
24-
crate_owner_invitations: [
25-
{
26-
invited_by_username: 'janed',
27-
crate_name: 'nanomsg',
28-
crate_id: 42,
29-
created_at: '2016-12-24T12:34:56Z',
30-
invitee_id: parseInt(user.id, 10),
31-
inviter_id: parseInt(inviter.id, 10),
32-
},
33-
{
34-
invited_by_username: 'wycats',
35-
crate_name: 'ember-rs',
36-
crate_id: 1,
37-
created_at: '2020-12-31T12:34:56Z',
38-
invitee_id: parseInt(user.id, 10),
39-
inviter_id: parseInt(inviter2.id, 10),
40-
},
41-
],
42-
users,
43-
};
17+
18+
let user = context.server.create('user');
19+
20+
let nanomsg = context.server.create('crate', { name: 'nanomsg' });
21+
context.server.create('version', { crate: nanomsg });
22+
context.server.create('crate-owner-invitation', {
23+
crate: nanomsg,
24+
createdAt: '2016-12-24T12:34:56Z',
25+
invitee: user,
26+
inviter,
4427
});
28+
29+
let ember = context.server.create('crate', { name: 'ember-rs' });
30+
context.server.create('version', { crate: ember });
31+
context.server.create('crate-owner-invitation', {
32+
crate: ember,
33+
createdAt: '2020-12-31T12:34:56Z',
34+
invitee: user,
35+
inviter: inviter2,
36+
});
37+
38+
context.authenticateAs(user);
39+
40+
return { nanomsg, user };
4541
}
4642

4743
test('redirects to / when not logged in', async function (assert) {
@@ -76,7 +72,7 @@ module('Acceptance | /me/pending-invites', function (hooks) {
7672
test('shows empty list message', async function (assert) {
7773
prepare(this);
7874

79-
this.server.get('/api/v1/me/crate_owner_invitations', { crate_owner_invitations: [] });
75+
this.server.schema.crateOwnerInvitations.all().destroy();
8076

8177
await visit('/me/pending-invites');
8278
assert.equal(currentURL(), '/me/pending-invites');
@@ -85,19 +81,11 @@ module('Acceptance | /me/pending-invites', function (hooks) {
8581
});
8682

8783
test('invites can be declined', async function (assert) {
88-
assert.expect(9);
84+
let { nanomsg, user } = prepare(this);
8985

90-
prepare(this);
91-
92-
this.server.put('/api/v1/me/crate_owner_invitations/:crate', (schema, request) => {
93-
assert.deepEqual(request.params, { crate: '42' });
94-
95-
let body = JSON.parse(request.requestBody);
96-
assert.strictEqual(body.crate_owner_invite.accepted, false);
97-
assert.strictEqual(body.crate_owner_invite.crate_id, 42);
98-
99-
return { crate_owner_invitation: { crate_id: 42, accepted: false } };
100-
});
86+
let { crateOwnerInvitations, crateOwnerships } = this.server.schema;
87+
assert.equal(crateOwnerInvitations.where({ crateId: nanomsg.id, inviteeId: user.id }).length, 1);
88+
assert.equal(crateOwnerships.where({ crateId: nanomsg.id, userId: user.id }).length, 0);
10189

10290
await visit('/me/pending-invites');
10391
assert.equal(currentURL(), '/me/pending-invites');
@@ -110,12 +98,15 @@ module('Acceptance | /me/pending-invites', function (hooks) {
11098
.hasText('Declined. You have not been added as an owner of crate nanomsg.');
11199
assert.dom('[data-test-invite="nanomsg"] [data-test-crate-link]').doesNotExist();
112100
assert.dom('[data-test-invite="nanomsg"] [data-test-inviter-link]').doesNotExist();
101+
102+
assert.equal(crateOwnerInvitations.where({ crateId: nanomsg.id, inviteeId: user.id }).length, 0);
103+
assert.equal(crateOwnerships.where({ crateId: nanomsg.id, userId: user.id }).length, 0);
113104
});
114105

115106
test('error message is shown if decline request fails', async function (assert) {
116107
prepare(this);
117108

118-
this.server.put('/api/v1/me/crate_owner_invitations/:crate', () => new Response(500));
109+
this.server.put('/api/v1/me/crate_owner_invitations/:crate_id', () => new Response(500));
119110

120111
await visit('/me/pending-invites');
121112
assert.equal(currentURL(), '/me/pending-invites');
@@ -127,19 +118,11 @@ module('Acceptance | /me/pending-invites', function (hooks) {
127118
});
128119

129120
test('invites can be accepted', async function (assert) {
130-
assert.expect(9);
121+
let { nanomsg, user } = prepare(this);
131122

132-
prepare(this);
133-
134-
this.server.put('/api/v1/me/crate_owner_invitations/:crate', (schema, request) => {
135-
assert.deepEqual(request.params, { crate: '42' });
136-
137-
let body = JSON.parse(request.requestBody);
138-
assert.strictEqual(body.crate_owner_invite.accepted, true);
139-
assert.strictEqual(body.crate_owner_invite.crate_id, 42);
140-
141-
return { crate_owner_invitation: { crate_id: 42, accepted: true } };
142-
});
123+
let { crateOwnerInvitations, crateOwnerships } = this.server.schema;
124+
assert.equal(crateOwnerInvitations.where({ crateId: nanomsg.id, inviteeId: user.id }).length, 1);
125+
assert.equal(crateOwnerships.where({ crateId: nanomsg.id, userId: user.id }).length, 0);
143126

144127
await visit('/me/pending-invites');
145128
assert.equal(currentURL(), '/me/pending-invites');
@@ -154,12 +137,15 @@ module('Acceptance | /me/pending-invites', function (hooks) {
154137
assert.dom('[data-test-invite="nanomsg"] [data-test-inviter-link]').doesNotExist();
155138

156139
await percySnapshot(assert);
140+
141+
assert.equal(crateOwnerInvitations.where({ crateId: nanomsg.id, inviteeId: user.id }).length, 0);
142+
assert.equal(crateOwnerships.where({ crateId: nanomsg.id, userId: user.id }).length, 1);
157143
});
158144

159145
test('error message is shown if accept request fails', async function (assert) {
160146
prepare(this);
161147

162-
this.server.put('/api/v1/me/crate_owner_invitations/:crate', () => new Response(500));
148+
this.server.put('/api/v1/me/crate_owner_invitations/:crate_id', () => new Response(500));
163149

164150
await visit('/me/pending-invites');
165151
assert.equal(currentURL(), '/me/pending-invites');
@@ -176,7 +162,7 @@ module('Acceptance | /me/pending-invites', function (hooks) {
176162
let errorMessage =
177163
'The invitation to become an owner of the demo_crate crate expired. Please reach out to an owner of the crate to request a new invitation.';
178164
let payload = { errors: [{ detail: errorMessage }] };
179-
this.server.put('/api/v1/me/crate_owner_invitations/:crate', payload, 410);
165+
this.server.put('/api/v1/me/crate_owner_invitations/:crate_id', payload, 410);
180166

181167
await visit('/me/pending-invites');
182168
assert.equal(currentURL(), '/me/pending-invites');

tests/acceptance/token-invites-test.js

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { currentURL } from '@ember/test-helpers';
22
import { module, test } from 'qunit';
33

44
import percySnapshot from '@percy/ember';
5-
import Response from 'ember-cli-mirage/response';
65

76
import { setupApplicationTest } from 'cargo/tests/helpers';
87

@@ -24,13 +23,6 @@ module('Acceptance | /accept-invite/:token', function (hooks) {
2423
});
2524

2625
test('shows error for unknown token', async function (assert) {
27-
assert.expect(3);
28-
29-
this.server.put('/api/v1/me/crate_owner_invitations/accept/:token', (schema, request) => {
30-
assert.deepEqual(request.params, { token: 'unknown' });
31-
return new Response(404);
32-
});
33-
3426
await visit('/accept-invite/unknown');
3527
assert.equal(currentURL(), '/accept-invite/unknown');
3628
assert.dom('[data-test-error-message]').hasText('You may want to visit crates.io/me/pending-invites to try again.');
@@ -48,15 +40,15 @@ module('Acceptance | /accept-invite/:token', function (hooks) {
4840
});
4941

5042
test('shows success for known token', async function (assert) {
51-
assert.expect(3);
43+
let inviter = this.server.create('user');
44+
let invitee = this.server.create('user');
5245

53-
this.server.put('/api/v1/me/crate_owner_invitations/accept/:token', (schema, request) => {
54-
assert.deepEqual(request.params, { token: 'ember-rs' });
55-
return { crate_owner_invitation: { crate_id: 42, accepted: true } };
56-
});
46+
let crate = this.server.create('crate', { name: 'nanomsg' });
47+
this.server.create('version', { crate });
48+
let invite = this.server.create('crate-owner-invitation', { crate, invitee, inviter });
5749

58-
await visit('/accept-invite/ember-rs');
59-
assert.equal(currentURL(), '/accept-invite/ember-rs');
50+
await visit(`/accept-invite/${invite.token}`);
51+
assert.equal(currentURL(), `/accept-invite/${invite.token}`);
6052
assert
6153
.dom('[data-test-success-message]')
6254
.hasText(

0 commit comments

Comments
 (0)