Skip to content

Commit

Permalink
Auto merge of #3707 - Turbo87:invites, r=locks
Browse files Browse the repository at this point in the history
mirage: Implement `crate_owner_invitations` endpoints

This should simplify implementing #2868 in the future :)
  • Loading branch information
bors committed Jun 16, 2021
2 parents 9c040cf + 9ab26af commit d547935
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 73 deletions.
18 changes: 18 additions & 0 deletions mirage/factories/crate-owner-invitation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Factory } from 'ember-cli-mirage';

export default Factory.extend({
createdAt: '2016-12-24T12:34:56Z',
token: i => `secret-token-${i}`,

afterCreate(invite) {
if (!invite.crateId) {
throw new Error(`Missing \`crate\` relationship on \`crate-owner-invitation:${invite.id}\``);
}
if (!invite.inviteeId) {
throw new Error(`Missing \`invitee\` relationship on \`crate-owner-invitation:${invite.id}\``);
}
if (!invite.inviterId) {
throw new Error(`Missing \`inviter\` relationship on \`crate-owner-invitation:${invite.id}\``);
}
},
});
7 changes: 7 additions & 0 deletions mirage/models/crate-owner-invitation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { belongsTo, Model } from 'ember-cli-mirage';

export default Model.extend({
crate: belongsTo(),
invitee: belongsTo('user'),
inviter: belongsTo('user'),
});
3 changes: 0 additions & 3 deletions mirage/models/crate-owner-invite.js

This file was deleted.

46 changes: 46 additions & 0 deletions mirage/route-handlers/me.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,50 @@ export function register(server) {

return { ok: true };
});

server.get('/api/v1/me/crate_owner_invitations', function (schema) {
let { user } = getSession(schema);
if (!user) {
return new Response(403, {}, { errors: [{ detail: 'must be logged in to perform that action' }] });
}

return schema.crateOwnerInvitations.where({ inviteeId: user.id });
});

server.put('/api/v1/me/crate_owner_invitations/:crate_id', (schema, request) => {
let { user } = getSession(schema);
if (!user) {
return new Response(403, {}, { errors: [{ detail: 'must be logged in to perform that action' }] });
}

let body = JSON.parse(request.requestBody);
let { accepted, crate_id: crateId } = body.crate_owner_invite;

let invite = schema.crateOwnerInvitations.findBy({ crateId, inviteeId: user.id });
if (!invite) {
return new Response(404);
}

if (accepted) {
server.create('crate-ownership', { crate: invite.crate, user });
}

invite.destroy();

return { crate_owner_invitation: { crate_id: crateId, accepted } };
});

server.put('/api/v1/me/crate_owner_invitations/accept/:token', (schema, request) => {
let { token } = request.params;

let invite = schema.crateOwnerInvitations.findBy({ token });
if (!invite) {
return new Response(404);
}

server.create('crate-ownership', { crate: invite.crate, user: invite.invitee });
invite.destroy();

return { crate_owner_invitation: { crate_id: invite.crateId, accepted: true } };
});
}
34 changes: 34 additions & 0 deletions mirage/serializers/crate-owner-invitation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import BaseSerializer from './application';

export default BaseSerializer.extend({
// eslint-disable-next-line ember/avoid-leaking-state-in-ember-objects
include: ['inviter'],

getHashForResource() {
let [hash, addToIncludes] = BaseSerializer.prototype.getHashForResource.apply(this, arguments);

if (Array.isArray(hash)) {
for (let resource of hash) {
this._adjust(resource);
}
} else {
this._adjust(hash);
}

return [hash, addToIncludes];
},

_adjust(hash) {
delete hash.id;
delete hash.token;

let crate = this.schema.crates.find(hash.crate_id);
hash.crate_name = crate.name;

hash.invitee_id = Number(hash.invitee_id);
hash.inviter_id = Number(hash.inviter_id);

let inviter = this.schema.users.find(hash.inviter_id);
hash.invited_by_username = inviter.login;
},
});
96 changes: 41 additions & 55 deletions tests/acceptance/invites-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,32 @@ module('Acceptance | /me/pending-invites', function (hooks) {
setupApplicationTest(hooks);

function prepare(context) {
let user = context.server.create('user');
context.authenticateAs(user);

let inviter = context.server.create('user', { name: 'janed' });
let inviter2 = context.server.create('user', { name: 'wycats' });
context.server.get('/api/v1/me/crate_owner_invitations', function () {
let users = [this.serialize(inviter, 'user').user, this.serialize(inviter2, 'user').user];

return {
crate_owner_invitations: [
{
invited_by_username: 'janed',
crate_name: 'nanomsg',
crate_id: 42,
created_at: '2016-12-24T12:34:56Z',
invitee_id: parseInt(user.id, 10),
inviter_id: parseInt(inviter.id, 10),
},
{
invited_by_username: 'wycats',
crate_name: 'ember-rs',
crate_id: 1,
created_at: '2020-12-31T12:34:56Z',
invitee_id: parseInt(user.id, 10),
inviter_id: parseInt(inviter2.id, 10),
},
],
users,
};

let user = context.server.create('user');

let nanomsg = context.server.create('crate', { name: 'nanomsg' });
context.server.create('version', { crate: nanomsg });
context.server.create('crate-owner-invitation', {
crate: nanomsg,
createdAt: '2016-12-24T12:34:56Z',
invitee: user,
inviter,
});

let ember = context.server.create('crate', { name: 'ember-rs' });
context.server.create('version', { crate: ember });
context.server.create('crate-owner-invitation', {
crate: ember,
createdAt: '2020-12-31T12:34:56Z',
invitee: user,
inviter: inviter2,
});

context.authenticateAs(user);

return { nanomsg, user };
}

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

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

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

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

prepare(this);

this.server.put('/api/v1/me/crate_owner_invitations/:crate', (schema, request) => {
assert.deepEqual(request.params, { crate: '42' });

let body = JSON.parse(request.requestBody);
assert.strictEqual(body.crate_owner_invite.accepted, false);
assert.strictEqual(body.crate_owner_invite.crate_id, 42);

return { crate_owner_invitation: { crate_id: 42, accepted: false } };
});
let { crateOwnerInvitations, crateOwnerships } = this.server.schema;
assert.equal(crateOwnerInvitations.where({ crateId: nanomsg.id, inviteeId: user.id }).length, 1);
assert.equal(crateOwnerships.where({ crateId: nanomsg.id, userId: user.id }).length, 0);

await visit('/me/pending-invites');
assert.equal(currentURL(), '/me/pending-invites');
Expand All @@ -110,12 +98,15 @@ module('Acceptance | /me/pending-invites', function (hooks) {
.hasText('Declined. You have not been added as an owner of crate nanomsg.');
assert.dom('[data-test-invite="nanomsg"] [data-test-crate-link]').doesNotExist();
assert.dom('[data-test-invite="nanomsg"] [data-test-inviter-link]').doesNotExist();

assert.equal(crateOwnerInvitations.where({ crateId: nanomsg.id, inviteeId: user.id }).length, 0);
assert.equal(crateOwnerships.where({ crateId: nanomsg.id, userId: user.id }).length, 0);
});

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

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

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

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

prepare(this);

this.server.put('/api/v1/me/crate_owner_invitations/:crate', (schema, request) => {
assert.deepEqual(request.params, { crate: '42' });

let body = JSON.parse(request.requestBody);
assert.strictEqual(body.crate_owner_invite.accepted, true);
assert.strictEqual(body.crate_owner_invite.crate_id, 42);

return { crate_owner_invitation: { crate_id: 42, accepted: true } };
});
let { crateOwnerInvitations, crateOwnerships } = this.server.schema;
assert.equal(crateOwnerInvitations.where({ crateId: nanomsg.id, inviteeId: user.id }).length, 1);
assert.equal(crateOwnerships.where({ crateId: nanomsg.id, userId: user.id }).length, 0);

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

await percySnapshot(assert);

assert.equal(crateOwnerInvitations.where({ crateId: nanomsg.id, inviteeId: user.id }).length, 0);
assert.equal(crateOwnerships.where({ crateId: nanomsg.id, userId: user.id }).length, 1);
});

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

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

await visit('/me/pending-invites');
assert.equal(currentURL(), '/me/pending-invites');
Expand All @@ -176,7 +162,7 @@ module('Acceptance | /me/pending-invites', function (hooks) {
let errorMessage =
'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.';
let payload = { errors: [{ detail: errorMessage }] };
this.server.put('/api/v1/me/crate_owner_invitations/:crate', payload, 410);
this.server.put('/api/v1/me/crate_owner_invitations/:crate_id', payload, 410);

await visit('/me/pending-invites');
assert.equal(currentURL(), '/me/pending-invites');
Expand Down
22 changes: 7 additions & 15 deletions tests/acceptance/token-invites-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { currentURL } from '@ember/test-helpers';
import { module, test } from 'qunit';

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

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

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

test('shows error for unknown token', async function (assert) {
assert.expect(3);

this.server.put('/api/v1/me/crate_owner_invitations/accept/:token', (schema, request) => {
assert.deepEqual(request.params, { token: 'unknown' });
return new Response(404);
});

await visit('/accept-invite/unknown');
assert.equal(currentURL(), '/accept-invite/unknown');
assert.dom('[data-test-error-message]').hasText('You may want to visit crates.io/me/pending-invites to try again.');
Expand All @@ -48,15 +40,15 @@ module('Acceptance | /accept-invite/:token', function (hooks) {
});

test('shows success for known token', async function (assert) {
assert.expect(3);
let inviter = this.server.create('user');
let invitee = this.server.create('user');

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

await visit('/accept-invite/ember-rs');
assert.equal(currentURL(), '/accept-invite/ember-rs');
await visit(`/accept-invite/${invite.token}`);
assert.equal(currentURL(), `/accept-invite/${invite.token}`);
assert
.dom('[data-test-success-message]')
.hasText(
Expand Down
Loading

0 comments on commit d547935

Please sign in to comment.