Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apple Wallet: Implement support for the lastUpdated tag (#75) #86

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ describe('Get the List of Updatable Passes', () => {
it('error when wrong request method (POST instead of GET)', () => {
cy.request({
method: 'POST',
url: '/api/apple/v1/devices/b33e3a3dccb3030333e3333da33333a3/registrations/pass.org.passport.nation3',
url: '/api/apple/v1/devices/cypress_b33e3a3dccb3030333e3333da33333a3/registrations/pass.org.passport.nation3',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(400)
Expand All @@ -21,29 +21,79 @@ describe('Get the List of Updatable Passes', () => {
})
})

/**
* When the passesUpdatedSince parameter is not included, expect all serial numbers for the device to be returned.
*
* To check the expected return value, run this SQL command:
* select serial_number from registrations where device_library_identifier = 'cypress_b33e3a3dccb3030333e3333da33333a3';
*/
it('200 when existing deviceLibraryIdentifier', () => {
cy.request({
method: 'GET',
url: '/api/apple/v1/devices/cypress_b33e3a3dccb3030333e3333da33333a3/registrations/pass.org.passport.nation3',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(200)

// Expected format: "serialNumbers":["333"]
expect(JSON.stringify(response.body)).to.contain('serialNumbers')
// TODO: verify serial number value(s)
expect(response.body.serialNumbers.length).to.eq(1)
expect(response.body.serialNumbers[0]).to.eq('333')

// Expected format: "lastUpdated":"1663899405"
expect(JSON.stringify(response.body)).to.contain('lastUpdated')
expect(response.body.lastUpdated.length).to.eq(10)
const lastUpdated = new Date(response.body.lastUpdated)
expect(lastUpdated.getTime() >= 1663899405*1000) // >= 2022-09-23 02:16:45 AM
})
})

it('200 when existing deviceLibraryIdentifier and passesUpdatedSince=1662541136', () => {
/**
* Expect HTTP 200 OK when the passesUpdatedSince value is smaller than the
* timestamp of the most recent update in the `latest_updates` database table.
*
* Epoch timestamp 1046662413 = 2003-03-03 03:33:33 AM
*/
it('200 when existing deviceLibraryIdentifier and passesUpdatedSince=1046662413', () => {
cy.request({
method: 'GET',
url: '/api/apple/v1/devices/cypress_b33e3a3dccb3030333e3333da33333a3/registrations/pass.org.passport.nation3?passesUpdatedSince=1662541136',
url: '/api/apple/v1/devices/cypress_b33e3a3dccb3030333e3333da33333a3/registrations/pass.org.passport.nation3?passesUpdatedSince=1046662413',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(200)

// Expected format: "serialNumbers":["333"]
expect(JSON.stringify(response.body)).to.contain('serialNumbers')
// TODO: verify serial number value(s)
expect(response.body.serialNumbers.length).to.eq(1)
expect(response.body.serialNumbers[0]).to.eq('333')

// Expected format: "lastUpdated":"1663899405"
expect(JSON.stringify(response.body)).to.contain('lastUpdated')
expect(response.body.lastUpdated.length).to.eq(10)
const lastUpdated = new Date(response.body.lastUpdated)
expect(lastUpdated.getTime() > 1046662413*1000) // >= 2003-03-03 03:33:33 AM
})
})

/**
* Expect HTTP 204 No Content when the passesUpdatedSince value is equal to or greather than the
* timestamp of the most recent update in the `latest_updates` database table.
*
* When the passesUpdatedSince parameter is included, it is expected that serial numbers
* only be returned if the passes have not already been updated.
*
* Using an epoch timestamp that is far into the future will prevent this test from failing
* in the future once we add more rows to the `latest_updates` table.
*
* Epoch timestamp 1993433613 = 2033-03-03 03:33:33 AM
*/
it('204 when existing deviceLibraryIdentifier and passesUpdatedSince=1993433613', () => {
cy.request({
method: 'GET',
url: '/api/apple/v1/devices/cypress_b33e3a3dccb3030333e3333da33333a3/registrations/pass.org.passport.nation3?passesUpdatedSince=1993433613',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(204)
})
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ describe('Register a Pass for Update Notifications', () => {
})
})

/**
* Note: For this test to work, a matching device_library_identifier must already exist in the database table.
*/
it('error when deviceLibraryIdentifier is already registered', () => {
cy.request({
method: 'POST',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,33 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) {
error: 'Internal Server Error: ' + latest_updates_result.error.message
})
} else {
// Return matching passes (serial numbers) and their modification time
// Convert from ISO string to Date
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up above you use .single and .limit(1) I do not think you need both, single will blow up if there is not 1 result, and will only return 1 result.

Copy link
Member Author

@aahna-ashina aahna-ashina Oct 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@johnmark13 Yes, I agree this seems redundant. However, without including .limit(1), it fails:

'Results contain 12 rows, application/vnd.pgrst.object+json requires 1 row'

I followed the code example in the documentation at https://supabase.com/docs/reference/javascript/single

const latestUpdateDate: Date = new Date(latest_updates_result.data['time'])
res.status(200).json({
serialNumbers: serialNumbers,
lastUpdated: String(Math.round(latestUpdateDate.getTime() / 1000))
})
console.log('latestUpdateDate:', latestUpdateDate)

if (!passesUpdatedSince) {
// The passes on this device have not been updated previously, so return all passes.
res.status(200).json({
serialNumbers: serialNumbers,
lastUpdated: String(Math.round(latestUpdateDate.getTime() / 1000))
})
} else {
// The passes on this device have been updated previously, so only return passes that
// were updated before the most recent Nation3 update in the `latest_updates` database table.

// Convert from epoch timestamp string to Date
const passesUpdatedSinceDate: Date = new Date(Number(passesUpdatedSince) * 1000)
console.log('passesUpdatedSinceDate:', passesUpdatedSinceDate)

if (passesUpdatedSinceDate.getTime() < latestUpdateDate.getTime()) {
res.status(200).json({
serialNumbers: serialNumbers,
lastUpdated: String(Math.round(latestUpdateDate.getTime() / 1000))
})
} else {
res.status(204).end()
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll give the same feedback as ever, for me there are too many branches of code in here, and too many different return paths that make it tricky for the reader to follow what the code is doing. I would pull out some methods with names making it clear on the purpose and havea simple helper for the / 1000 and * 1000 code to avoid tripping up.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@johnmark13 Good feedback, thank you. Will fix 😀

Copy link
Member Author

@aahna-ashina aahna-ashina Oct 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@johnmark13 Just to get the functional solution deployed, I'm merging this PR. And I added your refactoring suggestions to #76. I'm working on those now, in a separate branch/PR.

}
})
}
Expand Down