Repository: arb/celebrate (js)

Issue:
Subsequent calls no longer validating on upgrade to v11
- `node` version - 12.9.1
- `celebrate` version - 11.0.0-rc2
- `joi` version (via `npm ls --depth=0 | grep joi`) - 16.1.7

My `joi` schema looks like this:
```js
exports.getAllProjectParametersSchema = {
  query: Joi.object({
    limit: Joi.number()
      .integer()
      .default(50),
    start: Joi.number()
      .integer()
      .default(0),
    sort: Joi.string()
      .valid(
        'asset_name',
        'asset_name:asc',
        'asset_name:desc',
        'updated_by',
        'updated_by:asc',
        'updated_by:desc',
      )
      .default('updated_at:desc')
  }),
  params: Joi.object({
    project_id: Joi.number()
      .integer()
      .required()
  })
};
```

Here is an example value that is not working as expected:
Route being checked: 
```js
projectParameterRouter
  .route('/api/projects/:project_id/parameters')
  .get(celebrate(getAllProjectParametersSchema), getAllProjectParameters)
  .all(methodNotAllowed);
```
Example api call for which celebrate should set the default queries:
```js
'/api/projects/1/parameters'
```

The issue I am having with `celebrate` is:

The code was working ok in v10.1.0. I upgraded it to allow use of joi-v16 and the failover() method to set the queries to predefined values if they don't meet the schema. 
However, I'm running into validation issues before changing any of the code.
I'm using Jest to test the api and for the first value in the test the default values are updating req.query. However, all subsequent tests in the same run do not have their default values updated and do not appear to be getting validated. Equally all tests when run in isolation work as expected.
The same happens when running the api locally - the first call to the endpoint works, but subsequent calls do not (as per the tests).
I don't think there is an issue with the schema vs the upgraded joi version as its validating the first request ok.
Any ideas what would be causing this? Thanks.

Pull request description:
Per request `steps`
Fixes #147

In trying to be clever, I ended up introducing a bug where multiple
request would reuse the same Promises, so they wouldn't not run
after the first request.

Patch:
diff --git a/lib/index.js b/lib/index.js
index 89f9316..fa13e42 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -89,16 +89,16 @@ exports.celebrate = (schema, joiOpts = {}, celebrateOptions = {}) => {
   result = celebrateSchema.validate(celebrateOptions);
   Assert.ifError(result.error);
 
-  // We want a fresh copy of steps since `internals.check` mutates the array
-  const steps = [];
-  internals.REQ_VALIDATIONS.forEach((validateFn, segment) => {
-    const spec = schema[segment];
-    if (spec) {
-      steps.push(validateFn(segment, Joi.compile(spec)));
-    }
-  });
-
   const middleware = (req, res, next) => {
+    // We want a fresh copy of steps since `internals.check` mutates the array
+    const steps = [];
+    internals.REQ_VALIDATIONS.forEach((validateFn, segment) => {
+      const spec = schema[segment];
+      if (spec) {
+        steps.push(validateFn(segment, Joi.compile(spec)));
+      }
+    });
+
     const finalConfig = celebrateOptions.reqContext ? {
       ...joiOpts,
       context: req,


Test patch:
diff --git a/test/integration.test.js b/test/integration.test.js
index 4d4c426..7ba213d 100644
--- a/test/integration.test.js
+++ b/test/integration.test.js
@@ -356,3 +356,72 @@ describe('reqContext', () => {
     expect(next).not.toHaveBeenCalled();
   });
 });
+
+describe('multiple-runs', () => {
+  test('continues to set default values', () => {
+    expect.assertions(10);
+    const server = Server();
+
+    server.get('/', celebrate({
+      [Segments.HEADERS]: {
+        accept: Joi.string().regex(/json/),
+        'secret-header': Joi.string().default('@@@@@@'),
+      },
+    }, {
+      allowUnknown: true,
+    }), (req, res) => {
+      delete req.headers.host; // this can change computer to computer, so just remove it
+      res.send(req.headers);
+    });
+
+    const attempts = Array.from({ length: 10 }, () => new Promise((resolve) => server.inject({
+      method: 'GET',
+      url: '/',
+      headers: {
+        accept: 'application/json',
+      },
+    }, (r) => {
+      resolve(JSON.parse(r.payload));
+    })));
+
+    return Promise.all(attempts).then((v) => {
+      v.forEach((headers) => {
+        expect(headers).toEqual({
+          accept: 'application/json',
+          'user-agent': 'shot',
+          'secret-header': '@@@@@@',
+        });
+      });
+    });
+  });
+
+  test('continues to validate values', () => {
+    expect.assertions(10);
+    const server = Server();
+
+    server.post('/', celebrate({
+      [Segments.BODY]: {
+        name: Joi.string().required(),
+      },
+    }));
+
+    const attempts = Array.from({ length: 10 }, () => new Promise((resolve) => server.inject({
+      method: 'POST',
+      url: '/',
+      payload: {
+        age: random.number(),
+      },
+      headers: {
+        accept: 'application/json',
+      },
+    }, (r) => {
+      resolve(r.statusCode);
+    })));
+
+    return Promise.all(attempts).then((v) => {
+      v.forEach((statusCode) => {
+        expect(statusCode).toEqual(500);
+      });
+    });
+  });
+});