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
Refactor Dredd.js, improve integration tests coverage, drop Node 6 support #1317
Conversation
eba7565
to
b67c6b5
Compare
@@ -1,21 +1,89 @@ | |||
const async = require('async'); |
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 code of this file still seems ugly to me at many places and I blame mainly the asynchronous parts. I hope with async/await/promises it could get even more readable and unit-testable in the future.
const resolveModule = require('./resolveModule'); | ||
const logger = require('./logger'); | ||
const TransactionRunner = require('./TransactionRunner'); | ||
const { applyConfiguration } = require('./configuration'); | ||
|
||
|
||
const FILE_DOWNLOAD_TIMEOUT = 5000; | ||
function prefixErrors(decoratedCallback, prefix) { |
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 couldn't come up with a nicer way how to prefix errors with context than decorating the callback like this. I might be just missing a good idea or a best practise. But it's gonna disappear with async/await/promises anyway.
} | ||
|
||
|
||
function readLocations(locations, options, callback) { |
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 feel like readLocations() and parseContent() might get a lot slimmer or even redundant with Promise.all() or Promise.map(), but I decided to stay with callbacks until Dredd drops Node 6 support.
} | ||
|
||
this.logger.debug('Resolving API descriptions locations'); | ||
prepareAPIdescriptions(callback) { |
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 prepareAPIdescriptions() method isn't a real method of a real object as the Dredd class doesn't need to be a class in the first place. I created it just to make run() shorter. It roughly contains the pipeline turning API description locations into API descriptions, then parsing them, and then compiling them into transactions. Once we have async/await/promises and the code is less tied with 'this.configuration', it might make sense to refactor this further.
Is the provided path correct? | ||
`); | ||
return callback(err); | ||
run(callback) { |
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 Dredd class represents an object with just a single (asynchronous...) method, run(). That's an antipattern and a clear indication it doesn't need to be a class at all. It is interconnected by references with reporters though and it's Dredd's public JavaScript API, so I'm leaving it as a class for now. Also for now ignoring the fact that it calls the callback with both error and the stats (fixing it would be a breaking change). In the future I can imagine even something like const stats = await dredd(config);
.map(transaction => (Object.assign({ apiDescriptionMediaType: compileResult.mediaType }, transaction))); | ||
next(); | ||
this.logger.debug('Configuring reporters'); | ||
configureReporters(this.configuration, this.stats, this.transactionRunner); |
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 configureReporters() is a rabbit hole of side effects. Leaving it as it is for now.
this.configuration.apiDescriptions = apiDescriptions; | ||
|
||
// TODO https://github.com/apiaryio/dredd/issues/1191 | ||
const annotationsError = handleRuntimeProblems(apiDescriptions, this.logger); |
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 want to get rid of handleRuntimeProblems() as the next step after this PR, finally unblocking upgrading Dredd Transactions.
lib/Dredd.js
Outdated
try { | ||
return compile(mediaType, apiElements, location); | ||
} catch (error) { | ||
error.message = ( |
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.
If you separate the decoration logic from the prefixError
so it doesn't include a callback dispatch, you can reuse it here.
const decorateError = (error, prefix) => {
error.message = `${prefix}: ${error.message}`
return error
}
const prefixError = (...) => {
// ...
decodateError(...)
// handle callbacks and stuff
}
// compileTransactions
// ...
} catch (error) {
decorateError(error, 'Unable to compile ...')
}
lib/Dredd.js
Outdated
this.files = resolveLocations(this.configuration.custom.cwd, this.configuration.options.path); | ||
} catch (err) { | ||
callback(err, this.stats); | ||
locations = resolveLocations(this.configuration.custom.cwd, this.configuration.options.path); |
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 need to time the merges of this and configuration pull request. I vote for merging configuration updates first, since they didn't touch any actual logic in Dredd, or other files, but simply removed the .options.
nesting from internal usage. It would be much more simple to remove options nesting than ensure the logic introduced by your pull request is not lost.
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 agree, let's merge yours first. Also, your PR is semantically unreleasable, but this is a breaking change, so if yours goes first, my will release both.
const status = parseInt(response.status, 10); | ||
if ((status < 200) || (status >= 300)) { | ||
skip = true; | ||
} | ||
} | ||
delete transaction.apiDescriptionMediaType; | ||
delete transaction.apiDescription; |
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.
Is deleting the whole description intended?
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.
Yes. It is currently passed along only for the purpose of checking the media type above. I have future plans with it, so I keep it as an object, but currently it is not further used anywhere else, so I'm removing it immediately after it's used, to prevent it to leak into hooks etc.
lib/resolveLocations.js
Outdated
// flattens the array of arrays | ||
.reduce((flatArray, array) => flatArray.concat(array), []) | ||
// removes duplicates | ||
.filter((location, index, array) => !array.slice(0, index).includes(location)); |
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 use new Set()
to achieve the same output?
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.
Sure π€¦ββοΈ That's even how resolvePaths()
does it...
assert.instanceOf(dreddRuntimeInfo.err, Error); | ||
}); | ||
it('passes expected stats to the callback', () => { | ||
assert.hasAllKeys(dreddRuntimeInfo.stats, [ |
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 separate the list of Dredd's stats keys into a enum?
@artem-zakharchenko Thanks for the review! π I'll add a few commits to address your comments and then, if you approve, let's block this PR until yours gets merged. |
BREAKING CHANGE: The 'fileBasedReporters' property has been in the 'stats' object by accident and now is removed. Apparently it has been there for a while, as it got listed in #599, the only documentation of the 'stats' object to this date.
0ac9b7c
to
d3d77c3
Compare
I rebased this against master. As this is going to be a breaking change again because of 59702d4, I'll add a commit dropping Node 6 support, to lower the frequency of major Dredd releases per day π |
Allowing for #1275 BREAKING CHANGE: Dredd does not support Node 6 anymore. Node 6 has a scheduled maintenance end on April 2019.
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.
Looks good to me. Thank you for the changes.
π This PR is included in version 11.0.0 π The release is available on: Your semantic-release bot π¦π |
π Why this change?
I didn't like most of the
Dredd.js
file. This should streamline the pipeline from API description locations to parse results and transactions. The refactoring prepares ground for #1191. With #1275 it could be further refactored and streamlined.I still feel like I did this, but I hope it is still improvement in gradually isolating stuff.
π Related issues and Pull Requests
β What didn't I forget?
npm run lint