-
Notifications
You must be signed in to change notification settings - Fork 9
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
Conversation
…on field is in response body.
There was a problem hiding this 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?
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 }) |
There was a problem hiding this comment.
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
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.', { |
There was a problem hiding this comment.
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()
.
There was a problem hiding this comment.
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') |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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') |
There was a problem hiding this comment.
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') |
There was a problem hiding this comment.
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') |
There was a problem hiding this comment.
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
if (typeof failure !== 'boolean') { | ||
return res.status(400).send(failure) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's this for?
There was a problem hiding this comment.
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.
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) | ||
}) | ||
}) | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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') |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing but the version.
…' into feature/add_versioning_to_agency
} catch (err) { | ||
if (String(err).includes('duplicate')) { | ||
res.status(409).send({ | ||
error: 'already_registered', |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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' } |
There was a problem hiding this comment.
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.
} catch (err) { | ||
if (String(err).includes('duplicate')) { | ||
res.status(409).send({ | ||
error: 'already_registered', |
There was a problem hiding this comment.
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
error: 'not_found', | ||
error_description: 'Device not found' |
There was a problem hiding this comment.
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 ¯_(ツ)_/¯
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/shrug indeed
} | ||
|
||
return res.status(500).send(new ServerError()) | ||
return res.status(500).send(agencyServerError) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same thing about ServerError
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' }) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same thing about ServerError
There was a problem hiding this 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({}) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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({}) |
There was a problem hiding this comment.
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? |
There was a problem hiding this comment.
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', |
There was a problem hiding this comment.
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
PR Checklist
[mds-db] Add PG env var
,[config] Fix eslint config
Impacts