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

[mds-agency] Feature: add versioning to agency #302

Merged
merged 23 commits into from
May 14, 2020

Conversation

janedotx
Copy link

@janedotx janedotx commented May 6, 2020

PR Checklist

  • simple searchable title - [mds-db] Add PG env var, [config] Fix eslint config
  • briefly describe the changes in this PR
  • mark as draft if should not be merged
  • write tests for all new functionality

Impacts

  • Provider
  • Agency
  • Audit
  • Policy
  • Compliance
  • Daily
  • Native
  • Policy Author

Copy link

@avatarneil avatarneil left a comment

Choose a reason for hiding this comment

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

Any changes to error responses are breaking changes, are these in conformance with the agency spec?

@mdurling
Copy link

mdurling commented May 6, 2020

Any changes to error responses are breaking changes, are these in conformance with the agency spec?

Any changes to the response envelope at all are breaking.

error_description: 'none of the provided data was valid',
result: 'no valid telemetry submitted',
failures
error: new ValidationError('none of the provided data was valid', { failures })

Choose a reason for hiding this comment

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

Ought to be providing something like

{
  error: 'invalid_data',
  error_description: 'none of the provided data was valid',
  error_details: failures
}

See the Agency Telemetry spec

packages/mds-agency/request-handlers.ts Outdated Show resolved Hide resolved
error_description: 'none of the provided data was valid',
result: 'no valid telemetry submitted',
failures: [`device_id ${data[0].device_id}: not found`]
error: new ValidationError('none of the provided data was valid.', {

Choose a reason for hiding this comment

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

This should actually be a 500 response, probably with the error being a new ServerError().

Copy link
Author

Choose a reason for hiding this comment

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

I don't want to make it a ServerError object, since the spec says it should be a string.

const start = Date.now()

const { data } = req.body
const { provider_id } = res.locals
if (!provider_id) {
res.status(400).send({
error: 'bad_param',
error_description: 'bad or missing provider_id'
error: new BadParamsError('bad or missing provider_id')

Choose a reason for hiding this comment

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

Should have a error_description field as well, considering we had it before

Choose a reason for hiding this comment

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

Also error needs to be a string of something like bad_param, see the Agency Telemetry spec

Copy link
Author

@janedotx janedotx May 6, 2020

Choose a reason for hiding this comment

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

Can we just merge this versioning one and I'll cut another PR to address all the places the agency code diverges from the spec? Some of the 200 responses also diverge from it. I'd rather handle all divergences in one PR.

@@ -254,18 +283,16 @@ export const submitVehicleEvent = async (req: AgencyApiRequest, res: AgencyApiRe
if (message.includes('duplicate')) {
logger.info(name, 'duplicate event', event.event_type)
res.status(409).send({
error: 'duplicate_event',
error_description: 'an event with this device_id and timestamp has already been received'
error: new ConflictError('an event with this device_id and timestamp has already been received')

Choose a reason for hiding this comment

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

The spec does not actually have 409 as a valid response code for the events endpoint. I recommend changing this to a 400 with a bad_param error, see the Agency Events spec

})
} else if (message.includes('not found') || message.includes('unregistered')) {
logger.info(name, 'event for unregistered', event.device_id, event.event_type)
res.status(400).send({
error: 'unregistered',
error_description: 'the specified device_id has not been registered'
error: new DependencyMissingError('the specified device_id has not been registered')

Choose a reason for hiding this comment

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

Need to retain the format of error and error_description from the Agency Events spec.

} catch (err) {
if (String(err).includes('duplicate')) {
res.status(409).send({
error: 'already_registered',
error_description: 'A vehicle with this device_id is already registered'
error: new ConflictError('A vehicle with this device_id is already registered')

Choose a reason for hiding this comment

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

Need to follow the same response format as the spec, see the Agency Vehicles spec

Comment on lines 89 to 91
if (typeof failure !== 'boolean') {
return res.status(400).send(failure)
}

Choose a reason for hiding this comment

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

What's this for?

Copy link
Author

Choose a reason for hiding this comment

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

badDevice either returns true or an error object. The error object is formatted according to the error schema spelled out in the spec. I'll tweak it so it returns null if there are no errors, and the typeof check can go away.

Comment on lines 145 to 168
it('verifies non-uuid provider_id is rejected', done => {
request
.get('/devices')
.set('Authorization', AUTH_GARBAGE_PROVIDER)
.expect(400)
.end((err, result) => {
test.value(result).hasHeader('content-type', APP_JSON)
test.string(result.body.result).contains('invalid provider_id', 'is not a UUID')
done(err)
})
})

it('verifies unknown provider_id is rejected', done => {
request
.get('/devices')
.set('Authorization', AUTH_UNKNOWN_UUID_PROVIDER)
.expect(400)
.end((err, result) => {
test.value(result).hasHeader('content-type', APP_JSON)
test.string(result.body.result).contains('invalid provider_id', 'is not a known provider')
done(err)
})
})

Choose a reason for hiding this comment

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

Why are we removing these tests?

Copy link
Author

Choose a reason for hiding this comment

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

These endpoints simply don't exist in the API.

Copy link
Author

Choose a reason for hiding this comment

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

The reason they pass at all is because the auth token is bad.

Choose a reason for hiding this comment

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

Right right, cool 👍

@@ -362,7 +334,7 @@ describe('Tests API', () => {
.expect(201)
.end((err, result) => {
log('err', err, 'body', result.body)
test.string(result.body.result).contains('success')

Choose a reason for hiding this comment

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

As per the Agency Vehicles spec, we should be returning nothing along with the 201

Copy link
Author

@janedotx janedotx May 9, 2020

Choose a reason for hiding this comment

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

Nothing but the version.

} catch (err) {
if (String(err).includes('duplicate')) {
res.status(409).send({
error: 'already_registered',
Copy link
Author

Choose a reason for hiding this comment

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

Changed since already_registered is not an error type in the spec.

Choose a reason for hiding this comment

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

already_registered is definitely an error type in the spec

Copy link
Author

Choose a reason for hiding this comment

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

ah you're right


// eslint-disable-next-line @typescript-eslint/no-floating-promises
stream.initialize()
const agencyServerError = { error: 'server_error', error_description: 'Unknown server error' }
Copy link
Author

Choose a reason for hiding this comment

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

It's not in the spec, but nothing's currently specified for 500s and this at least follows the format.

@janedotx janedotx requested a review from avatarneil May 11, 2020 22:01
} catch (err) {
if (String(err).includes('duplicate')) {
res.status(409).send({
error: 'already_registered',

Choose a reason for hiding this comment

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

already_registered is definitely an error type in the spec

Comment on lines 127 to 128
error: 'not_found',
error_description: 'Device not found'
Copy link

@avatarneil avatarneil May 13, 2020

Choose a reason for hiding this comment

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

It looks like not_found isn't even in the spec, the spec dictates we should just send an empty body for a 404 here ¯_(ツ)_/¯

Copy link
Author

Choose a reason for hiding this comment

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

/shrug indeed

packages/mds-agency/request-handlers.ts Outdated Show resolved Hide resolved
packages/mds-agency/request-handlers.ts Outdated Show resolved Hide resolved
packages/mds-agency/request-handlers.ts Outdated Show resolved Hide resolved
}

return res.status(500).send(new ServerError())
return res.status(500).send(agencyServerError)

Choose a reason for hiding this comment

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

Could just use new ServerError here too I think because we don't have the same error structure

} catch (err) {
res.status(500).send(new ServerError())
res.status(500).send(agencyServerError)

Choose a reason for hiding this comment

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

Same thing about ServerError

packages/mds-agency/request-handlers.ts Outdated Show resolved Hide resolved
try {
const stops = await db.readStops()

if (!stops) {
return res.status(404).send(new NotFoundError())
return res.status(404).send({ error: 'not_found', error_description: 'No stops were found' })

Choose a reason for hiding this comment

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

Can use a new NotFoundError here

} catch (err) {
return res.status(500).send(new ServerError())
return res.status(500).send(agencyServerError)

Choose a reason for hiding this comment

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

Same thing about ServerError

Copy link

@avatarneil avatarneil left a comment

Choose a reason for hiding this comment

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

One outstanding thing I'd definitely like resolved before merging (the special error bit), besides that in good shape

res.status(404).send({
error: 'not_found'
})
res.status(404).send({})

Choose a reason for hiding this comment

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

I think we can just do res.status(404).send() without defining an empty object

Copy link
Author

Choose a reason for hiding this comment

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

We could, but then the middleware drops charset utf-8 from the version header, which then makes it slightly different from the other endpoints.

res.status(404).send({
error: 'not_found'
})
res.status(404).send({})

Choose a reason for hiding this comment

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

Same note about sending an empty object vs none

@@ -186,22 +196,23 @@ export const updateVehicle = async (req: AgencyApiRequest, res: AgencyApiRespons
await updateVehicleFail(req, res, provider_id, device_id, 'not found')
} else {
const device = await db.updateDevice(device_id, provider_id, update)
// TODO should we warn instead of fail if the cache/stream doesn't work?

Choose a reason for hiding this comment

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

That's what we're doing, no?

result: 'no valid telemetry submitted',
failures: [`device_id ${data[0].device_id}: not found`]
res.status(500).send({
error: 'server_error',

Choose a reason for hiding this comment

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

The error_details that are special are actually inaccurate for this code path, the original error message we sent was super off, and has actually lead to some diagnostic issues in the past

@janedotx janedotx merged commit 7b01a7f into develop May 14, 2020
@marie-x marie-x deleted the feature/add_versioning_to_agency branch March 10, 2021 16:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants