|
| 1 | +import dayjs from 'dayjs'; |
| 2 | +import { knex } from '../../db/knex-database-connection.js'; |
| 3 | +import { createServer } from '../../server.js'; |
| 4 | +import { passController } from '../../src/application/index.js'; |
| 5 | +import { authService } from '../../src/infrastructure/AuthService.js'; |
| 6 | +import { expect, generateAuthorizationToken, sinon } from '../test-helpers.js'; |
| 7 | + |
| 8 | +describe('Acceptance | Endpoints | Passes', function () { |
| 9 | + let server; |
| 10 | + |
| 11 | + beforeEach(async function () { |
| 12 | + passController.passAdapter = { |
| 13 | + get: async () => {}, |
| 14 | + }; |
| 15 | + server = await createServer({ passController, authService }); |
| 16 | + await knex('reservations').delete(); |
| 17 | + await knex('registrations').delete(); |
| 18 | + await knex('devices').delete(); |
| 19 | + await knex('passes').delete(); |
| 20 | + await knex('passes').insert({ passTypeIdentifier: 'passId', serialNumber: '12345' }); |
| 21 | + }); |
| 22 | + |
| 23 | + afterEach(async function () { |
| 24 | + await knex('reservations').delete(); |
| 25 | + await knex('registrations').delete(); |
| 26 | + await knex('devices').delete(); |
| 27 | + await knex('passes').delete(); |
| 28 | + }); |
| 29 | + |
| 30 | + describe('Register pass', function () { |
| 31 | + context('when registration does not exist', function () { |
| 32 | + it('should save device and register pass', async function () { |
| 33 | + const deviceLibraryIdentifier = 'deviceId'; |
| 34 | + const passTypeIdentifier = 'passId'; |
| 35 | + const serialNumber = '12345'; |
| 36 | + const token = await generateAuthorizationToken(); |
| 37 | + |
| 38 | + const response = await server.inject({ |
| 39 | + method: 'POST', |
| 40 | + url: `/v1/devices/${deviceLibraryIdentifier}/registrations/${passTypeIdentifier}/${serialNumber}`, |
| 41 | + headers: { authorization: token }, |
| 42 | + payload: { |
| 43 | + pushToken: 'push-token', |
| 44 | + }, |
| 45 | + }); |
| 46 | + |
| 47 | + expect(response.statusCode).to.equal(201); |
| 48 | + const savedRegistrations = await knex('registrations') |
| 49 | + .select('deviceLibraryIdentifier', 'passTypeIdentifier', 'serialNumber') |
| 50 | + .where({ deviceLibraryIdentifier, passTypeIdentifier, serialNumber }) |
| 51 | + .first(); |
| 52 | + expect(savedRegistrations).to.be.deep.equal({ deviceLibraryIdentifier, passTypeIdentifier, serialNumber }); |
| 53 | + }); |
| 54 | + }); |
| 55 | + |
| 56 | + context('when registration exists', function () { |
| 57 | + it('should do nothing', async function () { |
| 58 | + const deviceLibraryIdentifier = 'deviceId'; |
| 59 | + const passTypeIdentifier = 'passId'; |
| 60 | + const serialNumber = '12345'; |
| 61 | + const pushToken = 'push-token'; |
| 62 | + const token = await generateAuthorizationToken(); |
| 63 | + |
| 64 | + await knex('devices').insert({ deviceLibraryIdentifier, pushToken }); |
| 65 | + await knex('registrations').insert({ deviceLibraryIdentifier, passTypeIdentifier, serialNumber }); |
| 66 | + |
| 67 | + const response = await server.inject({ |
| 68 | + method: 'POST', |
| 69 | + url: `/v1/devices/${deviceLibraryIdentifier}/registrations/${passTypeIdentifier}/${serialNumber}`, |
| 70 | + headers: { authorization: token }, |
| 71 | + payload: { |
| 72 | + pushToken, |
| 73 | + }, |
| 74 | + }); |
| 75 | + |
| 76 | + expect(response.statusCode).to.equal(200); |
| 77 | + }); |
| 78 | + }); |
| 79 | + }); |
| 80 | + |
| 81 | + describe('Unregister pass', function () { |
| 82 | + it('should unregister pass', async function () { |
| 83 | + const deviceLibraryIdentifier = 'deviceId'; |
| 84 | + const passTypeIdentifier = 'passId'; |
| 85 | + const serialNumber = '12345'; |
| 86 | + const anotherSerialNumber = '6789'; |
| 87 | + const pushToken = 'push-token'; |
| 88 | + const token = await generateAuthorizationToken(); |
| 89 | + |
| 90 | + await knex('passes').insert({ passTypeIdentifier, serialNumber: anotherSerialNumber }); |
| 91 | + await knex('devices').insert({ deviceLibraryIdentifier, pushToken }); |
| 92 | + await knex('registrations').insert({ deviceLibraryIdentifier, passTypeIdentifier, serialNumber }); |
| 93 | + await knex('registrations').insert({ deviceLibraryIdentifier, passTypeIdentifier, serialNumber: anotherSerialNumber }); |
| 94 | + |
| 95 | + const response = await server.inject({ |
| 96 | + method: 'DELETE', |
| 97 | + url: `/v1/devices/${deviceLibraryIdentifier}/registrations/${passTypeIdentifier}/${serialNumber}`, |
| 98 | + headers: { authorization: token }, |
| 99 | + }); |
| 100 | + |
| 101 | + expect(response.statusCode).to.equal(200); |
| 102 | + const deletedRegistration = await knex('registrations').where({ deviceLibraryIdentifier, passTypeIdentifier, serialNumber }); |
| 103 | + expect(deletedRegistration).to.have.lengthOf(0); |
| 104 | + |
| 105 | + const keptRegistrations = await knex('registrations').where({ deviceLibraryIdentifier, passTypeIdentifier }); |
| 106 | + expect(keptRegistrations).to.have.lengthOf(1); |
| 107 | + }); |
| 108 | + |
| 109 | + context('when there is the last device registration', function () { |
| 110 | + it('should also remove device', async function () { |
| 111 | + const deviceLibraryIdentifier = 'deviceId'; |
| 112 | + const passTypeIdentifier = 'passId'; |
| 113 | + const serialNumber = '12345'; |
| 114 | + const pushToken = 'push-token'; |
| 115 | + const token = await generateAuthorizationToken(); |
| 116 | + |
| 117 | + await knex('devices').insert({ deviceLibraryIdentifier, pushToken }); |
| 118 | + await knex('registrations').insert({ deviceLibraryIdentifier, passTypeIdentifier, serialNumber }); |
| 119 | + |
| 120 | + const response = await server.inject({ |
| 121 | + method: 'DELETE', |
| 122 | + url: `/v1/devices/${deviceLibraryIdentifier}/registrations/${passTypeIdentifier}/${serialNumber}`, |
| 123 | + headers: { authorization: token }, |
| 124 | + }); |
| 125 | + |
| 126 | + expect(response.statusCode).to.equal(200); |
| 127 | + const deletedRegistration = await knex('registrations').where({ deviceLibraryIdentifier, passTypeIdentifier, serialNumber }); |
| 128 | + expect(deletedRegistration).to.have.lengthOf(0); |
| 129 | + |
| 130 | + const deletedDevices = await knex('devices').where({ deviceLibraryIdentifier, pushToken }); |
| 131 | + expect(deletedDevices).to.have.lengthOf(0); |
| 132 | + }); |
| 133 | + }); |
| 134 | + }); |
| 135 | + |
| 136 | + describe('Get Updatable Passes', function () { |
| 137 | + context('when it is the first call', function () { |
| 138 | + it('should return updatable pass without passesUpdatedSince', async function () { |
| 139 | + const deviceLibraryIdentifier = 'deviceId'; |
| 140 | + const passTypeIdentifier = 'passId'; |
| 141 | + const serialNumber = '12345'; |
| 142 | + const pushToken = 'push-token'; |
| 143 | + |
| 144 | + await knex('devices').insert({ deviceLibraryIdentifier, pushToken }); |
| 145 | + await knex('registrations').insert({ deviceLibraryIdentifier, passTypeIdentifier, serialNumber }); |
| 146 | + |
| 147 | + const response = await server.inject({ |
| 148 | + method: 'GET', |
| 149 | + url: `/v1/devices/${deviceLibraryIdentifier}/registrations/${passTypeIdentifier}`, |
| 150 | + }); |
| 151 | + |
| 152 | + expect(response.statusCode).to.equal(204); |
| 153 | + }); |
| 154 | + }); |
| 155 | + |
| 156 | + context('when it is not the first time', function () { |
| 157 | + let now; |
| 158 | + let clock; |
| 159 | + |
| 160 | + beforeEach(function () { |
| 161 | + now = new Date('2024-01-01'); |
| 162 | + clock = sinon.useFakeTimers({ now, toFake: ['Date'] }); |
| 163 | + }); |
| 164 | + |
| 165 | + afterEach(function () { |
| 166 | + clock.restore(); |
| 167 | + }); |
| 168 | + |
| 169 | + it('should return updatable pass based on passesUpdatedSince', async function () { |
| 170 | + const deviceLibraryIdentifier = 'deviceId'; |
| 171 | + const passTypeIdentifier = 'passId'; |
| 172 | + const serialNumber = '12345'; |
| 173 | + const pushToken = 'push-token'; |
| 174 | + |
| 175 | + await knex('passes').update({ updated_at: new Date('2024-01-02') }).where({ passTypeIdentifier, serialNumber }); |
| 176 | + |
| 177 | + await knex('devices').insert({ deviceLibraryIdentifier, pushToken }); |
| 178 | + await knex('registrations').insert({ deviceLibraryIdentifier, passTypeIdentifier, serialNumber }); |
| 179 | + |
| 180 | + const passesUpdatedSince = dayjs('2024-01-01').unix(); |
| 181 | + |
| 182 | + const response = await server.inject({ |
| 183 | + method: 'GET', |
| 184 | + url: `/v1/devices/${deviceLibraryIdentifier}/registrations/${passTypeIdentifier}?passesUpdatedSince=${passesUpdatedSince}`, |
| 185 | + }); |
| 186 | + |
| 187 | + expect(response.statusCode).to.equal(200); |
| 188 | + expect(response.result).to.deep.equal({ serialNumbers: ['12345'], lastUpdated: '1704153600' }); |
| 189 | + }); |
| 190 | + }); |
| 191 | + }); |
| 192 | + |
| 193 | + describe('Get updated pass', function () { |
| 194 | + it('should return updated pass', async function () { |
| 195 | + const passTypeIdentifier = 'passId'; |
| 196 | + const serialNumber = '12345'; |
| 197 | + const token = await generateAuthorizationToken(); |
| 198 | + |
| 199 | + const nextEvent = '123'; |
| 200 | + |
| 201 | + await knex('passes').update({ nextEvent }).where({ passTypeIdentifier, serialNumber }); |
| 202 | + await knex('reservations').insert({ code: nextEvent, start_at: new Date('2024-01-10'), court: '10', activity: 'Badminton', status: 'reserved', updated_at: new Date('2024-01-02') }); |
| 203 | + |
| 204 | + const response = await server.inject({ |
| 205 | + method: 'GET', |
| 206 | + url: `/v1/passes/${passTypeIdentifier}/${serialNumber}`, |
| 207 | + headers: { authorization: token }, |
| 208 | + }); |
| 209 | + |
| 210 | + expect(response.statusCode).to.equal(200); |
| 211 | + const { 'content-type': contentType, 'last-updated': lastUpdated } = response.headers; |
| 212 | + expect(contentType).to.equal('application/vnd.apple.pkpass'); |
| 213 | + expect(lastUpdated).to.equal('Tue, 02, Jan, 2024 00:00:00 GMT'); |
| 214 | + }); |
| 215 | + }); |
| 216 | + |
| 217 | + describe('Create passe', function () { |
| 218 | + context('when next event exists', function () { |
| 219 | + let now; |
| 220 | + let clock; |
| 221 | + |
| 222 | + beforeEach(function () { |
| 223 | + now = new Date('2024-01-01'); |
| 224 | + clock = sinon.useFakeTimers({ now, toFake: ['Date'] }); |
| 225 | + }); |
| 226 | + |
| 227 | + afterEach(function () { |
| 228 | + clock.restore(); |
| 229 | + }); |
| 230 | + |
| 231 | + it('should return pass', async function () { |
| 232 | + await knex('reservations').insert({ code: '12345', start_at: new Date('2024-01-10'), court: '10', activity: 'Badminton', status: 'reserved', updated_at: new Date('2024-01-02') }); |
| 233 | + |
| 234 | + const response = await server.inject({ |
| 235 | + method: 'GET', |
| 236 | + url: '/pass', |
| 237 | + }); |
| 238 | + |
| 239 | + expect(response.statusCode).to.equal(201); |
| 240 | + expect(response.headers['content-type']).to.equal('application/vnd.apple.pkpass'); |
| 241 | + }); |
| 242 | + }); |
| 243 | + |
| 244 | + context('when next event does not exist', function () { |
| 245 | + it('should return 503', async function () { |
| 246 | + const response = await server.inject({ |
| 247 | + method: 'GET', |
| 248 | + url: '/pass', |
| 249 | + }); |
| 250 | + |
| 251 | + expect(response.statusCode).to.equal(503); |
| 252 | + }); |
| 253 | + }); |
| 254 | + }); |
| 255 | +}); |
0 commit comments