Skip to content

Commit b3a65e4

Browse files
tests: add passes acceptance tests
1 parent dcf3abb commit b3a65e4

File tree

2 files changed

+259
-0
lines changed

2 files changed

+259
-0
lines changed

tests/acceptance/passes_tests.js

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

tests/test-helpers.js

+7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as chai from 'chai';
22
import nock from 'nock';
33
import * as sinon from 'sinon';
44
import sinonChai from 'sinon-chai';
5+
import { jsonWebTokenService } from '../src/infrastructure/JsonWebTokenService.js';
56

67
const expect = chai.expect;
78
chai.use(sinonChai);
@@ -17,9 +18,15 @@ afterEach(function () {
1718
nock.cleanAll();
1819
});
1920

21+
async function generateAuthorizationToken() {
22+
const token = await jsonWebTokenService.generateToken({ serialNumber: '123456' });
23+
return `ApplePass ${token}`;
24+
}
25+
2026
// eslint-disable-next-line mocha/no-exports
2127
export {
2228
expect,
29+
generateAuthorizationToken,
2330
nock,
2431
sinon,
2532
};

0 commit comments

Comments
 (0)