diff --git a/mirage/config.js b/mirage/config.js index e0a1409e637..533e3df1bcc 100644 --- a/mirage/config.js +++ b/mirage/config.js @@ -1,6 +1,7 @@ import * as Categories from './route-handlers/categories'; import * as Crates from './route-handlers/crates'; import * as DocsRS from './route-handlers/docs-rs'; +import * as Invites from './route-handlers/invites'; import * as Keywords from './route-handlers/keywords'; import * as Me from './route-handlers/me'; import * as Session from './route-handlers/session'; @@ -12,6 +13,7 @@ export default function () { Categories.register(this); Crates.register(this); DocsRS.register(this); + Invites.register(this); Keywords.register(this); Me.register(this); Session.register(this); diff --git a/mirage/route-handlers/invites.js b/mirage/route-handlers/invites.js new file mode 100644 index 00000000000..cd231cff785 --- /dev/null +++ b/mirage/route-handlers/invites.js @@ -0,0 +1,49 @@ +import { Response } from 'ember-cli-mirage'; + +import { getSession } from '../utils/session'; +import { notFound, withMeta } from './-utils'; + +export function register(server) { + server.get('/api/private/crate_owner_invitations', function (schema, request) { + let { user } = getSession(schema); + if (!user) { + return new Response(403, {}, { errors: [{ detail: 'must be logged in to perform that action' }] }); + } + + let invites; + if (request.queryParams['crate_name']) { + let crate = schema.crates.findBy({ name: request.queryParams['crate_name'] }); + if (!crate) return notFound(); + + invites = schema.crateOwnerInvitations.where({ crateId: crate.id }); + } else if (request.queryParams['invitee_id']) { + let inviteeId = request.queryParams['invitee_id']; + if (inviteeId !== user.id) { + return new Response(403, {}, { errors: [{ detail: 'must be logged in to perform that action' }] }); + } + + invites = schema.crateOwnerInvitations.where({ inviteeId }); + } else { + return new Response(400, {}, { errors: [{ detail: 'missing or invalid filter' }] }); + } + + let perPage = 10; + let start = request.queryParams['__start__'] ?? 0; + let end = start + perPage; + + let nextPage = null; + if (invites.length > end) { + let url = new URL(request.url, 'https://crates.io'); + url.searchParams.set('__start__', end); + nextPage = url.search; + } + + invites = invites.slice(start, end); + + let response = this.serialize(invites); + response.users ??= []; + response.meta = { next_page: nextPage }; + + return response; + }); +} diff --git a/tests/mirage/private/crate-owner-invitations/get-test.js b/tests/mirage/private/crate-owner-invitations/get-test.js new file mode 100644 index 00000000000..6bb61154e5b --- /dev/null +++ b/tests/mirage/private/crate-owner-invitations/get-test.js @@ -0,0 +1,229 @@ +import { module, test } from 'qunit'; + +import fetch from 'fetch'; + +import { setupTest } from '../../../helpers'; +import setupMirage from '../../../helpers/setup-mirage'; + +module('Mirage | GET /api/private/crate_owner_invitations', function (hooks) { + setupTest(hooks); + setupMirage(hooks); + + test('happy path (invitee_id)', async function (assert) { + 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 }); + + 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.create('crate-owner-invitation', { + crate: ember, + createdAt: '2020-12-31T12:34:56Z', + invitee: user, + inviter: inviter2, + }); + + let response = await fetch(`/api/private/crate_owner_invitations?invitee_id=${user.id}`); + assert.equal(response.status, 200); + assert.deepEqual(await response.json(), { + crate_owner_invitations: [ + { + crate_id: Number(nanomsg.id), + 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: Number(ember.id), + 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: user.avatar, + id: Number(user.id), + login: user.login, + name: user.name, + url: user.url, + }, + { + 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', + }, + ], + meta: { + next_page: null, + }, + }); + }); + + test('happy path with empty response (invitee_id)', async function (assert) { + let user = this.server.create('user'); + this.server.create('mirage-session', { user }); + + let response = await fetch(`/api/private/crate_owner_invitations?invitee_id=${user.id}`); + assert.equal(response.status, 200); + assert.deepEqual(await response.json(), { + crate_owner_invitations: [], + users: [], + meta: { + next_page: null, + }, + }); + }); + + test('happy path with pagination (invitee_id)', async function (assert) { + let inviter = this.server.create('user'); + + let user = this.server.create('user'); + this.server.create('mirage-session', { user }); + + for (let i = 0; i < 15; i++) { + let crate = this.server.create('crate'); + this.server.create('version', { crate }); + this.server.create('crate-owner-invitation', { crate, invitee: user, inviter }); + } + + let response = await fetch(`/api/private/crate_owner_invitations?invitee_id=${user.id}`); + assert.equal(response.status, 200); + let responseJSON = await response.json(); + assert.strictEqual(responseJSON['crate_owner_invitations'].length, 10); + assert.ok(responseJSON.meta['next_page']); + + response = await fetch(`/api/private/crate_owner_invitations${responseJSON.meta['next_page']}`); + assert.equal(response.status, 200); + responseJSON = await response.json(); + assert.strictEqual(responseJSON['crate_owner_invitations'].length, 5); + assert.strictEqual(responseJSON.meta['next_page'], null); + }); + + test('happy path (crate_name)', async function (assert) { + 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 }); + + 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.create('crate-owner-invitation', { + crate: ember, + createdAt: '2020-12-31T12:34:56Z', + invitee: user, + inviter: inviter2, + }); + + let response = await fetch(`/api/private/crate_owner_invitations?crate_name=ember-rs`); + assert.equal(response.status, 200); + assert.deepEqual(await response.json(), { + crate_owner_invitations: [ + { + crate_id: Number(ember.id), + 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: user.avatar, + id: Number(user.id), + login: user.login, + name: user.name, + url: user.url, + }, + { + avatar: 'https://avatars1.githubusercontent.com/u/14631425?v=4', + id: Number(inviter2.id), + login: 'wycats', + name: 'wycats', + url: 'https://github.com/wycats', + }, + ], + meta: { + next_page: null, + }, + }); + }); + + test('returns 403 if unauthenticated', async function (assert) { + let response = await fetch(`/api/private/crate_owner_invitations?invitee_id=42`); + assert.equal(response.status, 403); + assert.deepEqual(await response.json(), { + errors: [{ detail: 'must be logged in to perform that action' }], + }); + }); + + test('returns 400 if query params are missing', async function (assert) { + let user = this.server.create('user'); + this.server.create('mirage-session', { user }); + + let response = await fetch(`/api/private/crate_owner_invitations`); + assert.equal(response.status, 400); + assert.deepEqual(await response.json(), { + errors: [{ detail: 'missing or invalid filter' }], + }); + }); + + test("returns 404 if crate can't be found", async function (assert) { + let user = this.server.create('user'); + this.server.create('mirage-session', { user }); + + let response = await fetch(`/api/private/crate_owner_invitations?crate_name=foo`); + assert.equal(response.status, 404); + assert.deepEqual(await response.json(), { + errors: [{ detail: 'Not Found' }], + }); + }); + + test('returns 403 if requesting for other user', async function (assert) { + let user = this.server.create('user'); + this.server.create('mirage-session', { user }); + + let response = await fetch(`/api/private/crate_owner_invitations?invitee_id=${user.id + 1}`); + assert.equal(response.status, 403); + assert.deepEqual(await response.json(), { + errors: [{ detail: 'must be logged in to perform that action' }], + }); + }); +});