Skip to content

Commit 207ff68

Browse files
tests: add passes acceptance tests
1 parent dcf3abb commit 207ff68

File tree

4 files changed

+268
-0
lines changed

4 files changed

+268
-0
lines changed

config.js

+2
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ function buildConfiguration() {
6464
};
6565
if (config.environment === 'test') {
6666
config.logging.enabled = false;
67+
config.secret = 'SECRET_FOR_TESTS';
68+
config.pass.passTypeIdentifier = 'pass-identifier';
6769
}
6870

6971
if (!verifyConfig(config)) {

src/infrastructure/Browser.js

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { env } from 'node:process';
12
import puppeteer from 'puppeteer';
23

34
export class Browser {
@@ -7,6 +8,9 @@ export class Browser {
78
}
89

910
static async create() {
11+
if (env.NODE_ENV === 'test') {
12+
return;
13+
}
1014
const browser = await puppeteer.launch({ headless: true });
1115
const page = await browser.newPage();
1216
const customUA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:127.0) Gecko/20100101 Firefox/127.0';

tests/acceptance/passes_tests.js

+255
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
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+
});

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)