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

Add calling next for Adding next() call to prepareOutput middleware [… #388

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

bamidei
Copy link

@bamidei bamidei commented Sep 25, 2018

#337]

This fixes an issue whereby any middleware registered to occur after would not be called. The specific situation which we have which triggers this is a call to server.on('after', ) where server is a restify server instance. The specified middleware will never be called without this fix.

I notice that you have specific unit tests to ensure that next is not called from prepareOutput. If you would like to not merge this PR instead of changing those tests, could you please explain the reasoning for this? Also, the latest version removed the next parameter from being passed to the postProcess optional parameter which prevents an easy work-around for this not being called in prepareOutput.

@sgilroy
Copy link

sgilroy commented Sep 26, 2018

@bamidei It might be helpful to include more details about the test failures and a link to the tests themselves.

  1) prepareOutput
       calls outputFn with default options and no post* middleware:
     expected spy to not have been called but was called once
    spy() at callback (/home/travis/build/florianholzapfel/express-restify-mongoose/src/middleware/prepareOutput.js:60:9)
  AssertError: expected spy to not have been called but was called once
      spy() at callback (src/middleware/prepareOutput.js:60:9)
      at Object.fail (node_modules/sinon/lib/sinon/assert.js:110:29)
      at failAssertion (node_modules/sinon/lib/sinon/assert.js:69:24)
      at Object.assert.(anonymous function) [as notCalled] (node_modules/sinon/lib/sinon/assert.js:94:21)
      at Context.it (test/unit/middleware/prepareOutput.js:39:18)
  2) prepareOutput
       calls outputFn with default options and no post* middleware (async):
     expected spy to not have been called but was called once
    spy() at callback (/home/travis/build/florianholzapfel/express-restify-mongoose/src/middleware/prepareOutput.js:60:9)
  AssertError: expected spy to not have been called but was called once
      spy() at callback (src/middleware/prepareOutput.js:60:9)
      at Object.fail (node_modules/sinon/lib/sinon/assert.js:110:29)
      at failAssertion (node_modules/sinon/lib/sinon/assert.js:69:24)
      at Object.assert.(anonymous function) [as notCalled] (node_modules/sinon/lib/sinon/assert.js:94:21)
      at Context.it (test/unit/middleware/prepareOutput.js:58:18)
  3) prepareOutput
       calls postProcess with default options and no post* middleware:
     expected spy to not have been called but was called once
    spy() at callback (/home/travis/build/florianholzapfel/express-restify-mongoose/src/middleware/prepareOutput.js:57:11)
  AssertError: expected spy to not have been called but was called once
      spy() at callback (src/middleware/prepareOutput.js:57:11)
      at Object.fail (node_modules/sinon/lib/sinon/assert.js:110:29)
      at failAssertion (node_modules/sinon/lib/sinon/assert.js:69:24)
      at Object.assert.(anonymous function) [as notCalled] (node_modules/sinon/lib/sinon/assert.js:94:21)
      at Context.it (test/unit/middleware/prepareOutput.js:80:18)

I agree that it isn't clear why the tests include sinon.assert.notCalled(next) instead of sinon.assert.calledOnce(next). Perhaps add a commit with this change?

@bamidei
Copy link
Author

bamidei commented Sep 26, 2018

@sgilroy Thank you, Scott. Yes, those are the tests that I think should change and happy to do it. @Zertz, please let me know if you'd like me to adjust the tests to calledOnce as Scott mentioned or if you believe these tests and corresponding behavior should remain unchanged.

Thanks!

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.7%) to 97.025% when pulling 976b396 on bamidei:call-next-from-prepareOutput into f8b1e79 on florianholzapfel:master.

@bamidei
Copy link
Author

bamidei commented Oct 5, 2018

@sgilroy @Zertz I've done some work on the unit and integration tests and have done a bit more research on the underlying question of whether next() should be called or not.

It is my conclusion that, unfortunately, Express and Restify frameworks have different expectations/requirements around calling next() in this case. For Restify, it is required to call next() and for Express it is not required/recommended and actually causes problems. I've updated the change to reflect this - using the config.restify option which is already present to determine whether or not to call next() from prepareOutput middleware.

A few additional notes:

  1. The unit tests were not properly handling the async function callback. It is necessary to wait for the promise to resolve before performing the sinon assertions. I've done this in a way that works without adding additional frameworks to the project. However, to be more robust, I recommend adding the Chai assertion library which has better handling for testing code with promises which can make the tests a bit easier to read and maintain.

  2. Some external links which provide insights into my conclusion that Restify should have next() called.

  • A closed PR which someone had made to 'fix' Restify so that next() was not required in order to ensure 'after' middleware was invoked. This PR was rejected by the maintainer as not correct.
  • Restify documentation concerning next() usage. Note that, in their documentation, they state: 'Calling next() will move to the next function in the chain.
    Unlike other REST frameworks, calling res.send() does not trigger next() automatically. In many applications, work can continue to happen after res.send(), so flushing the response is not synonymous with completion of a request.'
  • A stack overflow post which highlights the exact issue which we are witnessing with the after middleware not being called. See the last post in the chain which I'll quote here: 'Another issue (currently) is that the Server after event is not emitted if all handlers don't call next. For example, if you are using auditLogger to log requests using the after event, you won't get logs for any requests that reach a handler that doesn't call next.
    I have opened a PR to fix the issue so that the after event is emitted either way, but calling next is the expected norm for apps using restify.'
    -- (The PR mentioned there chained to the one above which was closed as incorrect.)

@@ -49,10 +49,20 @@ module.exports = function (options, excludedMap) {
if (options.postProcess) {
if (promise && typeof promise.then === 'function') {
promise.then(() => {
options.postProcess(req, res)
options.postProcess(req, res);
if (options.restify) {
Copy link
Owner

Choose a reason for hiding this comment

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

Codacy has a fix for the issue: Replace ············ with ··············

Suggested change
if (options.restify) {
if (options.restify) {

sinon.assert.notCalled(onError)
sinon.assert.calledOnce(next)
done()
});
Copy link
Owner

Choose a reason for hiding this comment

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

Codacy has a fix for the issue: Delete ;

Suggested change
});
})

options.postProcess(req, res)
options.postProcess(req, res);
if (options.restify) {
next();
Copy link
Owner

Choose a reason for hiding this comment

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

Codacy has a fix for the issue: Replace next(); with ··next()

Suggested change
next();
next()

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