From 90956073e8e549b12bb9a1b58bbfe90ef6eec8bc Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Sat, 12 Jun 2021 16:03:00 +0200 Subject: [PATCH 1/7] mirage: Add tests for `GET /api/v1/me/crate_owner_invitations` endpoint These tests are a little pointless because they test the route handler implementation in the exact same test, but they will allow us to refactor those route handlers into generic implementations using proper factories --- tests/mirage/invitations-test.js | 101 +++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 tests/mirage/invitations-test.js diff --git a/tests/mirage/invitations-test.js b/tests/mirage/invitations-test.js new file mode 100644 index 0000000000..471d97206b --- /dev/null +++ b/tests/mirage/invitations-test.js @@ -0,0 +1,101 @@ +import { module, test } from 'qunit'; + +import fetch from 'fetch'; +import timekeeper from 'timekeeper'; + +import { setupTest } from 'cargo/tests/helpers'; + +import setupMirage from '../helpers/setup-mirage'; + +module('Mirage | Crate Owner Invitations', function (hooks) { + setupTest(hooks); + setupMirage(hooks); + + module('GET /api/v1/me/crate_owner_invitations', function () { + test('empty case', async function (assert) { + this.server.get('/api/v1/me/crate_owner_invitations', { crate_owner_invitations: [] }); + + let response = await fetch('/api/v1/me/crate_owner_invitations'); + assert.equal(response.status, 200); + + let responsePayload = await response.json(); + assert.deepEqual(responsePayload, { crate_owner_invitations: [] }); + }); + + test('returns a paginated crates list', async function (assert) { + timekeeper.freeze(new Date('2016-12-24T12:34:56Z')); + + let user = this.server.create('user'); + this.server.create('mirage-session', { user }); + + let inviter = this.server.create('user', { name: 'janed' }); + let inviter2 = this.server.create('user', { name: 'wycats' }); + this.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 response = await fetch('/api/v1/me/crate_owner_invitations'); + assert.equal(response.status, 200); + + let responsePayload = await response.json(); + assert.deepEqual(responsePayload, { + crate_owner_invitations: [ + { + crate_id: 42, + crate_name: 'nanomsg', + created_at: '2016-12-24T12:34:56Z', + invited_by_username: 'janed', + invitee_id: Number(user.id), + inviter_id: Number(inviter.id), + }, + { + crate_id: 1, + crate_name: 'ember-rs', + created_at: '2020-12-31T12:34:56Z', + invited_by_username: 'wycats', + invitee_id: Number(user.id), + inviter_id: Number(inviter2.id), + }, + ], + users: [ + { + avatar: 'https://avatars1.githubusercontent.com/u/14631425?v=4', + id: Number(inviter.id), + login: 'janed', + name: 'janed', + url: 'https://github.com/janed', + }, + { + avatar: 'https://avatars1.githubusercontent.com/u/14631425?v=4', + id: Number(inviter2.id), + login: 'wycats', + name: 'wycats', + url: 'https://github.com/wycats', + }, + ], + }); + }); + }); +}); From 5fb65dfad01e34572a97b5c5ac52375bb1082e4c Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Sat, 12 Jun 2021 16:04:53 +0200 Subject: [PATCH 2/7] mirage: Add empty `GET /api/v1/me/crate_owner_invitations` route handler --- mirage/route-handlers/me.js | 2 ++ tests/mirage/invitations-test.js | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mirage/route-handlers/me.js b/mirage/route-handlers/me.js index 8739915af5..91570cc36f 100644 --- a/mirage/route-handlers/me.js +++ b/mirage/route-handlers/me.js @@ -98,4 +98,6 @@ export function register(server) { return { ok: true }; }); + + server.get('/api/v1/me/crate_owner_invitations', { crate_owner_invitations: [] }); } diff --git a/tests/mirage/invitations-test.js b/tests/mirage/invitations-test.js index 471d97206b..00b5512e78 100644 --- a/tests/mirage/invitations-test.js +++ b/tests/mirage/invitations-test.js @@ -13,8 +13,6 @@ module('Mirage | Crate Owner Invitations', function (hooks) { module('GET /api/v1/me/crate_owner_invitations', function () { test('empty case', async function (assert) { - this.server.get('/api/v1/me/crate_owner_invitations', { crate_owner_invitations: [] }); - let response = await fetch('/api/v1/me/crate_owner_invitations'); assert.equal(response.status, 200); From a7b71a9d5bfdb6a498e6bc6510b5349422db32c8 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Sat, 12 Jun 2021 16:06:43 +0200 Subject: [PATCH 3/7] mirage: Add authentication check to `GET /api/v1/me/crate_owner_invitations` route handler --- mirage/route-handlers/me.js | 9 ++++++++- tests/mirage/invitations-test.js | 13 +++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/mirage/route-handlers/me.js b/mirage/route-handlers/me.js index 91570cc36f..e9cb007cf6 100644 --- a/mirage/route-handlers/me.js +++ b/mirage/route-handlers/me.js @@ -99,5 +99,12 @@ export function register(server) { return { ok: true }; }); - server.get('/api/v1/me/crate_owner_invitations', { crate_owner_invitations: [] }); + 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 { crate_owner_invitations: [] }; + }); } diff --git a/tests/mirage/invitations-test.js b/tests/mirage/invitations-test.js index 00b5512e78..fcbb8f46d6 100644 --- a/tests/mirage/invitations-test.js +++ b/tests/mirage/invitations-test.js @@ -13,6 +13,9 @@ module('Mirage | Crate Owner Invitations', function (hooks) { module('GET /api/v1/me/crate_owner_invitations', function () { test('empty case', async function (assert) { + let user = this.server.create('user'); + this.server.create('mirage-session', { user }); + let response = await fetch('/api/v1/me/crate_owner_invitations'); assert.equal(response.status, 200); @@ -95,5 +98,15 @@ module('Mirage | Crate Owner Invitations', function (hooks) { ], }); }); + + test('returns an error if unauthenticated', async function (assert) { + let response = await fetch('/api/v1/me/crate_owner_invitations'); + assert.equal(response.status, 403); + + let responsePayload = await response.json(); + assert.deepEqual(responsePayload, { + errors: [{ detail: 'must be logged in to perform that action' }], + }); + }); }); }); From 6241941915869b98810acc87f646db75ee970871 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Sat, 12 Jun 2021 16:15:13 +0200 Subject: [PATCH 4/7] mirage/invitations: Add `create('crate')` calls --- tests/mirage/invitations-test.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/mirage/invitations-test.js b/tests/mirage/invitations-test.js index fcbb8f46d6..f21d1bf7eb 100644 --- a/tests/mirage/invitations-test.js +++ b/tests/mirage/invitations-test.js @@ -26,6 +26,12 @@ module('Mirage | Crate Owner Invitations', function (hooks) { test('returns a paginated crates list', async function (assert) { timekeeper.freeze(new Date('2016-12-24T12:34:56Z')); + let nanomsg = this.server.create('crate', { name: 'nanomsg' }); + this.server.create('version', { crate: nanomsg }); + + let ember = this.server.create('crate', { name: 'ember-rs' }); + this.server.create('version', { crate: ember }); + let user = this.server.create('user'); this.server.create('mirage-session', { user }); @@ -38,16 +44,16 @@ module('Mirage | Crate Owner Invitations', function (hooks) { crate_owner_invitations: [ { invited_by_username: 'janed', - crate_name: 'nanomsg', - crate_id: 42, + crate_name: nanomsg.name, + crate_id: nanomsg.id, 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, + crate_name: ember.name, + crate_id: ember.id, created_at: '2020-12-31T12:34:56Z', invitee_id: parseInt(user.id, 10), inviter_id: parseInt(inviter2.id, 10), @@ -64,7 +70,7 @@ module('Mirage | Crate Owner Invitations', function (hooks) { assert.deepEqual(responsePayload, { crate_owner_invitations: [ { - crate_id: 42, + crate_id: nanomsg.id, crate_name: 'nanomsg', created_at: '2016-12-24T12:34:56Z', invited_by_username: 'janed', @@ -72,7 +78,7 @@ module('Mirage | Crate Owner Invitations', function (hooks) { inviter_id: Number(inviter.id), }, { - crate_id: 1, + crate_id: ember.id, crate_name: 'ember-rs', created_at: '2020-12-31T12:34:56Z', invited_by_username: 'wycats', From b3e27ff27249059e8d131eb9baec6abcb42c85b8 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Sat, 12 Jun 2021 16:40:40 +0200 Subject: [PATCH 5/7] mirage: Replace `GET /me/crate_owner_invitations` route handlers with generic implementation --- mirage/factories/crate-owner-invitation.js | 17 +++++ mirage/models/crate-owner-invitation.js | 7 ++ mirage/models/crate-owner-invite.js | 3 - mirage/route-handlers/me.js | 2 +- mirage/serializers/crate-owner-invitation.js | 33 +++++++++ tests/acceptance/invites-test.js | 74 +++++++++----------- tests/mirage/invitations-test.js | 41 ++++------- 7 files changed, 106 insertions(+), 71 deletions(-) create mode 100644 mirage/factories/crate-owner-invitation.js create mode 100644 mirage/models/crate-owner-invitation.js delete mode 100644 mirage/models/crate-owner-invite.js create mode 100644 mirage/serializers/crate-owner-invitation.js diff --git a/mirage/factories/crate-owner-invitation.js b/mirage/factories/crate-owner-invitation.js new file mode 100644 index 0000000000..cfe6968741 --- /dev/null +++ b/mirage/factories/crate-owner-invitation.js @@ -0,0 +1,17 @@ +import { Factory } from 'ember-cli-mirage'; + +export default Factory.extend({ + createdAt: '2016-12-24T12:34:56Z', + + 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}\``); + } + }, +}); diff --git a/mirage/models/crate-owner-invitation.js b/mirage/models/crate-owner-invitation.js new file mode 100644 index 0000000000..ec84c11d4d --- /dev/null +++ b/mirage/models/crate-owner-invitation.js @@ -0,0 +1,7 @@ +import { belongsTo, Model } from 'ember-cli-mirage'; + +export default Model.extend({ + crate: belongsTo(), + invitee: belongsTo('user'), + inviter: belongsTo('user'), +}); diff --git a/mirage/models/crate-owner-invite.js b/mirage/models/crate-owner-invite.js deleted file mode 100644 index 770b50936d..0000000000 --- a/mirage/models/crate-owner-invite.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Model } from 'ember-cli-mirage'; - -export default Model.extend({}); diff --git a/mirage/route-handlers/me.js b/mirage/route-handlers/me.js index e9cb007cf6..46a5c88110 100644 --- a/mirage/route-handlers/me.js +++ b/mirage/route-handlers/me.js @@ -105,6 +105,6 @@ export function register(server) { return new Response(403, {}, { errors: [{ detail: 'must be logged in to perform that action' }] }); } - return { crate_owner_invitations: [] }; + return schema.crateOwnerInvitations.where({ inviteeId: user.id }); }); } diff --git a/mirage/serializers/crate-owner-invitation.js b/mirage/serializers/crate-owner-invitation.js new file mode 100644 index 0000000000..97b5d92e68 --- /dev/null +++ b/mirage/serializers/crate-owner-invitation.js @@ -0,0 +1,33 @@ +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; + + 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; + }, +}); diff --git a/tests/acceptance/invites-test.js b/tests/acceptance/invites-test.js index cf5a0ac1ce..9b45eddda3 100644 --- a/tests/acceptance/invites-test.js +++ b/tests/acceptance/invites-test.js @@ -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 }; } test('redirects to / when not logged in', async function (assert) { @@ -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'); @@ -87,14 +83,14 @@ module('Acceptance | /me/pending-invites', function (hooks) { test('invites can be declined', async function (assert) { assert.expect(9); - prepare(this); + let { nanomsg } = prepare(this); - this.server.put('/api/v1/me/crate_owner_invitations/:crate', (schema, request) => { - assert.deepEqual(request.params, { crate: '42' }); + this.server.put('/api/v1/me/crate_owner_invitations/:crate_id', (schema, request) => { + assert.deepEqual(request.params, { crate_id: nanomsg.id }); let body = JSON.parse(request.requestBody); assert.strictEqual(body.crate_owner_invite.accepted, false); - assert.strictEqual(body.crate_owner_invite.crate_id, 42); + assert.strictEqual(body.crate_owner_invite.crate_id, nanomsg.id); return { crate_owner_invitation: { crate_id: 42, accepted: false } }; }); @@ -115,7 +111,7 @@ module('Acceptance | /me/pending-invites', function (hooks) { 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'); @@ -129,14 +125,14 @@ module('Acceptance | /me/pending-invites', function (hooks) { test('invites can be accepted', async function (assert) { assert.expect(9); - prepare(this); + let { nanomsg } = prepare(this); - this.server.put('/api/v1/me/crate_owner_invitations/:crate', (schema, request) => { - assert.deepEqual(request.params, { crate: '42' }); + this.server.put('/api/v1/me/crate_owner_invitations/:crate_id', (schema, request) => { + assert.deepEqual(request.params, { crate_id: nanomsg.id }); let body = JSON.parse(request.requestBody); assert.strictEqual(body.crate_owner_invite.accepted, true); - assert.strictEqual(body.crate_owner_invite.crate_id, 42); + assert.strictEqual(body.crate_owner_invite.crate_id, nanomsg.id); return { crate_owner_invitation: { crate_id: 42, accepted: true } }; }); @@ -159,7 +155,7 @@ module('Acceptance | /me/pending-invites', function (hooks) { 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'); @@ -176,7 +172,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'); diff --git a/tests/mirage/invitations-test.js b/tests/mirage/invitations-test.js index f21d1bf7eb..491a95991b 100644 --- a/tests/mirage/invitations-test.js +++ b/tests/mirage/invitations-test.js @@ -1,7 +1,6 @@ import { module, test } from 'qunit'; import fetch from 'fetch'; -import timekeeper from 'timekeeper'; import { setupTest } from 'cargo/tests/helpers'; @@ -23,9 +22,7 @@ module('Mirage | Crate Owner Invitations', function (hooks) { assert.deepEqual(responsePayload, { crate_owner_invitations: [] }); }); - test('returns a paginated crates list', async function (assert) { - timekeeper.freeze(new Date('2016-12-24T12:34:56Z')); - + test('returns the list of invitations for the authenticated user', async function (assert) { let nanomsg = this.server.create('crate', { name: 'nanomsg' }); this.server.create('version', { crate: nanomsg }); @@ -36,31 +33,19 @@ module('Mirage | Crate Owner Invitations', function (hooks) { this.server.create('mirage-session', { user }); let inviter = this.server.create('user', { name: 'janed' }); + this.server.create('crate-owner-invitation', { + crate: nanomsg, + createdAt: '2016-12-24T12:34:56Z', + invitee: user, + inviter, + }); + let inviter2 = this.server.create('user', { name: 'wycats' }); - this.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.name, - crate_id: nanomsg.id, - 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.name, - crate_id: ember.id, - created_at: '2020-12-31T12:34:56Z', - invitee_id: parseInt(user.id, 10), - inviter_id: parseInt(inviter2.id, 10), - }, - ], - users, - }; + this.server.create('crate-owner-invitation', { + crate: ember, + createdAt: '2020-12-31T12:34:56Z', + invitee: user, + inviter: inviter2, }); let response = await fetch('/api/v1/me/crate_owner_invitations'); From e80631810e0bd6e7d4384ee56ed293cef2ce544d Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Sat, 12 Jun 2021 21:34:39 +0200 Subject: [PATCH 6/7] mirage: Implement generic `PUT /me/crate_owner_invitations/:crate_id` route handler --- mirage/route-handlers/me.js | 23 ++++++++++++++++++ tests/acceptance/invites-test.js | 40 ++++++++++++-------------------- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/mirage/route-handlers/me.js b/mirage/route-handlers/me.js index 46a5c88110..ba58b13863 100644 --- a/mirage/route-handlers/me.js +++ b/mirage/route-handlers/me.js @@ -107,4 +107,27 @@ export function register(server) { 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 } }; + }); } diff --git a/tests/acceptance/invites-test.js b/tests/acceptance/invites-test.js index 9b45eddda3..1d2513d8bf 100644 --- a/tests/acceptance/invites-test.js +++ b/tests/acceptance/invites-test.js @@ -37,7 +37,7 @@ module('Acceptance | /me/pending-invites', function (hooks) { context.authenticateAs(user); - return { nanomsg }; + return { nanomsg, user }; } test('redirects to / when not logged in', async function (assert) { @@ -81,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); - let { nanomsg } = prepare(this); - - this.server.put('/api/v1/me/crate_owner_invitations/:crate_id', (schema, request) => { - assert.deepEqual(request.params, { crate_id: nanomsg.id }); - - let body = JSON.parse(request.requestBody); - assert.strictEqual(body.crate_owner_invite.accepted, false); - assert.strictEqual(body.crate_owner_invite.crate_id, nanomsg.id); - - 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'); @@ -106,6 +98,9 @@ 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) { @@ -123,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); - let { nanomsg } = prepare(this); - - this.server.put('/api/v1/me/crate_owner_invitations/:crate_id', (schema, request) => { - assert.deepEqual(request.params, { crate_id: nanomsg.id }); - - let body = JSON.parse(request.requestBody); - assert.strictEqual(body.crate_owner_invite.accepted, true); - assert.strictEqual(body.crate_owner_invite.crate_id, nanomsg.id); - - 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'); @@ -150,6 +137,9 @@ 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) { From 9ab26af135e59312e4c387f08920634cdf52fc7c Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Sat, 12 Jun 2021 21:42:41 +0200 Subject: [PATCH 7/7] mirage: Implement generic `PUT /me/crate_owner_invitations/accept/:token` route handler --- mirage/factories/crate-owner-invitation.js | 1 + mirage/route-handlers/me.js | 14 +++++++++++++ mirage/serializers/crate-owner-invitation.js | 1 + tests/acceptance/token-invites-test.js | 22 +++++++------------- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/mirage/factories/crate-owner-invitation.js b/mirage/factories/crate-owner-invitation.js index cfe6968741..cbc4d43ef6 100644 --- a/mirage/factories/crate-owner-invitation.js +++ b/mirage/factories/crate-owner-invitation.js @@ -2,6 +2,7 @@ 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) { diff --git a/mirage/route-handlers/me.js b/mirage/route-handlers/me.js index ba58b13863..b5dde2731e 100644 --- a/mirage/route-handlers/me.js +++ b/mirage/route-handlers/me.js @@ -130,4 +130,18 @@ export function register(server) { 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 } }; + }); } diff --git a/mirage/serializers/crate-owner-invitation.js b/mirage/serializers/crate-owner-invitation.js index 97b5d92e68..61c13dd4d0 100644 --- a/mirage/serializers/crate-owner-invitation.js +++ b/mirage/serializers/crate-owner-invitation.js @@ -20,6 +20,7 @@ export default BaseSerializer.extend({ _adjust(hash) { delete hash.id; + delete hash.token; let crate = this.schema.crates.find(hash.crate_id); hash.crate_name = crate.name; diff --git a/tests/acceptance/token-invites-test.js b/tests/acceptance/token-invites-test.js index a8eb6f5bc5..448631e89e 100644 --- a/tests/acceptance/token-invites-test.js +++ b/tests/acceptance/token-invites-test.js @@ -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'; @@ -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.'); @@ -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(