[
  {
    "path": ".eslintrc",
    "content": "{\n  \"env\": {\n    \"es2021\": true\n  },\n  \"extends\": [\"eslint:recommended\", \"plugin:@typescript-eslint/recommended\"],\n  \"overrides\": [\n    {\n      \"env\": {\n        \"mocha\": true,\n        \"node\": true\n      },\n      \"files\": [\"test/**/*.js\"],\n      \"plugins\": [\"mocha\"]\n    }\n  ],\n  \"parser\": \"@typescript-eslint/parser\",\n  \"parserOptions\": {\n    \"ecmaVersion\": 2021,\n    \"sourceType\": \"module\"\n  },\n  \"plugins\": [\"@typescript-eslint\"],\n  \"rules\": {\n    \"@typescript-eslint/no-unused-vars\": [\n      \"error\",\n      { \"ignoreRestSiblings\": true }\n    ]\n  }\n}\n"
  },
  {
    "path": ".github/workflows/node.js.yml",
    "content": "name: Test\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        mongodb-version: [\"4.4\", \"5.0\", \"6.0\", \"7.0\"]\n        node-version: [16.x, 18.x, 20.x, 21.x]\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          cache: \"yarn\"\n          node-version: ${{ matrix.node-version }}\n      - uses: supercharge/mongodb-github-action@1.6.0\n      - run: yarn --frozen-lockfile\n      - run: yarn lint\n      - run: yarn tsc\n      - run: yarn build\n      - run: yarn test\n"
  },
  {
    "path": ".github/workflows/npm-publish.yml",
    "content": "name: Publish\n\non:\n  release:\n    types: [created]\n\npermissions:\n  id-token: write\n  contents: read\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        mongodb-version: [\"4.4\", \"5.0\", \"6.0\", \"7.0\"]\n        node-version: [16.x, 18.x, 20.x, 21.x]\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          cache: \"yarn\"\n          node-version: ${{ matrix.node-version }}\n      - uses: supercharge/mongodb-github-action@1.6.0\n      - run: yarn --frozen-lockfile\n      - run: yarn lint\n      - run: yarn tsc\n      - run: yarn build\n      - run: yarn test\n\n  publish:\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          registry-url: https://registry.npmjs.org/\n      - run: npm install -g npm@latest\n      - run: yarn --frozen-lockfile\n      - run: yarn build\n      - run: npm publish\n"
  },
  {
    "path": ".gitignore",
    "content": "dist\nnode_modules\n"
  },
  {
    "path": ".swcrc",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/swcrc\",\n  \"jsc\": {\n    \"parser\": {\n      \"syntax\": \"typescript\"\n    },\n    \"target\": \"es2020\"\n  }\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll\": true,\n    \"source.organizeImports\": true\n  },\n  \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n  \"editor.formatOnSave\": true,\n  \"typescript.tsdk\": \"node_modules/typescript/lib\"\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n### 7.0.0\n\n> **This release requires mongoose 6.x**\n\n- updated mongoose to version 6.x\n\n### 6.0.0\n\n> **This release requires mongoose ~5.8**\n\n- updated mongoose to version 5.8\n\n### 5.0.0\n\n- dropped support for Node 4 and added support for Node 10\n- removed query operator parsing [#285](https://github.com/florianholzapfel/express-restify-mongoose/issues/285)\n- moved request query in req.erm.query [#299](https://github.com/florianholzapfel/express-restify-mongoose/issues/299) [#353](https://github.com/florianholzapfel/express-restify-mongoose/issues/353)\n- removed `next` from postProcess [#334](https://github.com/florianholzapfel/express-restify-mongoose/issues/334)\n- added error when skip and/or limit is not a valid integer\n- removed `_id` tinkering [#326](https://github.com/florianholzapfel/express-restify-mongoose/issues/326)\n- removed dependency on `async`\n\n### 4.3.0\n\n- added support for async `outputFn` by returning a Promise\n\n### 4.2.2\n\n- removed dependency on `lodash`, use specific modules and native methods when possible [#352](https://github.com/florianholzapfel/express-restify-mongoose/pull/352)\n\n### 4.2.1\n\n- fixed issue [#294](https://github.com/florianholzapfel/express-restify-mongoose/issues/294)\n\n### 4.2.0\n\n- removed `compile` step, code now runs natively on Node 4+ and `babel` is only used for coverage\n\n### 4.1.1\n\n- fixed `distinct` queries when `options.totalCountHeader` is enabled\n\n### 4.1.0\n\n- improved sync error handling in `buildQuery` by wrapping in a promise\n- fixed crash when `distinct` and `sort` operators were used in the same query\n\n### 4.0.0\n\n- improved default error middleware by serializing error objects and removing stack traces\n- fixed Mongoose async middleware error propagation\n- fixed requests to always set `req.erm.statusCode`\n- removed `statusCode` from error object and response body\n- removed undocumented `outputFn` parameter, use `req.erm.result` and `req.erm.statusCode`\n\n### 3.2.0\n\n- added an option to disable regex operations ([#195](https://github.com/florianholzapfel/express-restify-mongoose/issues/195))\n- fixed queries with an `idProperty` resulting in a `CastError` to return `404` instead of `400` ([#184](https://github.com/florianholzapfel/express-restify-mongoose/issues/184))\n- fixed query parser to handle geospatial operators ([#187](https://github.com/florianholzapfel/express-restify-mongoose/issues/187))\n\n### 3.1.0\n\n- critical security fix with the `distinct` operator, see [issue #252](https://github.com/florianholzapfel/express-restify-mongoose/issues/252) for details\n\n### 3.0.0\n\n- ported source to ES2015, compiled and published as ES5 with Babel\n- document filtering is now done right before output allowing access to the full document in post middleware\n- removed `options.lowercase` and `options.plural`, use `options.name = require('inflection').pluralize('modelName').toLowerCase()`\n\n### 2.0.0\n\n- changed `serve` to no longer returns an Express 4 router, now returns the resource's base path (ie.: `/api/v1/Customer`)\n- changed `options.private` and `options.protected` to no longer accept comma separated fields, pass an array instead\n- removed `options.excluded`, use `options.private`\n- removed support for querying directly with query parameters, use `url?query={\"name\":\"hello\"}`\n- removed $and and $or query parameters, use `url?query={\"$or\":[...]}`\n- removed `prereq`, use `preMiddleware` instead\n- changed `postCreate`, `postUpdate`, and `postDelete` signatures to `(req, res, next)`\n- deprecated `outputFn`'s `data` parameter, data now available on `req.erm.result` and `req.erm.statusCode`\n\n### 1.0.0\n\n> **This release requires mongoose ~4**\n\n- updated mongoose to version 4\n- removed `fullErrors`, implement a custom `onError` handler instead\n- removed `strict` option, allows DELETE without id and POST with id, disallows PUT without id\n- async `prereq` and `access` now use the standard `(err, data)` callback signature\n- `access` will throw an exception when an unsupported value is passed\n- changed `outputFn`'s signature to: `(req, res, { result: result, statusCode: statusCode })`\n\n### 0.7.0\n\n> **This release requires mongoose ~3**\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (C) 2013 by Florian Holzapfel\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# express-restify-mongoose\n\nEasily create a flexible REST interface for mongoose models.\n\n[![Build Status](https://github.com/florianholzapfel/express-restify-mongoose/actions/workflows/node.js.yml/badge.svg)](https://github.com/florianholzapfel/express-restify-mongoose/actions/workflows/node.js.yml)\n[![npm version](https://badge.fury.io/js/express-restify-mongoose.svg)](https://badge.fury.io/js/express-restify-mongoose)\n\n## Getting started\n\n```sh\nnpm install express-restify-mongoose --save\n```\n\n## Documentation\n\n[https://florianholzapfel.github.io/express-restify-mongoose/](https://florianholzapfel.github.io/express-restify-mongoose/)\n\n## Compatibility\n\n| This library |  Mongoose   |   MongoDB   |  NodeJS\n| ------------ | ----------- | ----------- | --------\n| >= 9.0.0     | 6.x - 8.x   | 3.6.x - 7.x | >=16\n| >= 8.0.0     | 6.x         | 3.6.x - 6.x | >=18\n| >= 7.0.0     | 6.x         | 3.6.x - 6.x | >=14\n| >= 6.0.0     | 5.8.0 - 6.x | 3.6.x - 6.x | >=6\n| >= 1.0.0     | 4.x         | 2.4 - 3.4   | >=0.10\n| 0.7.5        | 3.x         | 2.4 - 3.0   | *\n\n## Contributing\n\nFound a bug or have a suggestion to make? Have a took at [issues or open a new one](https://github.com/florianholzapfel/express-restify-mongoose/issues).\n\nEveryone is welcome to contribute code by [creating a pull request](https://github.com/florianholzapfel/express-restify-mongoose/pulls), just make sure to follow [standard style](https://github.com/feross/standard).\n\nMany thanks to all [contributors](https://github.com/florianholzapfel/express-restify-mongoose/graphs/contributors)!\n\n## License (MIT)\n\n```\nCopyright (C) 2013 by Florian Holzapfel\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n```\n"
  },
  {
    "path": "examples/invoicing.js",
    "content": "import bodyParser from \"body-parser\";\nimport express from \"express\";\nimport http from \"http\";\nimport methodOverride from \"method-override\";\nimport mongoose, { Schema } from \"mongoose\";\nimport { serve } from \"../dist/express-restify-mongoose.js\";\n\nmongoose.connect(\"mongodb://localhost/database\", {\n  useMongoClient: true,\n});\n\nconst Customer = new Schema({\n  name: { type: String, required: true },\n  comment: { type: String },\n});\n\nconst CustomerModel = mongoose.model(\"Customer\", Customer);\n\nconst Invoice = new Schema({\n  customer: { type: Schema.Types.ObjectId, ref: \"Customer\" },\n  amount: { type: Number, required: true },\n});\n\nconst InvoiceModel = mongoose.model(\"Invoice\", Invoice);\n\nconst app = express();\n\napp.use(bodyParser.urlencoded({ extended: true }));\napp.use(bodyParser.json());\napp.use(methodOverride(\"X-HTTP-Method-Override\"));\n\nserve(app, CustomerModel);\nserve(app, InvoiceModel);\n\nhttp.createServer(app).listen(3000, function () {\n  // eslint-disable-next-line no-undef\n  console.log(\"Express server listening on port 3000\");\n});\n"
  },
  {
    "path": "express.d.ts",
    "content": "import type { Document, Model } from \"mongoose\";\nimport type { QueryOptions } from \"./src/getQuerySchema\";\nimport type { Access } from \"./src/types\";\n\ndeclare global {\n  namespace Express {\n    export interface Request {\n      access: Access;\n      erm: {\n        document?: Document;\n        model?: Model<unknown>;\n        query?: QueryOptions;\n        result?: Record<string, unknown> | Record<string, unknown>[];\n        statusCode?: number;\n        totalCount?: number;\n      };\n    }\n  }\n}\n"
  },
  {
    "path": "index.d.ts",
    "content": "declare module \"lodash.isplainobject\" {\n  export default function isPlainObject(\n    o: unknown\n  ): o is Record<string, unknown>;\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"express-restify-mongoose\",\n  \"version\": \"9.0.10\",\n  \"description\": \"Easily create a flexible REST interface for mongoose models\",\n  \"keywords\": [\n    \"ReST\",\n    \"express\",\n    \"restify\",\n    \"mongodb\",\n    \"mongoose\",\n    \"model\"\n  ],\n  \"homepage\": \"http://florianholzapfel.github.io/express-restify-mongoose/\",\n  \"bugs\": {\n    \"url\": \"https://github.com/florianholzapfel/express-restify-mongoose/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/florianholzapfel/express-restify-mongoose.git\"\n  },\n  \"license\": \"MIT\",\n  \"author\": {\n    \"name\": \"Florian Holzapfel\",\n    \"email\": \"flo.holzapfel@gmail.com\"\n  },\n  \"main\": \"./dist/express-restify-mongoose.js\",\n  \"files\": [\n    \"dist/\"\n  ],\n  \"scripts\": {\n    \"build\": \"run-p build:*\",\n    \"build:cjs\": \"swc src --config module.type=commonjs --out-dir dist\",\n    \"build:dts\": \"tsup src/express-restify-mongoose.ts --dts-only\",\n    \"lint\": \"eslint . --ext .js,.ts\",\n    \"test\": \"run-s test:*\",\n    \"test:express\": \"mocha -R spec ./test/express.mjs --timeout 10s\",\n    \"test:filter\": \"mocha -R spec ./test/integration/resource_filter.mjs --timeout 10s\",\n    \"test:restify\": \"mocha -R spec ./test/restify.mjs --timeout 10s\",\n    \"test:unit\": \"mocha -R spec ./test/unit.mjs\",\n    \"tsc\": \"tsc --noEmit\"\n  },\n  \"dependencies\": {\n    \"dot-prop\": \"^6.0.0\",\n    \"lodash.isplainobject\": \"~4.0.6\",\n    \"mongoose\": \"^8.2.1\",\n    \"serialize-error\": \"^8.1.0\",\n    \"zod\": \"^3.19.1\"\n  },\n  \"devDependencies\": {\n    \"@swc/cli\": \"^0.1.57\",\n    \"@swc/core\": \"^1.3.17\",\n    \"@swc/register\": \"^0.1.10\",\n    \"@types/express\": \"^4.17.14\",\n    \"@typescript-eslint/eslint-plugin\": \"^5.40.0\",\n    \"@typescript-eslint/parser\": \"^5.40.0\",\n    \"body-parser\": \"^1.19.0\",\n    \"esbuild\": \"^0.15.12\",\n    \"eslint\": \"^8.25.0\",\n    \"eslint-plugin-mocha\": \"10.3.0\",\n    \"express\": \"^4.17.1\",\n    \"method-override\": \"^3.0.0\",\n    \"mocha\": \"^10.1.0\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"prettier\": \"^2.7.1\",\n    \"request\": \"^2.88.2\",\n    \"restify\": \"^4.3.4\",\n    \"sinon\": \"^14.0.1\",\n    \"tsup\": \"^6.3.0\",\n    \"typescript\": \"^4.8.4\"\n  },\n  \"engines\": {\n    \"node\": \">=16\"\n  }\n}\n"
  },
  {
    "path": "src/buildQuery.ts",
    "content": "import mongoose from \"mongoose\";\nimport { QueryOptions } from \"./getQuerySchema.js\";\nimport { Options } from \"./types\";\n\nexport function getBuildQuery(\n  options: Pick<Options, \"lean\" | \"limit\" | \"readPreference\">\n) {\n  return function buildQuery<T>(\n    query: mongoose.Query<unknown, unknown>,\n    queryOptions: QueryOptions | undefined\n  ): Promise<T> {\n    const promise = new Promise((resolve) => {\n      if (!queryOptions) {\n        return resolve(query);\n      }\n\n      if (queryOptions.query) {\n        query.where(queryOptions.query);\n      }\n\n      if (queryOptions.skip) {\n        query.skip(queryOptions.skip);\n      }\n\n      if (\n        options.limit &&\n        (!queryOptions.limit || queryOptions.limit > options.limit)\n      ) {\n        queryOptions.limit = options.limit;\n      }\n\n      if (\n        queryOptions.limit &&\n        // @ts-expect-error this is fine 🐶🔥\n        query.op !== \"countDocuments\" &&\n        !queryOptions.distinct\n      ) {\n        query.limit(queryOptions.limit);\n      }\n\n      if (queryOptions.sort) {\n        // Necessary to support Mongoose 8\n        if (typeof queryOptions.sort === 'object') {\n          Object.keys(queryOptions.sort).forEach(key => {\n              // @ts-expect-error this is fine 🐶🔥\n              queryOptions.sort[key] = Number(queryOptions.sort[key]);\n          });\n        }\n        // @ts-expect-error this is fine 🐶🔥\n        query.sort(queryOptions.sort);\n      }\n\n      if (queryOptions.populate) {\n        // @ts-expect-error this is fine 🐶🔥\n        query.populate(queryOptions.populate);\n      }\n\n      if (queryOptions.select) {\n        query.select(queryOptions.select);\n      }\n\n      if (queryOptions.distinct) {\n        query.distinct(queryOptions.distinct);\n      }\n\n      if (options.readPreference) {\n        query.read(options.readPreference);\n      }\n\n      if (options.lean) {\n        query.lean(options.lean);\n      }\n\n      resolve(query);\n    });\n\n    return promise as Promise<T>;\n  };\n}\n"
  },
  {
    "path": "src/detective.ts",
    "content": "import mongoose from \"mongoose\";\n\nexport function detective(model: mongoose.Model<unknown>, path: string) {\n  const keys = path.split(\".\");\n  let schema = model.schema;\n  let schemaPath = \"\";\n\n  for (let i = 0, length = keys.length; i < length; i++) {\n    if (schemaPath.length > 0) {\n      schemaPath += \".\";\n    }\n\n    schemaPath += keys[i];\n\n    if (schema.path(schemaPath) && schema.path(schemaPath).schema) {\n      schema = schema.path(schemaPath).schema;\n    }\n  }\n\n  if (!schema) {\n    return;\n  }\n\n  // @ts-expect-error this is fine 🐶🔥\n  schemaPath = schema.path(keys[keys.length - 1]) || schema.path(schemaPath);\n\n  if (!schemaPath && (!model || !model.discriminators)) {\n    return;\n  }\n\n  // @ts-expect-error this is fine 🐶🔥\n  if (schemaPath.caster && schemaPath.caster.options) {\n    // @ts-expect-error this is fine 🐶🔥\n    return schemaPath.caster.options.ref;\n    // @ts-expect-error this is fine 🐶🔥\n  } else if (schemaPath.options) {\n    // @ts-expect-error this is fine 🐶🔥\n    return schemaPath.options.ref;\n  }\n}\n"
  },
  {
    "path": "src/errorHandler.ts",
    "content": "import { ErrorRequestHandler } from \"express\";\nimport { STATUS_CODES } from \"http\";\nimport { Options } from \"./types\";\n\nexport function getErrorHandler(\n  options: Pick<Options, \"idProperty\" | \"onError\">\n) {\n  const fn: ErrorRequestHandler = function errorHandler(err, req, res, next) {\n    if (\n      err.message === STATUS_CODES[404] ||\n      (req.params?.id &&\n        err.path === options.idProperty &&\n        err.name === \"CastError\")\n    ) {\n      req.erm.statusCode = 404;\n    } else {\n      req.erm.statusCode =\n        req.erm.statusCode && req.erm.statusCode >= 400\n          ? req.erm.statusCode\n          : 400;\n    }\n\n    options.onError(err, req, res, next);\n  };\n\n  return fn;\n}\n"
  },
  {
    "path": "src/express-restify-mongoose.ts",
    "content": "import { Application } from \"express\";\nimport mongoose from \"mongoose\";\nimport { deprecate } from \"util\";\nimport { getAccessHandler } from \"./middleware/access.js\";\nimport { getEnsureContentTypeHandler } from \"./middleware/ensureContentType.js\";\nimport { getFilterAndFindByIdHandler } from \"./middleware/filterAndFindById.js\";\nimport { getOnErrorHandler } from \"./middleware/onError.js\";\nimport { getOutputFnHandler } from \"./middleware/outputFn.js\";\nimport { getPrepareOutputHandler } from \"./middleware/prepareOutput.js\";\nimport { getPrepareQueryHandler } from \"./middleware/prepareQuery.js\";\nimport { operations } from \"./operations.js\";\nimport { Filter } from \"./resource_filter.js\";\nimport { Options } from \"./types\";\n\nconst defaultOptions: Omit<Options, \"contextFilter\" | \"outputFn\" | \"onError\"> =\n  {\n    prefix: \"/api\",\n    version: \"/v1\",\n    idProperty: \"_id\",\n    restify: false,\n    allowRegex: true,\n    runValidators: false,\n    readPreference: \"primary\",\n    totalCountHeader: false,\n    private: [],\n    protected: [],\n    lean: true,\n    findOneAndUpdate: true,\n    findOneAndRemove: true,\n    upsert: false,\n    preMiddleware: [],\n    preCreate: [],\n    preRead: [],\n    preUpdate: [],\n    preDelete: [],\n    updateDeep: true,\n  };\n\nconst filter = new Filter();\n\nexport function serve(\n  app: Application,\n  model: mongoose.Model<unknown>,\n  options: Partial<Options> = {}\n) {\n  const serveOptions: Options = {\n    ...defaultOptions,\n    name: typeof options.name === \"string\" ? options.name : model.modelName,\n    contextFilter: (model, req, done) => done(model),\n    outputFn: getOutputFnHandler(\n      typeof options.restify === \"boolean\"\n        ? !options.restify\n        : !defaultOptions.restify\n    ),\n    onError: getOnErrorHandler(\n      typeof options.restify === \"boolean\"\n        ? !options.restify\n        : !defaultOptions.restify\n    ),\n    ...options,\n  };\n\n  model.schema.eachPath((name, path) => {\n    if (path.options.access) {\n      switch (path.options.access.toLowerCase()) {\n        case \"private\":\n          serveOptions.private.push(name);\n          break;\n        case \"protected\":\n          serveOptions.protected.push(name);\n          break;\n      }\n    }\n  });\n\n  filter.add(model, {\n    filteredKeys: {\n      private: serveOptions.private,\n      protected: serveOptions.protected,\n    },\n  });\n\n  const ops = operations(model, serveOptions, filter);\n\n  let uriItem = `${serveOptions.prefix}${serveOptions.version}/${serveOptions.name}`;\n\n  if (uriItem.indexOf(\"/:id\") === -1) {\n    uriItem += \"/:id\";\n  }\n\n  const uriItems = uriItem.replace(\"/:id\", \"\");\n  const uriCount = uriItems + \"/count\";\n  const uriShallow = uriItem + \"/shallow\";\n\n  if (typeof app.delete === \"undefined\") {\n    // @ts-expect-error restify\n    app.delete = app.del;\n  }\n\n  // @ts-expect-error restify\n  const modelMiddleware = async (req, res, next) => {\n    const getModel = serveOptions?.modelFactory?.getModel;\n    \n    req.erm = {\n      model: typeof getModel === 'function' ? await getModel(req) : model,\n    };\n    \n    next();\n  };\n\n  const accessMiddleware = serveOptions.access\n    ? getAccessHandler({\n        access: serveOptions.access,\n        idProperty: serveOptions.idProperty,\n        onError: serveOptions.onError,\n      })\n    : [];\n\n  const ensureContentType = getEnsureContentTypeHandler(serveOptions);\n  const filterAndFindById = getFilterAndFindByIdHandler(serveOptions);\n  const prepareQuery = getPrepareQueryHandler(serveOptions);\n  const prepareOutput = getPrepareOutputHandler(\n    serveOptions,\n    model.modelName,\n    filter\n  );\n\n  app.get(\n    uriItems,\n    modelMiddleware,\n    prepareQuery,\n    serveOptions.preMiddleware,\n    serveOptions.preRead,\n    accessMiddleware,\n    ops.getItems,\n    prepareOutput\n  );\n\n  app.get(\n    uriCount,\n    modelMiddleware,\n    prepareQuery,\n    serveOptions.preMiddleware,\n    serveOptions.preRead,\n    accessMiddleware,\n    ops.getCount,\n    prepareOutput\n  );\n\n  app.get(\n    uriItem,\n    modelMiddleware,\n    prepareQuery,\n    serveOptions.preMiddleware,\n    serveOptions.preRead,\n    accessMiddleware,\n    ops.getItem,\n    prepareOutput\n  );\n\n  app.get(\n    uriShallow,\n    modelMiddleware,\n    prepareQuery,\n    serveOptions.preMiddleware,\n    serveOptions.preRead,\n    accessMiddleware,\n    ops.getShallow,\n    prepareOutput\n  );\n\n  app.post(\n    uriItems,\n    modelMiddleware,\n    prepareQuery,\n    ensureContentType,\n    serveOptions.preMiddleware,\n    serveOptions.preCreate,\n    accessMiddleware,\n    ops.createObject,\n    prepareOutput\n  );\n\n  app.post(\n    uriItem,\n    modelMiddleware,\n    deprecate(\n      prepareQuery,\n      \"express-restify-mongoose: in a future major version, the POST method to update resources will be removed. Use PATCH instead.\"\n    ),\n    ensureContentType,\n    serveOptions.preMiddleware,\n    serveOptions.findOneAndUpdate ? [] : filterAndFindById,\n    serveOptions.preUpdate,\n    accessMiddleware,\n    ops.modifyObject,\n    prepareOutput\n  );\n\n  app.put(\n    uriItem,\n    modelMiddleware,\n    deprecate(\n      prepareQuery,\n      \"express-restify-mongoose: in a future major version, the PUT method will replace rather than update a resource. Use PATCH instead.\"\n    ),\n    ensureContentType,\n    serveOptions.preMiddleware,\n    serveOptions.findOneAndUpdate ? [] : filterAndFindById,\n    serveOptions.preUpdate,\n    accessMiddleware,\n    ops.modifyObject,\n    prepareOutput\n  );\n\n  app.patch(\n    uriItem,\n    modelMiddleware,\n    prepareQuery,\n    ensureContentType,\n    serveOptions.preMiddleware,\n    serveOptions.findOneAndUpdate ? [] : filterAndFindById,\n    serveOptions.preUpdate,\n    accessMiddleware,\n    ops.modifyObject,\n    prepareOutput\n  );\n\n  app.delete(\n    uriItems,\n    modelMiddleware,\n    prepareQuery,\n    serveOptions.preMiddleware,\n    serveOptions.preDelete,\n    ops.deleteItems,\n    prepareOutput\n  );\n\n  app.delete(\n    uriItem,\n    modelMiddleware,\n    prepareQuery,\n    serveOptions.preMiddleware,\n    serveOptions.findOneAndRemove ? [] : filterAndFindById,\n    serveOptions.preDelete,\n    ops.deleteItem,\n    prepareOutput\n  );\n\n  return uriItems;\n}\n"
  },
  {
    "path": "src/getQuerySchema.ts",
    "content": "import { z } from \"zod\";\n\nconst PopulateOptionsSchema = z.object({\n  path: z.string(),\n  match: z.record(z.unknown()).optional(),\n  options: z.record(z.unknown()).optional(),\n  select: z.string().optional(),\n  populate: z.record(z.unknown()).optional(),\n  // Configure populate query to not use strict populate to maintain\n  // behavior from Mongoose previous to v6 (unless already configured)\n  strictPopulate: z.boolean().optional().default(false),\n});\n\nconst PopulateSchema = z.preprocess((value) => {\n  if (typeof value === \"string\") {\n    if (value.startsWith(\"{\")) {\n      return JSON.parse(`[${value}]`);\n    }\n\n    if (value.startsWith(\"[\")) {\n      return JSON.parse(value);\n    }\n\n    return value;\n  }\n\n  return Array.isArray(value) ? value : [value];\n}, z.union([z.string(), z.array(PopulateOptionsSchema)]));\n\nconst SelectSchema = z.preprocess((value) => {\n  const fieldToRecord = (field: string) => {\n    if (field.startsWith(\"-\")) {\n      return [field.substring(1), 0];\n    }\n\n    return [field, 1];\n  };\n\n  if (typeof value === \"string\") {\n    if (value.startsWith(\"{\")) {\n      return JSON.parse(value);\n    }\n\n    return Object.fromEntries(\n      value.split(\",\").filter(Boolean).map(fieldToRecord)\n    );\n  }\n\n  if (Array.isArray(value)) {\n    return Object.fromEntries(value.map(fieldToRecord));\n  }\n\n  return value;\n}, z.record(z.number().min(0).max(1)));\n\nconst SortSchema = z.preprocess((value) => {\n  if (typeof value === \"string\" && value.startsWith(\"{\")) {\n    return JSON.parse(value);\n  }\n\n  return value;\n}, z.union([z.string(), z.record(z.enum([\"asc\", \"desc\", \"ascending\", \"descending\", \"-1\", \"1\"])), z.record(z.number().min(-1).max(1))]));\n\nconst LimitSkipSchema = z.preprocess((value) => {\n  if (typeof value !== \"string\") {\n    return value;\n  }\n\n  return Number(value);\n}, z.number());\n\nexport function getQueryOptionsSchema({ allowRegex }: { allowRegex: boolean }) {\n  const QuerySchema = z\n    .preprocess((value) => {\n      if (!allowRegex && `${value}`.toLowerCase().includes(\"$regex\")) {\n        throw new Error(\"regex_not_allowed\");\n      }\n\n      if (typeof value !== \"string\") {\n        return value;\n      }\n\n      return JSON.parse(value);\n    }, z.record(z.unknown()))\n    .transform((value) => {\n      return Object.fromEntries(\n        Object.entries(value).map(([key, value]) => {\n          if (Array.isArray(value) && !key.startsWith(\"$\")) {\n            return [key, { $in: value }];\n          }\n\n          return [key, value];\n        })\n      );\n    });\n\n  return z\n    .object({\n      query: QuerySchema.optional(),\n      populate: PopulateSchema.optional(),\n      select: SelectSchema.optional(),\n      sort: SortSchema.optional(),\n      limit: LimitSkipSchema.optional(),\n      skip: LimitSkipSchema.optional(),\n      distinct: z.string().optional(),\n    })\n    .transform((value) => {\n      if (typeof value.populate === \"undefined\") {\n        return value;\n      }\n\n      const populate =\n        typeof value.populate === \"string\"\n          ? value.populate\n              .split(\",\")\n              .filter(Boolean)\n              .map<z.infer<typeof PopulateOptionsSchema>>((field) => {\n                const pop: z.infer<typeof PopulateOptionsSchema> = {\n                  path: field,\n                  strictPopulate: false,\n                };\n\n                if (!value.select) {\n                  return pop;\n                }\n\n                for (const [k, v] of Object.entries(value.select)) {\n                  if (k.startsWith(`${field}.`)) {\n                    if (pop.select) {\n                      pop.select += \" \";\n                    } else {\n                      pop.select = \"\";\n                    }\n\n                    if (v === 0) {\n                      pop.select += \"-\";\n                    }\n\n                    pop.select += k.substring(field.length + 1);\n\n                    delete value.select[k];\n                  }\n                }\n\n                // If other specific fields are selected, add the populated field\n                if (\n                  Object.keys(value.select).length > 0 &&\n                  !value.select[field]\n                ) {\n                  value.select[field] = 1;\n                }\n\n                return pop;\n              })\n          : value.populate;\n\n      return {\n        ...value,\n        populate,\n      };\n    })\n    .transform((value) => {\n      if (\n        !value.populate ||\n        (Array.isArray(value.populate) && value.populate.length === 0)\n      ) {\n        delete value.populate;\n      }\n\n      if (!value.select || Object.keys(value.select).length === 0) {\n        delete value.select;\n      }\n\n      return value;\n    });\n}\n\nexport type QueryOptions = z.infer<ReturnType<typeof getQueryOptionsSchema>>;\n"
  },
  {
    "path": "src/middleware/access.ts",
    "content": "import { RequestHandler } from \"express\";\nimport { getErrorHandler } from \"../errorHandler.js\";\nimport { Access, Options } from \"../types\";\n\nexport function getAccessHandler(\n  options: Required<Pick<Options, \"access\" | \"idProperty\" | \"onError\">>\n) {\n  const errorHandler = getErrorHandler(options);\n\n  const fn: RequestHandler = function access(req, res, next) {\n    const handler = function (access: Access) {\n      if (![\"public\", \"private\", \"protected\"].includes(access)) {\n        throw new Error(\n          'Unsupported access, must be \"public\", \"private\" or \"protected\"'\n        );\n      }\n\n      req.access = access;\n\n      next();\n    };\n\n    const result = options.access(req);\n\n    if (typeof result === \"string\") {\n      handler(result);\n    } else {\n      result.then(handler).catch((err) => errorHandler(err, req, res, next));\n    }\n  };\n\n  return fn;\n}\n"
  },
  {
    "path": "src/middleware/ensureContentType.ts",
    "content": "import { RequestHandler } from \"express\";\nimport { getErrorHandler } from \"../errorHandler.js\";\nimport { Options } from \"../types\";\n\nexport function getEnsureContentTypeHandler(\n  options: Pick<Options, \"idProperty\" | \"onError\">\n) {\n  const errorHandler = getErrorHandler(options);\n\n  const fn: RequestHandler = function ensureContentType(req, res, next) {\n    const contentType = req.headers[\"content-type\"];\n\n    if (!contentType) {\n      return errorHandler(new Error(\"missing_content_type\"), req, res, next);\n    }\n\n    if (!contentType.includes(\"application/json\")) {\n      return errorHandler(new Error(\"invalid_content_type\"), req, res, next);\n    }\n\n    next();\n  };\n\n  return fn;\n}\n"
  },
  {
    "path": "src/middleware/filterAndFindById.ts",
    "content": "import { RequestHandler } from \"express\";\nimport { STATUS_CODES } from \"http\";\nimport mongoose from \"mongoose\";\nimport { getErrorHandler } from \"../errorHandler.js\";\nimport { Options } from \"../types\";\n\nexport function getFilterAndFindByIdHandler(\n  options: Pick<\n    Options,\n    \"contextFilter\" | \"idProperty\" | \"onError\" | \"readPreference\"\n  >\n) {\n  const errorHandler = getErrorHandler(options);\n\n  const fn: RequestHandler = function filterAndFindById(req, res, next) {\n    const contextModel = req.erm.model;\n    if (!contextModel) {\n      return errorHandler(new Error('Model is undefined.'), req, res, next);\n    }\n\n    if (!req.params.id) {\n      return next();\n    }\n\n    options.contextFilter(contextModel, req, (filteredContext) => {\n      filteredContext\n        // @ts-expect-error this is fine 🐶🔥\n        .findOne()\n        .and({\n          [options.idProperty]: req.params.id,\n        })\n        .lean(false)\n        .read(options.readPreference || \"p\")\n        .exec()\n        .then((doc: mongoose.Document | null) => {\n          if (!doc) {\n            return errorHandler(new Error(STATUS_CODES[404]), req, res, next);\n          }\n\n          req.erm.document = doc;\n\n          next();\n        })\n        .catch((err: Error) => errorHandler(err, req, res, next));\n    });\n  };\n\n  return fn;\n}\n"
  },
  {
    "path": "src/middleware/onError.ts",
    "content": "import { ErrorRequestHandler } from \"express\";\nimport { serializeError } from \"serialize-error\";\n\nexport function getOnErrorHandler(isExpress: boolean) {\n  const fn: ErrorRequestHandler = function onError(err, req, res) {\n    const serializedErr = serializeError(err);\n\n    delete serializedErr.stack;\n\n    if (serializedErr.errors) {\n      for (const key in serializedErr.errors) {\n        delete serializedErr.errors[key].reason;\n        delete serializedErr.errors[key].stack;\n      }\n    }\n\n    res.setHeader(\"Content-Type\", \"application/json\");\n\n    if (isExpress) {\n      res.status(req.erm.statusCode || 500).send(serializedErr);\n    } else {\n      // @ts-expect-error restify\n      res.send(req.erm.statusCode, serializedErr);\n    }\n  };\n\n  return fn;\n}\n"
  },
  {
    "path": "src/middleware/outputFn.ts",
    "content": "import { OutputFn } from \"../types\";\n\nexport function getOutputFnHandler(isExpress: boolean) {\n  const fn: OutputFn = function outputFn(req, res) {\n    if (!req.erm.statusCode) {\n      throw new Error(\"statusCode not set\");\n    }\n\n    if (isExpress) {\n      if (req.erm.result) {\n        res.status(req.erm.statusCode).json(req.erm.result);\n      } else {\n        res.sendStatus(req.erm.statusCode);\n      }\n    } else {\n      // @ts-expect-error restify\n      res.send(req.erm.statusCode, req.erm.result);\n    }\n  };\n\n  return fn;\n}\n"
  },
  {
    "path": "src/middleware/prepareOutput.ts",
    "content": "import { RequestHandler } from \"express\";\nimport { getErrorHandler } from \"../errorHandler.js\";\nimport { Filter } from \"../resource_filter.js\";\nimport { Options } from \"../types\";\n\nfunction isDefined<T>(arg: T | undefined): arg is T {\n  return typeof arg !== \"undefined\";\n}\n\nexport function getPrepareOutputHandler(\n  options: Pick<\n    Options,\n    | \"idProperty\"\n    | \"onError\"\n    | \"postCreate\"\n    | \"postRead\"\n    | \"postUpdate\"\n    | \"postDelete\"\n    | \"outputFn\"\n    | \"postProcess\"\n    | \"totalCountHeader\"\n  >,\n  modelName: string,\n  filter: Filter\n) {\n  const errorHandler = getErrorHandler(options);\n\n  const fn: RequestHandler = function prepareOutput(req, res, next) {\n    const postMiddleware = (() => {\n      switch (req.method.toLowerCase()) {\n        case \"get\": {\n          return Array.isArray(options.postRead)\n            ? options.postRead\n            : [options.postRead];\n        }\n        case \"post\": {\n          if (req.erm.statusCode === 201) {\n            return Array.isArray(options.postCreate)\n              ? options.postCreate\n              : [options.postCreate];\n          }\n\n          return Array.isArray(options.postUpdate)\n            ? options.postUpdate\n            : [options.postUpdate];\n        }\n        case \"put\":\n        case \"patch\": {\n          return Array.isArray(options.postUpdate)\n            ? options.postUpdate\n            : [options.postUpdate];\n        }\n        case \"delete\": {\n          return Array.isArray(options.postDelete)\n            ? options.postDelete\n            : [options.postDelete];\n        }\n        default: {\n          return [];\n        }\n      }\n    })().filter(isDefined);\n\n    const callback = () => {\n      // TODO: this will, but should not, filter /count queries\n      if (req.erm.result) {\n        req.erm.result = filter.filterObject(req.erm.result, {\n          access: req.access,\n          modelName,\n          // @ts-expect-error this is fine 🐶🔥\n          populate: req.erm.query?.populate,\n        });\n      }\n\n      if (options.totalCountHeader && typeof req.erm.totalCount === \"number\") {\n        res.header(\n          typeof options.totalCountHeader === \"string\"\n            ? options.totalCountHeader\n            : \"X-Total-Count\",\n          `${req.erm.totalCount}`\n        );\n      }\n\n      const promise = options.outputFn(req, res);\n\n      if (options.postProcess) {\n        if (promise && typeof promise.then === \"function\") {\n          promise\n            .then(() => {\n              options.postProcess?.(req, res);\n            })\n            .catch((err) => errorHandler(err, req, res, next));\n        } else {\n          options.postProcess(req, res);\n        }\n      }\n    };\n\n    if (!postMiddleware || postMiddleware.length === 0) {\n      return callback();\n    }\n\n    postMiddleware\n      .reduce(async (acc, middleware) => {\n        await acc;\n\n        return new Promise((resolve, reject) => {\n          middleware(req, res, (err) => (err ? reject(err) : resolve(err)));\n        });\n      }, Promise.resolve())\n      .then(callback)\n      .catch((err) => errorHandler(err, req, res, next));\n  };\n\n  return fn;\n}\n"
  },
  {
    "path": "src/middleware/prepareQuery.ts",
    "content": "import { RequestHandler } from \"express\";\nimport { getErrorHandler } from \"../errorHandler.js\";\nimport { getQueryOptionsSchema } from \"../getQuerySchema.js\";\nimport { Options } from \"../types\";\n\nexport function getPrepareQueryHandler(\n  options: Pick<Options, \"allowRegex\" | \"idProperty\" | \"onError\">\n) {\n  const errorHandler = getErrorHandler(options);\n\n  const fn: RequestHandler = function prepareQuery(req, res, next) {\n    req.erm = req.erm || {};\n\n    try {\n      req.erm.query = getQueryOptionsSchema({\n        allowRegex: options.allowRegex,\n      }).parse(req.query || {});\n\n      next();\n    } catch (e) {\n      return errorHandler(new Error(\"invalid_json_query\"), req, res, next);\n    }\n  };\n\n  return fn;\n}\n"
  },
  {
    "path": "src/moredots.ts",
    "content": "import isPlainObject from \"lodash.isplainobject\";\n\nexport function moredots(\n  src: Record<string, unknown>,\n  dst: Record<string, unknown> = {},\n  prefix = \"\"\n) {\n  for (const [key, value] of Object.entries(src)) {\n    if (isPlainObject(value)) {\n      moredots(value, dst, `${prefix}${key}.`);\n    } else {\n      dst[`${prefix}${key}`] = value;\n    }\n  }\n\n  return dst;\n}\n"
  },
  {
    "path": "src/operations.ts",
    "content": "import { Request, RequestHandler } from \"express\";\nimport { STATUS_CODES } from \"http\";\nimport isPlainObject from \"lodash.isplainobject\";\nimport mongoose from \"mongoose\";\nimport { getBuildQuery } from \"./buildQuery.js\";\nimport { getErrorHandler } from \"./errorHandler.js\";\nimport { moredots } from \"./moredots.js\";\nimport { Filter } from \"./resource_filter.js\";\nimport { Options } from \"./types\";\n\nexport function operations(\n  model: mongoose.Model<unknown>,\n  options: Pick<\n    Options,\n    | \"contextFilter\"\n    | \"findOneAndRemove\"\n    | \"findOneAndUpdate\"\n    | \"idProperty\"\n    | \"lean\"\n    | \"limit\"\n    | \"onError\"\n    | \"readPreference\"\n    | \"runValidators\"\n    | \"totalCountHeader\"\n    | \"upsert\"\n    | \"updateDeep\"\n  >,\n  filter: Filter\n) {\n  const buildQuery = getBuildQuery(options);\n  const errorHandler = getErrorHandler(options);\n\n  function findById(filteredContext: mongoose.Model<unknown>, id: unknown) {\n    return filteredContext.findOne().and([\n      {\n        [options.idProperty]: id,\n      },\n    ]);\n  }\n\n  function isDistinctExcluded(req: Request) {\n    if (!req.erm.query?.distinct) {\n      return false;\n    }\n\n    return filter\n      .getExcluded({\n        access: req.access,\n        modelName: model.modelName,\n      })\n      .includes(req.erm.query.distinct);\n  }\n\n  const getItems: RequestHandler = function (req, res, next) {\n    const contextModel = req.erm.model;\n    if (!contextModel) {\n      return errorHandler(new Error('Model is undefined.'), req, res, next);\n    }\n\n    if (isDistinctExcluded(req)) {\n      req.erm.result = [];\n      req.erm.statusCode = 200;\n      return next();\n    }\n\n    options.contextFilter(contextModel, req, (filteredContext) => {\n      buildQuery<Record<string, unknown>[]>(\n        // @ts-expect-error this is fine 🐶🔥\n        filteredContext.find(),\n        req.erm.query\n      )\n        .then((items) => {\n          req.erm.result = items;\n          req.erm.statusCode = 200;\n\n          if (options.totalCountHeader && !req.erm.query?.distinct) {\n            options.contextFilter(contextModel, req, (countFilteredContext) => {\n              buildQuery<number>(countFilteredContext.countDocuments(), {\n                ...req.erm.query,\n                skip: 0,\n                limit: 0,\n              })\n                .then((count) => {\n                  req.erm.totalCount = count;\n                  next();\n                })\n                .catch((err) => errorHandler(err, req, res, next));\n            });\n          } else {\n            next();\n          }\n        })\n        .catch((err) => errorHandler(err, req, res, next));\n    });\n  };\n\n  const getCount: RequestHandler = function (req, res, next) {\n    const contextModel = req.erm.model;\n    if (!contextModel) {\n      return errorHandler(new Error('Model is undefined.'), req, res, next);\n    }\n\n    options.contextFilter(contextModel, req, (filteredContext) => {\n      buildQuery(filteredContext.countDocuments(), req.erm.query)\n        .then((count) => {\n          req.erm.result = { count: count };\n          req.erm.statusCode = 200;\n\n          next();\n        })\n        .catch((err) => errorHandler(err, req, res, next));\n    });\n  };\n\n  const getShallow: RequestHandler = function (req, res, next) {\n    const contextModel = req.erm.model;\n    if (!contextModel) {\n      return errorHandler(new Error('Model is undefined.'), req, res, next);\n    }\n\n    options.contextFilter(contextModel, req, (filteredContext) => {\n      buildQuery<Record<string, unknown> | null>(\n        // @ts-expect-error this is fine 🐶🔥\n        findById(filteredContext, req.params.id),\n        req.erm.query\n      )\n        .then((item) => {\n          if (!item) {\n            return errorHandler(new Error(STATUS_CODES[404]), req, res, next);\n          }\n\n          for (const prop in item) {\n            item[prop] =\n              typeof item[prop] === \"object\" && prop !== \"_id\"\n                ? true\n                : item[prop];\n          }\n\n          req.erm.result = item;\n          req.erm.statusCode = 200;\n\n          next();\n        })\n        .catch((err) => errorHandler(err, req, res, next));\n    });\n  };\n\n  const deleteItems: RequestHandler = function (req, res, next) {\n    const contextModel = req.erm.model;\n    if (!contextModel) {\n      return errorHandler(new Error('Model is undefined.'), req, res, next);\n    }\n\n    options.contextFilter(contextModel, req, (filteredContext) => {\n      buildQuery(filteredContext.deleteMany(), req.erm.query)\n        .then(() => {\n          req.erm.statusCode = 204;\n\n          next();\n        })\n        .catch((err) => errorHandler(err, req, res, next));\n    });\n  };\n\n  const getItem: RequestHandler = function (req, res, next) {\n    const contextModel = req.erm.model;\n    if (!contextModel) {\n      return errorHandler(new Error('Model is undefined.'), req, res, next);\n    }\n\n    if (isDistinctExcluded(req)) {\n      req.erm.result = [];\n      req.erm.statusCode = 200;\n      return next();\n    }\n\n    options.contextFilter(contextModel, req, (filteredContext) => {\n      buildQuery<Record<string, unknown> | null>(\n        // @ts-expect-error this is fine 🐶🔥\n        findById(filteredContext, req.params.id),\n        req.erm.query\n      )\n        .then((item) => {\n          if (!item) {\n            return errorHandler(new Error(STATUS_CODES[404]), req, res, next);\n          }\n\n          req.erm.result = item;\n          req.erm.statusCode = 200;\n\n          next();\n        })\n        .catch((err) => errorHandler(err, req, res, next));\n    });\n  };\n\n  const deleteItem: RequestHandler = function (req, res, next) {\n    const contextModel = req.erm.model;\n    if (!contextModel) {\n      return errorHandler(new Error('Model is undefined.'), req, res, next);\n    }\n\n    if (options.findOneAndRemove) {\n      options.contextFilter(contextModel, req, (filteredContext) => {\n        // @ts-expect-error this is fine 🐶🔥\n        findById(filteredContext, req.params.id)\n          .findOneAndDelete() // switched to findOneAndDelete to add support for Mongoose 7 and 8\n          .then((item) => {\n            if (!item) {\n              return errorHandler(new Error(STATUS_CODES[404]), req, res, next);\n            }\n\n            req.erm.statusCode = 204;\n\n            next();\n          })\n          .catch((err: Error) => errorHandler(err, req, res, next));\n      });\n    } else {\n      req.erm.document\n        ?.deleteOne() // switched to deleteOne to add support for Mongoose 7 and 8\n        .then(() => {\n          req.erm.statusCode = 204;\n\n          next();\n        })\n        .catch((err: Error) => errorHandler(err, req, res, next));\n    }\n  };\n\n  const createObject: RequestHandler = function (req, res, next) {\n    const contextModel = req.erm.model;\n    if (!contextModel) {\n      return errorHandler(new Error('Model is undefined.'), req, res, next);\n    }\n\n    req.body = filter.filterObject(req.body || {}, {\n      access: req.access,\n      modelName: model.modelName,\n      // @ts-expect-error this is fine 🐶🔥\n      populate: req.erm.query?.populate,\n    });\n\n    if (req.body._id === null) {\n      delete req.body._id;\n    }\n\n    // @ts-expect-error this is fine 🐶🔥\n    if (contextModel.schema.options.versionKey) {\n      // @ts-expect-error this is fine 🐶🔥\n      delete req.body[contextModel.schema.options.versionKey];\n    }\n\n    contextModel\n      .create(req.body)\n      .then((item) => {\n        // @ts-expect-error this is fine 🐶🔥\n        return contextModel.populate(item, req.erm.query?.populate || []);\n      })\n      .then((item) => {\n        req.erm.result = item as unknown as Record<string, unknown>;\n        req.erm.statusCode = 201;\n\n        next();\n      })\n      .catch((err) => errorHandler(err, req, res, next));\n  };\n\n  const modifyObject: RequestHandler = function (req, res, next) {\n    const contextModel = req.erm.model;\n    if (!contextModel) {\n      return errorHandler(new Error('Model is undefined.'), req, res, next);\n    }\n\n    req.body = filter.filterObject(req.body || {}, {\n      access: req.access,\n      modelName: model.modelName,\n      // @ts-expect-error this is fine 🐶🔥\n      populate: req.erm.query?.populate,\n    });\n\n    delete req.body._id;\n\n    // @ts-expect-error this is fine 🐶🔥\n    if (contextModel.schema.options.versionKey) {\n      // @ts-expect-error this is fine 🐶🔥\n      delete req.body[contextModel.schema.options.versionKey];\n    }\n\n    function depopulate(src: Record<string, unknown>) {\n      const dst: Record<string, unknown> = {};\n\n      for (const [key, value] of Object.entries(src)) {\n        // @ts-expect-error this is fine 🐶🔥\n        const path = contextModel.schema.path(key);\n\n        // @ts-expect-error this is fine 🐶🔥\n        // Add support for Mongoose 7 and 8 while keeping backwards-compatibility to 6 by allowing ObjectID and ObejctId \n        if (path && path.caster && (path.caster.instance === \"ObjectID\" || path.caster.instance === \"ObjectId\")) {\n          if (Array.isArray(value)) {\n            for (let j = 0; j < value.length; ++j) {\n              if (typeof value[j] === \"object\") {\n                dst[key] = dst[key] || [];\n                // @ts-expect-error this is fine 🐶🔥\n                dst[key].push(value[j]._id);\n              }\n            }\n          } else if (isPlainObject(value)) {\n            dst[key] = value._id;\n          }\n        } else if (isPlainObject(value)) {\n        // Add support for Mongoose 7 and 8 while keeping backwards-compatibility to 6 by allowing ObjectID and ObejctId \n          if (path && (path.instance === \"ObjectID\" || path.instance === \"ObjectId\")) {\n            dst[key] = value._id;\n          } else {\n            dst[key] = depopulate(value);\n          }\n        }\n\n        if (typeof dst[key] === \"undefined\") {\n          dst[key] = value;\n        }\n      }\n\n      return dst;\n    }\n\n    let cleanBody = depopulate(req.body);\n\n    if (options.updateDeep) { \n      cleanBody = moredots(cleanBody);\n    }\n\n    if (options.findOneAndUpdate) {\n      options.contextFilter(contextModel, req, (filteredContext) => {\n        // @ts-expect-error this is fine 🐶🔥\n        findById(filteredContext, req.params.id)\n          .findOneAndUpdate(\n            {},\n            {\n              $set: cleanBody,\n            },\n            {\n              new: true,\n              upsert: options.upsert,\n              runValidators: options.runValidators,\n            }\n          )\n          .exec()\n          .then((item) => {\n            // @ts-expect-error this is fine 🐶🔥\n            return contextModel.populate(item, req.erm.query?.populate || []);\n          })\n          .then((item) => {\n            if (!item) {\n              return errorHandler(new Error(STATUS_CODES[404]), req, res, next);\n            }\n\n            req.erm.result = item as unknown as Record<string, unknown>;\n            req.erm.statusCode = 200;\n\n            next();\n          })\n          .catch((err) => errorHandler(err, req, res, next));\n      });\n    } else {\n      for (const [key, value] of Object.entries(cleanBody)) {\n        req.erm.document?.set(key, value);\n      }\n\n      req.erm.document\n        ?.save()\n        .then((item) => {\n          // @ts-expect-error this is fine 🐶🔥\n          return contextModel.populate(item, req.erm.query?.populate || []);\n        })\n        .then((item) => {\n          req.erm.result = item as unknown as Record<string, unknown>;\n          req.erm.statusCode = 200;\n\n          next();\n        })\n        .catch((err: Error) => errorHandler(err, req, res, next));\n    }\n  };\n\n  return {\n    getItems,\n    getCount,\n    getItem,\n    getShallow,\n    createObject,\n    modifyObject,\n    deleteItems,\n    deleteItem,\n  };\n}\n"
  },
  {
    "path": "src/resource_filter.ts",
    "content": "import dotProp from \"dot-prop\";\nimport mongoose from \"mongoose\";\nimport { detective } from \"./detective.js\";\nimport { QueryOptions } from \"./getQuerySchema.js\";\nimport { Access, ExcludedMap, FilteredKeys } from \"./types\";\nimport { weedout } from \"./weedout.js\";\nconst { get: getProperty, has: hasProperty } = dotProp; // Because we're using an older version of dotProp that supports CommonJS\n\nexport class Filter {\n  excludedMap: ExcludedMap = new Map();\n\n  add(\n    model: mongoose.Model<unknown>,\n    options: {\n      filteredKeys: FilteredKeys;\n    }\n  ) {\n    if (model.discriminators) {\n      for (const modelName in model.discriminators) {\n        const excluded = this.excludedMap.get(modelName);\n\n        if (excluded) {\n          options.filteredKeys.private = options.filteredKeys.private.concat(\n            excluded.filteredKeys.private\n          );\n\n          options.filteredKeys.protected =\n            options.filteredKeys.protected.concat(\n              excluded.filteredKeys.protected\n            );\n        }\n      }\n    }\n\n    this.excludedMap.set(model.modelName, {\n      filteredKeys: options.filteredKeys,\n      model,\n    });\n  }\n\n  /**\n   * Gets excluded keys for a given model and access.\n   */\n  getExcluded(options: { access: Access; modelName: string }) {\n    if (options.access === \"private\") {\n      return [];\n    }\n\n    const filteredKeys = this.excludedMap.get(options.modelName)?.filteredKeys;\n\n    if (!filteredKeys) {\n      return [];\n    }\n\n    return options.access === \"protected\"\n      ? filteredKeys.private\n      : filteredKeys.private.concat(filteredKeys.protected);\n  }\n\n  /**\n   * Removes excluded keys from a document.\n   */\n  private filterItem<\n    T extends undefined | Record<string, unknown> | Record<string, unknown>[]\n  >(item: T, excluded: string[]): T {\n    if (!item) {\n      return item;\n    }\n\n    if (Array.isArray(item)) {\n      return item.map((i) => this.filterItem(i, excluded)) as T;\n    }\n\n    if (excluded) {\n      if (typeof item.toObject === \"function\") {\n        item = item.toObject();\n      }\n\n      for (let i = 0; i < excluded.length; i++) {\n        weedout(item as Record<string, unknown>, excluded[i]);\n      }\n    }\n\n    return item;\n  }\n\n  /**\n   * Removes excluded keys from a document with populated subdocuments.\n   */\n  private filterPopulatedItem<\n    T extends Record<string, unknown> | Record<string, unknown>[]\n  >(\n    item: T,\n    options: {\n      access: Access;\n      modelName: string;\n      populate: Exclude<QueryOptions[\"populate\"], undefined | string>;\n    }\n  ): T {\n    if (Array.isArray(item)) {\n      return item.map((i) => this.filterPopulatedItem(i, options)) as T;\n    }\n\n    for (let i = 0; i < options.populate.length; i++) {\n      if (!options.populate[i].path) {\n        continue;\n      }\n\n      const model = this.excludedMap.get(options.modelName)?.model;\n\n      if (!model) {\n        continue;\n      }\n\n      const excluded = this.getExcluded({\n        access: options.access,\n        modelName: detective(model, options.populate[i].path),\n      });\n\n      if (hasProperty(item, options.populate[i].path)) {\n        this.filterItem(\n          getProperty(item, options.populate[i].path) as T,\n          excluded\n        );\n      } else {\n        const pathToArray = options.populate[i].path\n          .split(\".\")\n          .slice(0, -1)\n          .join(\".\");\n\n        if (hasProperty(item, pathToArray)) {\n          const array = getProperty(item, pathToArray);\n          const pathToObject = options.populate[i].path\n            .split(\".\")\n            .slice(-1)\n            .join(\".\");\n\n          if (Array.isArray(array)) {\n            this.filterItem(\n              // @ts-expect-error this is fine 🐶🔥\n              array.map((element) => getProperty(element, pathToObject)),\n              excluded\n            );\n          }\n        }\n      }\n    }\n\n    return item;\n  }\n\n  /**\n   * Removes excluded keys from a document.\n   */\n  filterObject(\n    resource: Record<string, unknown> | Record<string, unknown>[],\n    options: {\n      access: Access;\n      modelName: string;\n      populate?: Exclude<QueryOptions[\"populate\"], string>;\n    }\n  ) {\n    const excluded = this.getExcluded({\n      access: options.access,\n      modelName: options.modelName,\n    });\n\n    const filtered = this.filterItem(resource, excluded);\n\n    if (options?.populate) {\n      this.filterPopulatedItem(filtered, {\n        access: options.access,\n        modelName: options.modelName,\n        populate: options.populate,\n      });\n    }\n\n    return filtered;\n  }\n}\n"
  },
  {
    "path": "src/types.ts",
    "content": "import {\n  ErrorRequestHandler,\n  Request,\n  RequestHandler,\n  Response,\n} from \"express\";\nimport mongoose from \"mongoose\";\n\nexport type Access = \"private\" | \"protected\" | \"public\";\n\nexport type FilteredKeys = {\n  private: string[];\n  protected: string[];\n};\n\nexport type ExcludedMap = Map<\n  string,\n  {\n    filteredKeys: FilteredKeys;\n    model: mongoose.Model<unknown>;\n  }\n>;\n\nexport type OutputFn = (req: Request, res: Response) => void | Promise<void>;\n\nexport type ReadPreference =\n  | \"p\"\n  | \"primary\"\n  | \"pp\"\n  | \"primaryPreferred\"\n  | \"s\"\n  | \"secondary\"\n  | \"sp\"\n  | \"secondaryPreferred\"\n  | \"n\"\n  | \"nearest\";\n\nexport type Options = {\n  prefix: `/${string}`;\n  version: `/v${number}`;\n  idProperty: string;\n  restify: boolean;\n  name?: string;\n  allowRegex: boolean;\n  runValidators: boolean;\n  readPreference: ReadPreference;\n  totalCountHeader: boolean | string;\n  private: string[];\n  protected: string[];\n  lean: boolean;\n  limit?: number;\n  findOneAndRemove: boolean;\n  findOneAndUpdate: boolean;\n  upsert: boolean;\n  preMiddleware: RequestHandler | RequestHandler[];\n  preCreate: RequestHandler | RequestHandler[];\n  preRead: RequestHandler | RequestHandler[];\n  preUpdate: RequestHandler | RequestHandler[];\n  preDelete: RequestHandler | RequestHandler[];\n  updateDeep: boolean;\n  access?: (req: Request) => Access | Promise<Access>;\n  contextFilter: (\n    model: mongoose.Model<unknown>,\n    req: Request,\n    done: (\n      query: mongoose.Model<unknown> | mongoose.Query<unknown, unknown>\n    ) => void\n  ) => void;\n  postCreate?: RequestHandler | RequestHandler[];\n  postRead?: RequestHandler | RequestHandler[];\n  postUpdate?: RequestHandler | RequestHandler[];\n  postDelete?: RequestHandler | RequestHandler[];\n  outputFn: OutputFn;\n  postProcess?: (req: Request, res: Response) => void;\n  onError: ErrorRequestHandler;\n  modelFactory?: {\n    getModel: (req: Request) => mongoose.Model<unknown>;\n  };\n};\n"
  },
  {
    "path": "src/weedout.ts",
    "content": "export function weedout(obj: Record<string, unknown>, path: string) {\n  const keys = path.split(\".\");\n\n  for (let i = 0, length = keys.length; i < length; i++) {\n    if (Array.isArray(obj)) {\n      for (let j = 0; j < obj.length; j++) {\n        weedout(obj[j], keys.slice(1).join(\".\"));\n      }\n    } else if (!obj || typeof obj[keys[i]] === \"undefined\") {\n      return;\n    }\n\n    if (i < keys.length - 1) {\n      // @ts-expect-error this is fine 🐶🔥\n      obj = obj[keys[i]];\n    } else {\n      delete obj[keys[i]];\n    }\n  }\n\n  return obj;\n}\n"
  },
  {
    "path": "test/express.mjs",
    "content": "import bodyParser from \"body-parser\";\nimport express from \"express\";\nimport methodOverride from \"method-override\";\n\nimport accessTests from \"./integration/access.mjs\";\nimport contextFilterTests from \"./integration/contextFilter.mjs\";\nimport createTests from \"./integration/create.mjs\";\nimport deleteTests from \"./integration/delete.mjs\";\nimport hookTests from \"./integration/hooks.mjs\";\nimport middlewareTests from \"./integration/middleware.mjs\";\nimport optionsTests from \"./integration/options.mjs\";\nimport readTests from \"./integration/read.mjs\";\nimport updateTests from \"./integration/update.mjs\";\nimport virtualsTests from \"./integration/virtuals.mjs\";\n\nimport setupDb from \"./integration/setup.mjs\";\n\nconst db = setupDb();\n\nfunction Express() {\n  let app = express();\n  app.use(bodyParser.json());\n  app.use(bodyParser.urlencoded({ extended: true }));\n  app.use(methodOverride());\n  return app;\n}\n\nfunction setup(callback) {\n  db.initialize((err) => {\n    if (err) {\n      return callback(err);\n    }\n\n    db.reset(callback);\n  });\n}\n\nfunction dismantle(app, server, callback) {\n  db.close((err) => {\n    if (err) {\n      return callback(err);\n    }\n\n    if (app.close) {\n      return app.close(callback);\n    }\n\n    server.close(callback);\n  });\n}\n\nfunction runTests(createFn) {\n  describe(createFn.name, () => {\n    createTests(createFn, setup, dismantle);\n    readTests(createFn, setup, dismantle);\n    updateTests(createFn, setup, dismantle);\n    deleteTests(createFn, setup, dismantle);\n    accessTests(createFn, setup, dismantle);\n    contextFilterTests(createFn, setup, dismantle);\n    hookTests(createFn, setup, dismantle);\n    middlewareTests(createFn, setup, dismantle);\n    optionsTests(createFn, setup, dismantle);\n    virtualsTests(createFn, setup, dismantle);\n  });\n}\n\nrunTests(Express);\n"
  },
  {
    "path": "test/integration/access.mjs",
    "content": "import assert from \"assert\";\nimport request from \"request\";\nimport { serve } from \"../../dist/express-restify-mongoose.js\";\n\nimport setupDb from \"./setup.mjs\";\n\nexport default function (createFn, setup, dismantle) {\n  const db = setupDb();\n\n  const testPort = 30023;\n  const testUrl = `http://localhost:${testPort}`;\n  const updateMethods = [\"PATCH\", \"POST\", \"PUT\"];\n\n  describe(\"access\", () => {\n    describe(\"private - include private and protected fields\", () => {\n      let app = createFn();\n      let server;\n      let product;\n      let customer;\n      let invoice;\n      let account;\n      let repeatCustomer;\n      let repeatCustomerInvoice;\n\n      before((done) => {\n        setup((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          serve(app, db.models.RepeatCustomer, {\n            private: [\"job\"],\n            protected: [\"status\"],\n            access: () => {\n              return \"private\";\n            },\n            restify: app.isRestify,\n          });\n\n          serve(app, db.models.Customer, {\n            private: [\n              \"age\",\n              \"favorites.animal\",\n              \"favorites.purchase.number\",\n              \"purchases.number\",\n              \"privateDoes.notExist\",\n            ],\n            protected: [\"comment\", \"favorites.color\", \"protectedDoes.notExist\"],\n            access: () => {\n              return Promise.resolve(\"private\");\n            },\n            restify: app.isRestify,\n          });\n\n          serve(app, db.models.Invoice, {\n            private: [\"amount\"],\n            protected: [\"receipt\"],\n            access: () => {\n              return \"private\";\n            },\n            restify: app.isRestify,\n          });\n\n          serve(app, db.models.Product, {\n            private: [\"department.code\"],\n            protected: [\"price\"],\n            access: () => {\n              return \"private\";\n            },\n            restify: app.isRestify,\n          });\n\n          serve(app, db.models.Account, {\n            private: [\"accountNumber\"],\n            protected: [\"points\"],\n            access: () => {\n              return \"private\";\n            },\n            restify: app.isRestify,\n          });\n\n          server = app.listen(testPort, done);\n        });\n      });\n\n      beforeEach((done) => {\n        db.reset((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          db.models.Product.create({\n            name: \"Bobsleigh\",\n            price: 42,\n            department: {\n              code: 51,\n            },\n          })\n            .then((createdProduct) => {\n              product = createdProduct;\n\n              return db.models.Customer.create({\n                name: \"Bob\",\n                age: 12,\n                comment: \"Boo\",\n                favorites: {\n                  animal: \"Boar\",\n                  color: \"Black\",\n                  purchase: {\n                    item: product._id,\n                    number: 1,\n                  },\n                },\n                purchases: [\n                  {\n                    item: product._id,\n                    number: 2,\n                  },\n                ],\n                returns: [product._id],\n              });\n            })\n            .then((createdCustomer) => {\n              customer = createdCustomer;\n\n              return db.models.Invoice.create({\n                customer: customer._id,\n                amount: 100,\n                receipt: \"A\",\n              });\n            })\n            .then((createdInvoice) => {\n              invoice = createdInvoice;\n\n              return db.models.Account.create({\n                accountNumber: \"123XYZ\",\n                points: 244,\n              });\n            })\n            .then((createdAccount) => {\n              account = createdAccount;\n\n              return db.models.RepeatCustomer.create({\n                account: account._id,\n                name: \"Mike\",\n                visits: 24,\n                status: \"Awesome\",\n                job: \"Hunter\",\n              });\n            })\n            .then((createdRepeatCustomer) => {\n              repeatCustomer = createdRepeatCustomer;\n\n              return db.models.Invoice.create({\n                customer: repeatCustomer._id,\n                amount: 200,\n                receipt: \"B\",\n              });\n            })\n            .then((createdRepeatCustomerInvoice) => {\n              repeatCustomerInvoice = createdRepeatCustomerInvoice;\n            })\n            .then(done)\n            .catch(done);\n        });\n      });\n\n      after((done) => {\n        dismantle(app, server, done);\n      });\n\n      it(\"GET /Customer 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 2);\n            assert.equal(body[0].name, \"Bob\");\n            assert.equal(body[0].age, 12);\n            assert.equal(body[0].comment, \"Boo\");\n            assert.equal(body[0].purchases.length, 1);\n            assert.deepEqual(body[0].favorites, {\n              animal: \"Boar\",\n              color: \"Black\",\n              purchase: {\n                item: product._id.toHexString(),\n                number: 1,\n              },\n            });\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer?distinct=age 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              distinct: \"age\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 1);\n            assert.equal(body[0], 12);\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer?distinct=comment 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              distinct: \"comment\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 1);\n            assert.equal(body[0], \"Boo\");\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer/:id 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.name, \"Bob\");\n            assert.equal(body.age, 12);\n            assert.equal(body.comment, \"Boo\");\n            assert.equal(body.purchases.length, 1);\n            assert.deepEqual(body.favorites, {\n              animal: \"Boar\",\n              color: \"Black\",\n              purchase: {\n                item: product._id.toHexString(),\n                number: 1,\n              },\n            });\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer/:id?distinct=age 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            qs: {\n              distinct: \"age\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 1);\n            assert.equal(body[0], 12);\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer/:id?distinct=comment 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            qs: {\n              distinct: \"comment\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 1);\n            assert.equal(body[0], \"Boo\");\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer?populate=favorites.purchase.item,purchases.item,returns 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              populate: \"favorites.purchase.item,purchases.item,returns\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 2);\n            assert.equal(body[0].name, \"Bob\");\n            assert.equal(body[0].age, 12);\n            assert.equal(body[0].comment, \"Boo\");\n            assert.deepEqual(body[0].favorites, {\n              animal: \"Boar\",\n              color: \"Black\",\n              purchase: {\n                item: {\n                  __v: 0,\n                  _id: product._id.toHexString(),\n                  name: \"Bobsleigh\",\n                  price: 42,\n                  department: {\n                    code: 51,\n                  },\n                },\n                number: 1,\n              },\n            });\n            assert.equal(body[0].purchases.length, 1);\n            assert.ok(body[0].purchases[0].item);\n            assert.equal(\n              body[0].purchases[0].item._id,\n              product._id.toHexString()\n            );\n            assert.equal(body[0].purchases[0].item.name, \"Bobsleigh\");\n            assert.equal(body[0].purchases[0].item.price, 42);\n            assert.deepEqual(body[0].purchases[0].item.department, {\n              code: 51,\n            });\n            assert.equal(body[0].purchases[0].number, 2);\n            assert.equal(body[0].returns.length, 1);\n            assert.equal(body[0].returns[0].name, \"Bobsleigh\");\n            assert.equal(body[0].returns[0].price, 42);\n            assert.deepEqual(body[0].returns[0].department, {\n              code: 51,\n            });\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer/:id?populate=favorites.purchase.item,purchases.item,returns 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            qs: {\n              populate: \"favorites.purchase.item,purchases.item,returns\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.name, \"Bob\");\n            assert.equal(body.age, 12);\n            assert.equal(body.comment, \"Boo\");\n            assert.deepEqual(body.favorites, {\n              animal: \"Boar\",\n              color: \"Black\",\n              purchase: {\n                item: {\n                  __v: 0,\n                  _id: product._id.toHexString(),\n                  name: \"Bobsleigh\",\n                  price: 42,\n                  department: {\n                    code: 51,\n                  },\n                },\n                number: 1,\n              },\n            });\n            assert.equal(body.purchases.length, 1);\n            assert.ok(body.purchases[0].item);\n            assert.equal(body.purchases[0].item._id, product._id.toHexString());\n            assert.equal(body.purchases[0].item.name, \"Bobsleigh\");\n            assert.equal(body.purchases[0].item.price, 42);\n            assert.deepEqual(body.purchases[0].item.department, {\n              code: 51,\n            });\n            assert.equal(body.purchases[0].number, 2);\n            assert.equal(body.returns.length, 1);\n            assert.equal(body.returns[0].name, \"Bobsleigh\");\n            assert.equal(body.returns[0].price, 42);\n            assert.deepEqual(body.returns[0].department, {\n              code: 51,\n            });\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Invoice?populate=customer 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Invoice`,\n            qs: {\n              populate: \"customer\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 2);\n            assert.ok(body[0].customer);\n            assert.equal(body[0].amount, 100);\n            assert.equal(body[0].receipt, \"A\");\n            assert.equal(body[0].customer.name, \"Bob\");\n            assert.equal(body[0].customer.age, 12);\n            assert.equal(body[0].customer.comment, \"Boo\");\n            assert.equal(body[0].customer.purchases.length, 1);\n            assert.deepEqual(body[0].customer.favorites, {\n              animal: \"Boar\",\n              color: \"Black\",\n              purchase: {\n                item: product._id.toHexString(),\n                number: 1,\n              },\n            });\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Invoice/:id?populate=customer 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Invoice/${invoice._id}`,\n            qs: {\n              populate: \"customer\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.ok(body.customer);\n            assert.equal(body.amount, 100);\n            assert.equal(body.receipt, \"A\");\n            assert.equal(body.customer.name, \"Bob\");\n            assert.equal(body.customer.age, 12);\n            assert.equal(body.customer.comment, \"Boo\");\n            assert.equal(body.customer.purchases.length, 1);\n            assert.deepEqual(body.customer.favorites, {\n              animal: \"Boar\",\n              color: \"Black\",\n              purchase: {\n                item: product._id.toHexString(),\n                number: 1,\n              },\n            });\n            done();\n          }\n        );\n      });\n\n      updateMethods.forEach((method) => {\n        it(`${method} /Customer/:id - saves all fields`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${customer._id}`,\n              json: {\n                name: \"John\",\n                age: 24,\n                comment: \"Jumbo\",\n                favorites: {\n                  animal: \"Jaguar\",\n                  color: \"Jade\",\n                  purchase: {\n                    number: 2,\n                  },\n                },\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.name, \"John\");\n              assert.equal(body.age, 24);\n              assert.equal(body.comment, \"Jumbo\");\n              assert.equal(body.purchases.length, 1);\n              assert.deepEqual(body.favorites, {\n                animal: \"Jaguar\",\n                color: \"Jade\",\n                purchase: {\n                  item: product._id.toHexString(),\n                  number: 2,\n                },\n              });\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Customer/:id - saves all fields (falsy values)`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${customer._id}`,\n              json: {\n                age: 0,\n                comment: \"\",\n                favorites: {\n                  animal: \"\",\n                  color: \"\",\n                  purchase: {\n                    number: 0,\n                  },\n                },\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.name, \"Bob\");\n              assert.equal(body.age, 0);\n              assert.equal(body.comment, \"\");\n              assert.equal(body.purchases.length, 1);\n              assert.deepEqual(body.favorites, {\n                animal: \"\",\n                color: \"\",\n                purchase: {\n                  item: product._id.toHexString(),\n                  number: 0,\n                },\n              });\n              done();\n            }\n          );\n        });\n      });\n\n      it(\"GET /RepeatCustomer 200 - discriminator\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/RepeatCustomer`,\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 1);\n            assert.equal(body[0].account, account._id.toHexString());\n            assert.equal(body[0].name, \"Mike\");\n            assert.equal(body[0].visits, 24);\n            assert.equal(body[0].status, \"Awesome\");\n            assert.equal(body[0].job, \"Hunter\");\n            done();\n          }\n        );\n      });\n\n      it(\"GET /RepeatCustomer/:id?populate=account 200 - populate discriminator field from base schema\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/RepeatCustomer/${repeatCustomer._id}`,\n            qs: {\n              populate: \"account\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.ok(body.account);\n            assert.equal(body.account._id, account._id.toHexString());\n            assert.equal(body.account.accountNumber, \"123XYZ\");\n            assert.equal(body.account.points, 244);\n            assert.equal(body.name, \"Mike\");\n            assert.equal(body.visits, 24);\n            assert.equal(body.status, \"Awesome\");\n            assert.equal(body.job, \"Hunter\");\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Invoice/:id?populate=customer 200 - populated discriminator\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Invoice/${repeatCustomerInvoice._id}`,\n            qs: {\n              populate: \"customer\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.ok(body.customer);\n            assert.equal(body.amount, 200);\n            assert.equal(body.receipt, \"B\");\n            assert.equal(body.customer.name, \"Mike\");\n            assert.equal(body.customer.visits, 24);\n            assert.equal(body.customer.status, \"Awesome\");\n            assert.equal(body.customer.job, \"Hunter\");\n            done();\n          }\n        );\n      });\n    });\n\n    describe(\"protected - exclude private fields and include protected fields\", () => {\n      let app = createFn();\n      let server;\n      let product;\n      let customer;\n      let invoice;\n      let account;\n      let repeatCustomer;\n      let repeatCustomerInvoice;\n\n      before((done) => {\n        setup((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          serve(app, db.models.RepeatCustomer, {\n            private: [\"job\"],\n            protected: [\"status\"],\n            access: () => {\n              return \"protected\";\n            },\n            restify: app.isRestify,\n          });\n\n          serve(app, db.models.Customer, {\n            private: [\n              \"age\",\n              \"favorites.animal\",\n              \"favorites.purchase.number\",\n              \"purchases.number\",\n              \"privateDoes.notExist\",\n            ],\n            protected: [\"comment\", \"favorites.color\", \"protectedDoes.notExist\"],\n            access: () => {\n              return Promise.resolve(\"protected\");\n            },\n            restify: app.isRestify,\n          });\n\n          serve(app, db.models.Invoice, {\n            private: [\"amount\"],\n            protected: [\"receipt\"],\n            access: () => {\n              return \"protected\";\n            },\n            restify: app.isRestify,\n          });\n\n          serve(app, db.models.Product, {\n            private: [\"department.code\"],\n            protected: [\"price\"],\n            access: () => {\n              return \"protected\";\n            },\n            restify: app.isRestify,\n          });\n\n          serve(app, db.models.Account, {\n            private: [\"accountNumber\"],\n            protected: [\"points\"],\n            access: () => {\n              return \"protected\";\n            },\n            restify: app.isRestify,\n          });\n\n          server = app.listen(testPort, done);\n        });\n      });\n\n      beforeEach((done) => {\n        db.reset((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          db.models.Product.create({\n            name: \"Bobsleigh\",\n            price: 42,\n            department: {\n              code: 51,\n            },\n          })\n            .then((createdProduct) => {\n              product = createdProduct;\n\n              return db.models.Customer.create({\n                name: \"Bob\",\n                age: 12,\n                comment: \"Boo\",\n                favorites: {\n                  animal: \"Boar\",\n                  color: \"Black\",\n                  purchase: {\n                    item: product._id,\n                    number: 1,\n                  },\n                },\n                purchases: [\n                  {\n                    item: product._id,\n                    number: 2,\n                  },\n                ],\n                returns: [product._id],\n              });\n            })\n            .then((createdCustomer) => {\n              customer = createdCustomer;\n\n              return db.models.Invoice.create({\n                customer: customer._id,\n                amount: 100,\n                receipt: \"A\",\n              });\n            })\n            .then((createdInvoice) => {\n              invoice = createdInvoice;\n\n              return db.models.Account.create({\n                accountNumber: \"123XYZ\",\n                points: 244,\n              });\n            })\n            .then((createdAccount) => {\n              account = createdAccount;\n\n              return db.models.RepeatCustomer.create({\n                account: account._id,\n                name: \"Mike\",\n                visits: 24,\n                status: \"Awesome\",\n                job: \"Hunter\",\n              });\n            })\n            .then((createdRepeatCustomer) => {\n              repeatCustomer = createdRepeatCustomer;\n\n              return db.models.Invoice.create({\n                customer: repeatCustomer._id,\n                amount: 200,\n                receipt: \"B\",\n              });\n            })\n            .then((createdRepeatCustomerInvoice) => {\n              repeatCustomerInvoice = createdRepeatCustomerInvoice;\n            })\n            .then(done)\n            .catch(done);\n        });\n      });\n\n      after((done) => {\n        dismantle(app, server, done);\n      });\n\n      it(\"GET /Customer 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 2);\n            assert.equal(body[0].name, \"Bob\");\n            assert.equal(body[0].age, undefined);\n            assert.equal(body[0].comment, \"Boo\");\n            assert.deepEqual(body[0].favorites, {\n              color: \"Black\",\n              purchase: {\n                item: product._id.toHexString(),\n              },\n            });\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer?distinct=age 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              distinct: \"age\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 0);\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer?distinct=comment 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              distinct: \"comment\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 1);\n            assert.equal(body[0], \"Boo\");\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer/:id 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.name, \"Bob\");\n            assert.equal(body.age, undefined);\n            assert.equal(body.comment, \"Boo\");\n            assert.deepEqual(body.favorites, {\n              color: \"Black\",\n              purchase: {\n                item: product._id.toHexString(),\n              },\n            });\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer/:id?distinct=age 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            qs: {\n              distinct: \"age\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 0);\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer/:id?distinct=comment 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            qs: {\n              distinct: \"comment\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 1);\n            assert.equal(body[0], \"Boo\");\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer?populate=favorites.purchase.item,purchases.item,returns 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              populate: \"favorites.purchase.item,purchases.item,returns\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 2);\n            assert.equal(body[0].name, \"Bob\");\n            assert.equal(body[0].age, undefined);\n            assert.equal(body[0].comment, \"Boo\");\n            assert.deepEqual(body[0].favorites, {\n              color: \"Black\",\n              purchase: {\n                item: {\n                  __v: 0,\n                  _id: product._id.toHexString(),\n                  name: \"Bobsleigh\",\n                  price: 42,\n                  department: {},\n                },\n              },\n            });\n            assert.equal(body[0].purchases.length, 1);\n            assert.ok(body[0].purchases[0].item);\n            assert.equal(\n              body[0].purchases[0].item._id,\n              product._id.toHexString()\n            );\n            assert.equal(body[0].purchases[0].item.name, \"Bobsleigh\");\n            assert.equal(body[0].purchases[0].item.price, 42);\n            assert.deepEqual(body[0].purchases[0].item.department, {});\n            assert.equal(body[0].purchases[0].number, undefined);\n            assert.equal(body[0].returns.length, 1);\n            assert.equal(body[0].returns[0].name, \"Bobsleigh\");\n            assert.equal(body[0].returns[0].price, 42);\n            assert.deepEqual(body[0].returns[0].department, {});\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer/:id?populate=favorites.purchase.item,purchases.item,returns 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            qs: {\n              populate: \"favorites.purchase.item,purchases.item,returns\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.name, \"Bob\");\n            assert.equal(body.age, undefined);\n            assert.equal(body.comment, \"Boo\");\n            assert.deepEqual(body.favorites, {\n              color: \"Black\",\n              purchase: {\n                item: {\n                  __v: 0,\n                  _id: product._id.toHexString(),\n                  name: \"Bobsleigh\",\n                  price: 42,\n                  department: {},\n                },\n              },\n            });\n            assert.equal(body.purchases.length, 1);\n            assert.ok(body.purchases[0].item);\n            assert.equal(body.purchases[0].item._id, product._id.toHexString());\n            assert.equal(body.purchases[0].item.name, \"Bobsleigh\");\n            assert.equal(body.purchases[0].item.price, 42);\n            assert.deepEqual(body.purchases[0].item.department, {});\n            assert.equal(body.purchases[0].number, undefined);\n            assert.equal(body.returns.length, 1);\n            assert.equal(body.returns[0].name, \"Bobsleigh\");\n            assert.equal(body.returns[0].price, 42);\n            assert.deepEqual(body.returns[0].department, {});\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Invoice?populate=customer 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Invoice`,\n            qs: {\n              populate: \"customer\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 2);\n            assert.ok(body[0].customer);\n            assert.equal(body[0].amount, undefined);\n            assert.equal(body[0].receipt, \"A\");\n            assert.equal(body[0].customer.name, \"Bob\");\n            assert.equal(body[0].customer.age, undefined);\n            assert.equal(body[0].customer.comment, \"Boo\");\n            assert.deepEqual(body[0].customer.favorites, {\n              color: \"Black\",\n              purchase: {\n                item: product._id.toHexString(),\n              },\n            });\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Invoice/:id?populate=customer 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Invoice/${invoice._id}`,\n            qs: {\n              populate: \"customer\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.ok(body.customer);\n            assert.equal(body.amount, undefined);\n            assert.equal(body.receipt, \"A\");\n            assert.equal(body.customer.name, \"Bob\");\n            assert.equal(body.customer.age, undefined);\n            assert.equal(body.customer.comment, \"Boo\");\n            assert.deepEqual(body.customer.favorites, {\n              color: \"Black\",\n              purchase: {\n                item: product._id.toHexString(),\n              },\n            });\n            done();\n          }\n        );\n      });\n\n      updateMethods.forEach((method) => {\n        it(`${method} /Customer/:id - saves protected and public fields`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${customer._id}`,\n              json: {\n                name: \"John\",\n                age: 24,\n                comment: \"Jumbo\",\n                favorites: {\n                  animal: \"Jaguar\",\n                  color: \"Jade\",\n                  purchase: {\n                    number: 2,\n                  },\n                },\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.name, \"John\");\n              assert.equal(body.age, undefined);\n              assert.equal(body.comment, \"Jumbo\");\n              assert.deepEqual(body.favorites, {\n                color: \"Jade\",\n                purchase: {\n                  item: product._id.toHexString(),\n                },\n              });\n\n              db.models.Customer.findById(customer._id)\n                .then((customer) => {\n                  assert.equal(customer.age, 12);\n                  assert.deepEqual(customer.favorites.toObject(), {\n                    animal: \"Boar\",\n                    color: \"Jade\",\n                    purchase: {\n                      item: product._id,\n                      number: 1,\n                    },\n                  });\n                  done();\n                })\n                .catch(done);\n            }\n          );\n        });\n\n        it(`${method} /Customer/:id - saves protected and public fields (falsy values)`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${customer._id}`,\n              json: {\n                age: 0,\n                comment: \"\",\n                favorites: {\n                  animal: \"\",\n                  color: \"\",\n                  purchase: {\n                    number: 0,\n                  },\n                },\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.name, \"Bob\");\n              assert.equal(body.age, undefined);\n              assert.equal(body.comment, \"\");\n              assert.deepEqual(body.favorites, {\n                color: \"\",\n                purchase: {\n                  item: product._id.toHexString(),\n                },\n              });\n\n              db.models.Customer.findById(customer._id)\n                .then((foundCustomer) => {\n                  assert.equal(foundCustomer.age, 12);\n                  assert.deepEqual(foundCustomer.favorites.toObject(), {\n                    animal: \"Boar\",\n                    color: \"\",\n                    purchase: {\n                      item: product._id,\n                      number: 1,\n                    },\n                  });\n                  done();\n                })\n                .catch(done);\n\n            \n            }\n          );\n        });\n      });\n\n      it(\"GET /RepeatCustomer 200 - discriminator\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/RepeatCustomer`,\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body[0].name, \"Mike\");\n            assert.equal(body[0].visits, 24);\n            assert.equal(body[0].status, \"Awesome\");\n            assert.equal(body[0].job, undefined);\n            done();\n          }\n        );\n      });\n\n      it(\"GET /RepeatCustomer/:id?populate=account 200 - populate discriminator field from base schema\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/RepeatCustomer/${repeatCustomer._id}`,\n            qs: {\n              populate: \"account\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.ok(body.account);\n            assert.equal(body.account._id, account._id.toHexString());\n            assert.equal(body.account.accountNumber, undefined);\n            assert.equal(body.account.points, 244);\n            assert.equal(body.name, \"Mike\");\n            assert.equal(body.visits, 24);\n            assert.equal(body.status, \"Awesome\");\n            assert.equal(body.job, undefined);\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Invoice/:id?populate=customer 200 - populated discriminator\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Invoice/${repeatCustomerInvoice._id}`,\n            qs: {\n              populate: \"customer\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.ok(body.customer);\n            assert.equal(body.amount, undefined);\n            assert.equal(body.receipt, \"B\");\n            assert.equal(body.customer.name, \"Mike\");\n            assert.equal(body.customer.visits, 24);\n            assert.equal(body.customer.status, \"Awesome\");\n            assert.equal(body.customer.job, undefined);\n            done();\n          }\n        );\n      });\n    });\n\n    describe(\"public - exclude private and protected fields\", () => {\n      let app = createFn();\n      let server;\n      let product;\n      let customer;\n      let invoice;\n      let account;\n      let repeatCustomer;\n      let repeatCustomerInvoice;\n\n      before((done) => {\n        setup((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          serve(app, db.models.RepeatCustomer, {\n            private: [\"job\"],\n            protected: [\"status\"],\n            restify: app.isRestify,\n          });\n\n          serve(app, db.models.Customer, {\n            private: [\n              \"age\",\n              \"favorites.animal\",\n              \"favorites.purchase.number\",\n              \"purchases.number\",\n              \"privateDoes.notExist\",\n            ],\n            protected: [\"comment\", \"favorites.color\", \"protectedDoes.notExist\"],\n            restify: app.isRestify,\n          });\n\n          serve(app, db.models.Invoice, {\n            private: [\"amount\"],\n            protected: [\"receipt\"],\n            restify: app.isRestify,\n          });\n\n          serve(app, db.models.Product, {\n            private: [\"department.code\"],\n            protected: [\"price\"],\n            restify: app.isRestify,\n          });\n\n          serve(app, db.models.Account, {\n            private: [\"accountNumber\"],\n            protected: [\"points\"],\n            restify: app.isRestify,\n          });\n\n          server = app.listen(testPort, done);\n        });\n      });\n\n      beforeEach((done) => {\n        db.reset((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          db.models.Product.create({\n            name: \"Bobsleigh\",\n            price: 42,\n            department: {\n              code: 51,\n            },\n          })\n            .then((createdProduct) => {\n              product = createdProduct;\n\n              return db.models.Customer.create({\n                name: \"Bob\",\n                age: 12,\n                comment: \"Boo\",\n                favorites: {\n                  animal: \"Boar\",\n                  color: \"Black\",\n                  purchase: {\n                    item: product._id,\n                    number: 1,\n                  },\n                },\n                purchases: [\n                  {\n                    item: product._id,\n                    number: 2,\n                  },\n                ],\n                returns: [product._id],\n              });\n            })\n            .then((createdCustomer) => {\n              customer = createdCustomer;\n\n              return db.models.Invoice.create({\n                customer: customer._id,\n                amount: 100,\n                receipt: \"A\",\n              });\n            })\n            .then((createdInvoice) => {\n              invoice = createdInvoice;\n\n              return db.models.Account.create({\n                accountNumber: \"123XYZ\",\n                points: 244,\n              });\n            })\n            .then((createdAccount) => {\n              account = createdAccount;\n\n              return db.models.RepeatCustomer.create({\n                account: account._id,\n                name: \"Mike\",\n                visits: 24,\n                status: \"Awesome\",\n                job: \"Hunter\",\n              });\n            })\n            .then((createdRepeatCustomer) => {\n              repeatCustomer = createdRepeatCustomer;\n\n              return db.models.Invoice.create({\n                customer: repeatCustomer._id,\n                amount: 200,\n                receipt: \"B\",\n              });\n            })\n            .then((createdRepeatCustomerInvoice) => {\n              repeatCustomerInvoice = createdRepeatCustomerInvoice;\n            })\n            .then(done)\n            .catch(done);\n        });\n      });\n\n      after((done) => {\n        dismantle(app, server, done);\n      });\n\n      it(\"GET /Customer 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 2);\n            assert.equal(body[0].name, \"Bob\");\n            assert.equal(body[0].age, undefined);\n            assert.equal(body[0].comment, undefined);\n            assert.equal(body[0].purchases.length, 1);\n            assert.deepEqual(body[0].favorites, {\n              purchase: {\n                item: product._id.toHexString(),\n              },\n            });\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer?distinct=age 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              distinct: \"age\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 0);\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer?distinct=comment 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              distinct: \"comment\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 0);\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer/:id 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.name, \"Bob\");\n            assert.equal(body.age, undefined);\n            assert.equal(body.comment, undefined);\n            assert.equal(body.purchases.length, 1);\n            assert.deepEqual(body.favorites, {\n              purchase: {\n                item: product._id.toHexString(),\n              },\n            });\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer/:id?distinct=age 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            qs: {\n              distinct: \"age\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 0);\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer/:id?distinct=comment 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            qs: {\n              distinct: \"comment\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 0);\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer?populate=favorites.purchase.item,purchases.item,returns 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              populate: \"favorites.purchase.item,purchases.item,returns\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 2);\n            assert.equal(body[0].name, \"Bob\");\n            assert.equal(body[0].age, undefined);\n            assert.equal(body[0].comment, undefined);\n            assert.deepEqual(body[0].favorites, {\n              purchase: {\n                item: {\n                  __v: 0,\n                  _id: product._id.toHexString(),\n                  name: \"Bobsleigh\",\n                  department: {},\n                },\n              },\n            });\n            assert.equal(body[0].purchases.length, 1);\n            assert.ok(body[0].purchases[0].item);\n            assert.equal(\n              body[0].purchases[0].item._id,\n              product._id.toHexString()\n            );\n            assert.equal(body[0].purchases[0].item.name, \"Bobsleigh\");\n            assert.equal(body[0].purchases[0].item.price, undefined);\n            assert.deepEqual(body[0].purchases[0].item.department, {});\n            assert.equal(body[0].purchases[0].number, undefined);\n            assert.equal(body[0].returns.length, 1);\n            assert.equal(body[0].returns[0].name, \"Bobsleigh\");\n            assert.equal(body[0].returns[0].price, undefined);\n            assert.deepEqual(body[0].returns[0].department, {});\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer/:id?populate=favorites.purchase.item,purchases.item,returns 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            qs: {\n              populate: \"favorites.purchase.item,purchases.item,returns\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.name, \"Bob\");\n            assert.equal(body.age, undefined);\n            assert.equal(body.comment, undefined);\n            assert.deepEqual(body.favorites, {\n              purchase: {\n                item: {\n                  __v: 0,\n                  _id: product._id.toHexString(),\n                  name: \"Bobsleigh\",\n                  department: {},\n                },\n              },\n            });\n            assert.equal(body.purchases.length, 1);\n            assert.ok(body.purchases[0].item);\n            assert.equal(body.purchases[0].item._id, product._id.toHexString());\n            assert.equal(body.purchases[0].item.name, \"Bobsleigh\");\n            assert.equal(body.purchases[0].item.price, undefined);\n            assert.deepEqual(body.purchases[0].item.department, {});\n            assert.equal(body.purchases[0].number, undefined);\n            assert.equal(body.returns.length, 1);\n            assert.equal(body.returns[0].name, \"Bobsleigh\");\n            assert.equal(body.returns[0].price, undefined);\n            assert.deepEqual(body.returns[0].department, {});\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Invoice?populate=customer 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Invoice`,\n            qs: {\n              populate: \"customer\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 2);\n            assert.ok(body[0].customer);\n            assert.equal(body[0].amount, undefined);\n            assert.equal(body[0].receipt, undefined);\n            assert.equal(body[0].customer.name, \"Bob\");\n            assert.equal(body[0].customer.age, undefined);\n            assert.equal(body[0].customer.comment, undefined);\n            assert.deepEqual(body[0].customer.favorites, {\n              purchase: {\n                item: product._id.toHexString(),\n              },\n            });\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Invoice/:id?populate=customer 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Invoice/${invoice._id}`,\n            qs: {\n              populate: \"customer\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.ok(body.customer);\n            assert.equal(body.amount, undefined);\n            assert.equal(body.receipt, undefined);\n            assert.equal(body.customer.name, \"Bob\");\n            assert.equal(body.customer.age, undefined);\n            assert.equal(body.customer.comment, undefined);\n            assert.deepEqual(body.customer.favorites, {\n              purchase: {\n                item: product._id.toHexString(),\n              },\n            });\n            done();\n          }\n        );\n      });\n\n      updateMethods.forEach((method) => {\n        it(`${method} /Customer/:id - saves public fields`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${customer._id}`,\n              json: {\n                name: \"John\",\n                age: 24,\n                comment: \"Jumbo\",\n                favorites: {\n                  animal: \"Jaguar\",\n                  color: \"Jade\",\n                  purchase: {\n                    number: 2,\n                  },\n                },\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.name, \"John\");\n              assert.equal(body.age, undefined);\n              assert.equal(body.comment, undefined);\n              assert.deepEqual(body.favorites, {\n                purchase: {\n                  item: product._id.toHexString(),\n                },\n              });\n\n              db.models.Customer.findById(customer._id)\n              .then((foundCustomer) => {\n                assert.equal(foundCustomer.age, 12);\n                assert.equal(foundCustomer.comment, \"Boo\");\n                assert.deepEqual(foundCustomer.favorites.toObject(), {\n                  animal: \"Boar\",\n                  color: \"Black\",\n                  purchase: {\n                    item: product._id,\n                    number: 1,\n                  },\n                });\n                done();\n              })\n              .catch(done);            \n            }\n          );\n        });\n\n        it(`${method} /Customer/:id - saves public fields (falsy values)`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${customer._id}`,\n              json: {\n                age: 0,\n                comment: \"\",\n                favorites: {\n                  animal: \"\",\n                  color: \"\",\n                  purchase: {\n                    number: 0,\n                  },\n                },\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.name, \"Bob\");\n              assert.equal(body.age, undefined);\n              assert.equal(body.comment, undefined);\n              assert.deepEqual(body.favorites, {\n                purchase: {\n                  item: product._id.toHexString(),\n                },\n              });\n\n              db.models.Customer.findById(customer._id)\n              .then((foundCustomer) => {\n                assert.equal(foundCustomer.age, 12);\n                assert.equal(foundCustomer.comment, \"Boo\");\n                assert.deepEqual(foundCustomer.favorites.toObject(), {\n                  animal: \"Boar\",\n                  color: \"Black\",\n                  purchase: {\n                    item: product._id,\n                    number: 1,\n                  },\n                });\n                done();\n              })\n              .catch(done);            \n            }\n          );\n        });\n      });\n\n      it(\"GET /RepeatCustomer 200 - discriminator\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/RepeatCustomer`,\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body[0].name, \"Mike\");\n            assert.equal(body[0].visits, 24);\n            assert.equal(body[0].status, undefined);\n            assert.equal(body[0].job, undefined);\n            done();\n          }\n        );\n      });\n\n      it(\"GET /RepeatCustomer/:id?populate=account 200 - populate discriminator field from base schema\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/RepeatCustomer/${repeatCustomer._id}`,\n            qs: {\n              populate: \"account\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.ok(body.account);\n            assert.equal(body.account._id, account._id.toHexString());\n            assert.equal(body.account.accountNumber, undefined);\n            assert.equal(body.account.points, undefined);\n            assert.equal(body.name, \"Mike\");\n            assert.equal(body.visits, 24);\n            assert.equal(body.status, undefined);\n            assert.equal(body.job, undefined);\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Invoice/:id?populate=customer 200 - populated discriminator\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Invoice/${repeatCustomerInvoice._id}`,\n            qs: {\n              populate: \"customer\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.ok(body.customer);\n            assert.equal(body.amount, undefined);\n            assert.equal(body.receipt, undefined);\n            assert.equal(body.customer.name, \"Mike\");\n            assert.equal(body.customer.visits, 24);\n            assert.equal(body.customer.status, undefined);\n            assert.equal(body.customer.job, undefined);\n            done();\n          }\n        );\n      });\n    });\n\n    describe(\"yields an error\", () => {\n      let app = createFn();\n      let server;\n\n      before((done) => {\n        setup((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          serve(app, db.models.Customer, {\n            access: () => {\n              let err = new Error(\"Something went wrong\");\n              return Promise.reject(err);\n            },\n            restify: app.isRestify,\n          });\n\n          server = app.listen(testPort, done);\n        });\n      });\n\n      after((done) => {\n        dismantle(app, server, done);\n      });\n\n      it(\"GET /Customer 500\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 400);\n            assert.deepEqual(body, {\n              name: \"Error\",\n              message: \"Something went wrong\",\n            });\n            done();\n          }\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "test/integration/contextFilter.mjs",
    "content": "import assert from \"assert\";\nimport request from \"request\";\nimport { serve } from \"../../dist/express-restify-mongoose.js\";\n\nimport setupDb from \"./setup.mjs\";\n\nexport default function (createFn, setup, dismantle) {\n  const db = setupDb();\n\n  const testPort = 30023;\n  const testUrl = `http://localhost:${testPort}`;\n  const updateMethods = [\"PATCH\", \"POST\", \"PUT\"];\n\n  describe(\"contextFilter\", () => {\n    let app = createFn();\n    let server;\n    let customers;\n\n    let contextFilter = function (model, req, done) {\n      done(\n        model.find({\n          name: { $ne: \"Bob\" },\n          age: { $lt: 36 },\n        })\n      );\n    };\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, {\n          contextFilter: contextFilter,\n          restify: app.isRestify,\n        });\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    beforeEach((done) => {\n      db.reset((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        db.models.Customer.create([\n          {\n            name: \"Bob\",\n            age: 12,\n          },\n          {\n            name: \"John\",\n            age: 24,\n          },\n          {\n            name: \"Mike\",\n            age: 36,\n          },\n        ])\n          .then((createdCustomers) => {\n            customers = createdCustomers;\n          })\n          .then(done)\n          .catch(done);\n      });\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"GET /Customer 200 - filtered name and age\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: true,\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          assert.equal(body.length, 1);\n          assert.equal(body[0].name, \"John\");\n          assert.equal(body[0].age, 24);\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer/:id 404 - filtered name\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer/${customers[0]._id}`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 404);\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer/:id/shallow 404 - filtered age\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer/${customers[2]._id}/shallow`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 404);\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer/count 200 - filtered name and age\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer/count`,\n          json: true,\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          assert.equal(body.count, 1);\n          done();\n        }\n      );\n    });\n\n    updateMethods.forEach((method) => {\n      it(`${method} /Customer/:id 200`, (done) => {\n        request(\n          {\n            method,\n            url: `${testUrl}/api/v1/Customer/${customers[1]._id}`,\n            json: {\n              name: \"Johnny\",\n            },\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.name, \"Johnny\");\n            done();\n          }\n        );\n      });\n\n      it(`${method} /Customer/:id 404 - filtered name`, (done) => {\n        request(\n          {\n            method,\n            url: `${testUrl}/api/v1/Customer/${customers[0]._id}`,\n            json: {\n              name: \"Bobby\",\n            },\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 404);\n\n            db.models.Customer.findById(customers[0]._id)\n            .then((foundCustomer) => {\n              assert.notEqual(foundCustomer.name, \"Bobby\");\n              done();\n            })\n            .catch(done);          \n          }\n        );\n      });\n    });\n\n    it(\"DELETE /Customer/:id 200\", (done) => {\n      request.del(\n        {\n          url: `${testUrl}/api/v1/Customer/${customers[1]._id}`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 204);\n\n          db.models.Customer.findById(customers[1]._id)\n          .then((foundCustomer) => {\n            assert.ok(!foundCustomer);\n            done();\n          })\n          .catch(done);        \n        }\n      );\n    });\n\n    it(\"DELETE /Customer/:id 404 - filtered age\", (done) => {\n      request.del(\n        {\n          url: `${testUrl}/api/v1/Customer/${customers[2]._id}`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 404);\n\n          db.models.Customer.findById(customers[2]._id)\n          .then((foundCustomer) => {\n            assert.ok(foundCustomer);\n            assert.equal(foundCustomer.name, \"Mike\");\n            done();\n          })\n          .catch(done);        \n        }\n      );\n    });\n\n    it(\"DELETE /Customer 200 - filtered name and age\", (done) => {\n      request.del(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 204);\n\n          db.models.Customer.countDocuments()\n            .then((count) => {\n              assert.equal(count, 2);\n              done();\n            })\n            .catch(done);\n        }\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "test/integration/create.mjs",
    "content": "import assert from \"assert\";\nimport mongoose from \"mongoose\";\nimport request from \"request\";\nimport { serve } from \"../../dist/express-restify-mongoose.js\";\n\nimport setupDb from \"./setup.mjs\";\n\nexport default function (createFn, setup, dismantle) {\n  const db = setupDb();\n\n  let testPort = 30023;\n  let testUrl = `http://localhost:${testPort}`;\n  let invalidId = \"invalid-id\";\n  let randomId = new mongoose.Types.ObjectId().toHexString();\n\n  describe(\"Create documents\", () => {\n    let app = createFn();\n    let server;\n    let customer, product;\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, {\n          restify: app.isRestify,\n        });\n\n        serve(app, db.models.Invoice, {\n          restify: app.isRestify,\n        });\n\n        serve(app, db.models.Product, {\n          restify: app.isRestify,\n        });\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    beforeEach((done) => {\n      db.reset((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        Promise.all([\n          db.models.Customer.create({\n            name: \"Bob\",\n          }),\n          db.models.Product.create({\n            name: \"Bobsleigh\",\n          }),\n        ])\n          .then(([createdCustomer, createdProduct]) => {\n            customer = createdCustomer;\n            product = createdProduct;\n          })\n          .then(done)\n          .catch(done);\n      });\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"POST /Customer 201\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: {\n            name: \"John\",\n          },\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 201);\n          assert.ok(body._id);\n          assert.equal(body.name, \"John\");\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Customer 201 - generate _id (undefined)\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: {\n            _id: undefined,\n            name: \"John\",\n          },\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 201);\n          assert.ok(body._id);\n          assert.equal(body.name, \"John\");\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Customer 201 - generate _id (null)\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: {\n            _id: null,\n            name: \"John\",\n          },\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 201);\n          assert.ok(body._id);\n          assert.equal(body.name, \"John\");\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Customer 201 - use provided _id\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: {\n            _id: randomId,\n            name: \"John\",\n          },\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 201);\n          assert.ok(body._id);\n          assert.ok(body._id === randomId);\n          assert.equal(body.name, \"John\");\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Customer 201 - ignore __v\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: {\n            __v: \"1\",\n            name: \"John\",\n          },\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 201);\n          assert.ok(body._id);\n          assert.ok(body.__v === 0);\n          assert.equal(body.name, \"John\");\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Customer 201 - array\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: [\n            {\n              name: \"John\",\n            },\n            {\n              name: \"Mike\",\n            },\n          ],\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 201);\n          assert.ok(Array.isArray(body));\n          assert.equal(body.length, 2);\n          assert.ok(body[0]._id);\n          assert.equal(body[0].name, \"John\");\n          assert.ok(body[1]._id);\n          assert.equal(body[1].name, \"Mike\");\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Customer 400 - validation error\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: {},\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 400);\n          assert.equal(body.name, \"ValidationError\");\n          assert.deepEqual(body, {\n            name: \"ValidationError\",\n            message:\n              \"Customer validation failed: name: Path `name` is required.\",\n            _message: \"Customer validation failed\",\n            errors: {\n              name: {\n                kind: \"required\",\n                message: \"Path `name` is required.\",\n                name: \"ValidatorError\",\n                path: \"name\",\n                properties: {\n                  message: \"Path `name` is required.\",\n                  path: \"name\",\n                  type: \"required\",\n                },\n              },\n            },\n          });\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Customer 400 - missing content type\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 400);\n          assert.deepEqual(JSON.parse(body), {\n            name: \"Error\",\n            message: \"missing_content_type\",\n          });\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Customer 400 - invalid content type\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          formData: {},\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 400);\n          assert.deepEqual(JSON.parse(body), {\n            name: \"Error\",\n            message: \"invalid_content_type\",\n          });\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Invoice 201 - referencing customer and product ids as strings\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Invoice`,\n          json: {\n            customer: customer._id.toHexString(),\n            products: product._id.toHexString(),\n            amount: 42,\n          },\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 201);\n          assert.ok(body._id);\n          assert.equal(body.customer, customer._id);\n          assert.equal(body.amount, 42);\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Invoice 201 - referencing customer and products ids as strings\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Invoice`,\n          json: {\n            customer: customer._id.toHexString(),\n            products: [product._id.toHexString(), product._id.toHexString()],\n            amount: 42,\n          },\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 201);\n          assert.ok(body._id);\n          assert.equal(body.customer, customer._id);\n          assert.equal(body.amount, 42);\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Invoice 201 - referencing customer and product ids\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Invoice`,\n          json: {\n            customer: customer._id,\n            products: product._id,\n            amount: 42,\n          },\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 201);\n          assert.ok(body._id);\n          assert.equal(body.customer, customer._id);\n          assert.equal(body.amount, 42);\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Invoice 201 - referencing customer and products ids\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Invoice`,\n          json: {\n            customer: customer._id,\n            products: [product._id, product._id],\n            amount: 42,\n          },\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 201);\n          assert.ok(body._id);\n          assert.equal(body.customer, customer._id);\n          assert.equal(body.amount, 42);\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Invoice 201 - referencing customer and product\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Invoice`,\n          json: {\n            customer: customer,\n            products: product,\n            amount: 42,\n          },\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 201);\n          assert.ok(body._id);\n          assert.equal(body.customer, customer._id);\n          assert.equal(body.amount, 42);\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Invoice 201 - referencing customer and products\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Invoice`,\n          json: {\n            customer: customer,\n            products: [product, product],\n            amount: 42,\n          },\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 201);\n          assert.ok(body._id);\n          assert.equal(body.customer, customer._id);\n          assert.equal(body.amount, 42);\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Invoice?populate=customer,products 201 - referencing customer and products\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Invoice`,\n          qs: {\n            populate: \"customer,products\",\n          },\n          json: {\n            customer: customer,\n            products: [product, product],\n            amount: 42,\n          },\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 201);\n          assert.ok(body._id);\n          assert.equal(body.amount, 42);\n          assert.equal(body.customer._id, customer._id);\n          assert.equal(body.customer.name, customer.name);\n          assert.equal(body.products[0]._id, product._id.toHexString());\n          assert.equal(body.products[0].name, product.name);\n          assert.equal(body.products[1]._id, product._id.toHexString());\n          assert.equal(body.products[1].name, product.name);\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Invoice 400 - referencing invalid customer and products ids\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Invoice`,\n          json: {\n            customer: invalidId,\n            products: [invalidId, invalidId],\n            amount: 42,\n          },\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 400);\n          delete body.message;\n          delete body.errors.customer.message;\n          assert.deepEqual(body, {\n            name: \"ValidationError\",\n            _message: \"Invoice validation failed\",\n            errors: {\n              customer: {\n                kind: \"ObjectId\",\n                name: \"CastError\",\n                path: \"customer\",\n                stringValue: '\"invalid-id\"',\n                value: \"invalid-id\",\n                valueType: \"string\",\n              },\n              \"products.0\": {\n                kind: \"[ObjectId]\",\n                message:\n                  'Cast to [ObjectId] failed for value \"[ \\'invalid-id\\', \\'invalid-id\\' ]\" (type string) at path \"products.0\" because of \"CastError\"',\n                name: \"CastError\",\n                path: \"products.0\",\n                stringValue: \"\\\"[ 'invalid-id', 'invalid-id' ]\\\"\",\n                value: \"[ 'invalid-id', 'invalid-id' ]\",\n                valueType: \"string\",\n              },\n            },\n          });\n          done();\n        }\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "test/integration/delete.mjs",
    "content": "import assert from \"assert\";\nimport mongoose from \"mongoose\";\nimport request from \"request\";\nimport { serve } from \"../../dist/express-restify-mongoose.js\";\n\nimport setupDb from \"./setup.mjs\";\n\nexport default function (createFn, setup, dismantle) {\n  const db = setupDb();\n\n  const testPort = 30023;\n  const testUrl = `http://localhost:${testPort}`;\n  const invalidId = \"invalid-id\";\n  const randomId = new mongoose.Types.ObjectId().toHexString();\n\n  describe(\"Delete documents\", () => {\n    describe(\"findOneAndRemove: true\", () => {\n      let app = createFn();\n      let server;\n      let customer;\n\n      before((done) => {\n        setup((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          serve(app, db.models.Customer, {\n            findOneAndRemove: true,\n            restify: app.isRestify,\n          });\n\n          server = app.listen(testPort, done);\n        });\n      });\n\n      beforeEach((done) => {\n        db.reset((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          db.models.Customer.create([\n            {\n              name: \"Bob\",\n            },\n            {\n              name: \"John\",\n            },\n            {\n              name: \"Mike\",\n            },\n          ])\n            .then((createdCustomers) => {\n              customer = createdCustomers[0];\n            })\n            .then(done)\n            .catch(done);\n        });\n      });\n\n      after((done) => {\n        dismantle(app, server, done);\n      });\n\n      it(\"DELETE /Customer 204 - no id\", (done) => {\n        request.del(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 204);\n            done();\n          }\n        );\n      });\n\n      it(\"DELETE /Customer/:id 204 - created id\", (done) => {\n        request.del(\n          {\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 204);\n            done();\n          }\n        );\n      });\n\n      it(\"DELETE /Customer/:id 404 - invalid id\", (done) => {\n        request.del(\n          {\n            url: `${testUrl}/api/v1/Customer/${invalidId}`,\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 404);\n            done();\n          }\n        );\n      });\n\n      it(\"DELETE /Customer/:id 404 - random id\", (done) => {\n        request.del(\n          {\n            url: `${testUrl}/api/v1/Customer/${randomId}`,\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 404);\n            done();\n          }\n        );\n      });\n\n      it('DELETE /Customer?query={\"name\":\"John\"} 200 - exact match', (done) => {\n        request.del(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              query: JSON.stringify({\n                name: \"John\",\n              }),\n            },\n            json: true,\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 204);\n\n            db.models.Customer.find({})\n            .then((customers) => {\n              assert.equal(customers.length, 2);\n              customers.forEach((customer) => {\n                assert.ok(customer.name !== \"John\");\n              });\n              done();\n            })\n            .catch(done);\n          }\n        );\n      });\n    });\n\n    describe(\"findOneAndRemove: false\", () => {\n      let app = createFn();\n      let server;\n      let customer;\n\n      before((done) => {\n        setup((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          serve(app, db.models.Customer, {\n            findOneAndRemove: false,\n            restify: app.isRestify,\n          });\n\n          server = app.listen(testPort, done);\n        });\n      });\n\n      beforeEach((done) => {\n        db.reset((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          db.models.Customer.create([\n            {\n              name: \"Bob\",\n            },\n            {\n              name: \"John\",\n            },\n            {\n              name: \"Mike\",\n            },\n          ])\n            .then((createdCustomers) => {\n              customer = createdCustomers[0];\n            })\n            .then(done)\n            .catch(done);\n        });\n      });\n\n      after((done) => {\n        dismantle(app, server, done);\n      });\n\n      it(\"DELETE /Customer 204 - no id\", (done) => {\n        request.del(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 204);\n            done();\n          }\n        );\n      });\n\n      it(\"DELETE /Customer/:id 204 - created id\", (done) => {\n        request.del(\n          {\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 204);\n            done();\n          }\n        );\n      });\n\n      it(\"DELETE /Customer/:id 404 - invalid id\", (done) => {\n        request.del(\n          {\n            url: `${testUrl}/api/v1/Customer/${invalidId}`,\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 404);\n            done();\n          }\n        );\n      });\n\n      it(\"DELETE /Customer/:id 404 - random id\", (done) => {\n        request.del(\n          {\n            url: `${testUrl}/api/v1/Customer/${randomId}`,\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 404);\n            done();\n          }\n        );\n      });\n\n      it('DELETE /Customer?query={\"name\":\"John\"} 200 - exact match', (done) => {\n        request.del(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              query: JSON.stringify({\n                name: \"John\",\n              }),\n            },\n            json: true,\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 204);\n\n            db.models.Customer.find({})\n              .then((customers) => {\n                assert.equal(customers.length, 2);\n            \n                customers.forEach((customer) => {\n                  assert.ok(customer.name !== \"John\");\n                });\n            \n                done();\n              })\n              .catch(done);\n          }\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "test/integration/hooks.mjs",
    "content": "import assert from \"assert\";\nimport request from \"request\";\nimport { serve } from \"../../dist/express-restify-mongoose.js\";\n\nimport setupDb from \"./setup.mjs\";\n\nexport default function (createFn, setup, dismantle) {\n  const db = setupDb();\n\n  let testPort = 30023;\n  let testUrl = `http://localhost:${testPort}`;\n\n  describe(\"Mongoose hooks\", () => {\n    let app = createFn();\n    let server;\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Hook, {\n          restify: app.isRestify,\n        });\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"POST /Hook 201\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Hook`,\n          json: {\n            preSaveError: false,\n            postSaveError: false,\n          },\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 201);\n          assert.ok(body._id);\n          assert.equal(body.preSaveError, false);\n          assert.equal(body.postSaveError, false);\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Hook 400\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Hook`,\n          json: {\n            preSaveError: true,\n            postSaveError: false,\n          },\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 400);\n          assert.deepEqual(body, {\n            name: \"Error\",\n            message: \"AsyncPreSaveError\",\n          });\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Hook 400\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Hook`,\n          json: {\n            preSaveError: false,\n            postSaveError: true,\n          },\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 400);\n          assert.deepEqual(body, {\n            name: \"Error\",\n            message: \"AsyncPostSaveError\",\n          });\n          done();\n        }\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "test/integration/middleware.mjs",
    "content": "import assert from \"assert\";\nimport mongoose from \"mongoose\";\nimport request from \"request\";\nimport sinon from \"sinon\";\nimport { serve } from \"../../dist/express-restify-mongoose.js\";\n\nimport setupDb from \"./setup.mjs\";\n\nexport default function (createFn, setup, dismantle) {\n  const db = setupDb();\n\n  const testPort = 30023;\n  const testUrl = `http://localhost:${testPort}`;\n  const invalidId = \"invalid-id\";\n  const randomId = new mongoose.Types.ObjectId().toHexString();\n  const updateMethods = [\"PATCH\", \"POST\", \"PUT\"];\n\n  describe(\"preMiddleware/Create/Read/Update/Delete - undefined\", () => {\n    let app = createFn();\n    let server;\n    let customer;\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, {\n          restify: app.isRestify,\n        });\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    beforeEach((done) => {\n      db.reset((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        db.models.Customer.create({\n          name: \"Bob\",\n        })\n          .then((createdCustomer) => {\n            customer = createdCustomer;\n          })\n          .then(done)\n          .catch(done);\n      });\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"POST /Customer 201\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: {\n            name: \"John\",\n          },\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 201);\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          done();\n        }\n      );\n    });\n\n    updateMethods.forEach((method) => {\n      it(`${method} /Customer/:id 200`, (done) => {\n        request(\n          {\n            method,\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            json: {\n              name: \"Bobby\",\n            },\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            done();\n          }\n        );\n      });\n    });\n\n    it(\"DELETE /Customer/:id 204\", (done) => {\n      request.del(\n        {\n          url: `${testUrl}/api/v1/Customer/${customer._id}`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 204);\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"preMiddleware\", () => {\n    let app = createFn();\n    let server;\n    let customer;\n    let options = {\n      preMiddleware: sinon.spy((req, res, next) => {\n        next();\n      }),\n      restify: app.isRestify,\n    };\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, options);\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    beforeEach((done) => {\n      db.reset((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        db.models.Customer.create({\n          name: \"Bob\",\n        })\n          .then((createdCustomer) => {\n            customer = createdCustomer;\n          })\n          .then(done)\n          .catch(done);\n      });\n    });\n\n    afterEach(() => {\n      options.preMiddleware.resetHistory();\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"GET /Customer 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          sinon.assert.calledOnce(options.preMiddleware);\n          let args = options.preMiddleware.args[0];\n          assert.equal(args.length, 3);\n          assert.equal(typeof args[2], \"function\");\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer/:id 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          sinon.assert.calledOnce(options.preMiddleware);\n          let args = options.preMiddleware.args[0];\n          assert.equal(args.length, 3);\n          assert.equal(typeof args[2], \"function\");\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Customer 201\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: {\n            name: \"Pre\",\n          },\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 201);\n          sinon.assert.calledOnce(options.preMiddleware);\n          let args = options.preMiddleware.args[0];\n          assert.equal(args.length, 3);\n          assert.equal(typeof args[2], \"function\");\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Customer 400 - not called (missing content type)\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 400);\n          assert.deepEqual(JSON.parse(body), {\n            name: \"Error\",\n            message: \"missing_content_type\",\n          });\n          sinon.assert.notCalled(options.preMiddleware);\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Customer 400 - not called (invalid content type)\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          formData: {},\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 400);\n          assert.deepEqual(JSON.parse(body), {\n            name: \"Error\",\n            message: \"invalid_content_type\",\n          });\n          sinon.assert.notCalled(options.preMiddleware);\n          done();\n        }\n      );\n    });\n\n    updateMethods.forEach((method) => {\n      it(`${method} /Customer/:id 200`, (done) => {\n        request(\n          {\n            method,\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            json: {\n              name: \"Bobby\",\n            },\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            sinon.assert.calledOnce(options.preMiddleware);\n            let args = options.preMiddleware.args[0];\n            assert.equal(args.length, 3);\n            assert.equal(typeof args[2], \"function\");\n            done();\n          }\n        );\n      });\n\n      it(`${method} /Customer/:id 400 - not called (missing content type)`, (done) => {\n        request(\n          {\n            method,\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 400);\n            assert.deepEqual(JSON.parse(body), {\n              name: \"Error\",\n              message: \"missing_content_type\",\n            });\n            sinon.assert.notCalled(options.preMiddleware);\n            done();\n          }\n        );\n      });\n\n      it(`${method} /Customer/:id 400 - not called (invalid content type)`, (done) => {\n        request(\n          {\n            method,\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            formData: {},\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 400);\n            assert.deepEqual(JSON.parse(body), {\n              name: \"Error\",\n              message: \"invalid_content_type\",\n            });\n            sinon.assert.notCalled(options.preMiddleware);\n            done();\n          }\n        );\n      });\n    });\n\n    it(\"DELETE /Customer 204\", (done) => {\n      request.del(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 204);\n          sinon.assert.calledOnce(options.preMiddleware);\n          let args = options.preMiddleware.args[0];\n          assert.equal(args.length, 3);\n          assert.equal(typeof args[2], \"function\");\n          done();\n        }\n      );\n    });\n\n    it(\"DELETE /Customer/:id 204\", (done) => {\n      request.del(\n        {\n          url: `${testUrl}/api/v1/Customer/${customer._id}`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 204);\n          sinon.assert.calledOnce(options.preMiddleware);\n          let args = options.preMiddleware.args[0];\n          assert.equal(args.length, 3);\n          assert.equal(typeof args[2], \"function\");\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"preCreate\", () => {\n    let app = createFn();\n    let server;\n    let options = {\n      preCreate: sinon.spy((req, res, next) => {\n        next();\n      }),\n      restify: app.isRestify,\n    };\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, options);\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    afterEach(() => {\n      options.preCreate.resetHistory();\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"POST /Customer 201\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: {\n            name: \"Bob\",\n          },\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 201);\n          sinon.assert.calledOnce(options.preCreate);\n          let args = options.preCreate.args[0];\n          assert.equal(args.length, 3);\n          assert.equal(args[0].erm.result.name, \"Bob\");\n          assert.equal(args[0].erm.statusCode, 201);\n          assert.equal(typeof args[2], \"function\");\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"preRead\", () => {\n    let app = createFn();\n    let server;\n    let customer;\n    let options = {\n      preRead: sinon.spy((req, res, next) => {\n        next();\n      }),\n      restify: app.isRestify,\n    };\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, options);\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    beforeEach((done) => {\n      db.reset((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        db.models.Customer.create({\n          name: \"Bob\",\n        })\n          .then((createdCustomer) => {\n            customer = createdCustomer;\n          })\n          .then(done)\n          .catch(done);\n      });\n    });\n\n    afterEach(() => {\n      options.preRead.resetHistory();\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"GET /Customer 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          sinon.assert.calledOnce(options.preRead);\n          let args = options.preRead.args[0];\n          assert.equal(args.length, 3);\n          assert.equal(args[0].erm.result[0].name, \"Bob\");\n          assert.equal(args[0].erm.statusCode, 200);\n          assert.equal(typeof args[2], \"function\");\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer/count 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer/count`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          sinon.assert.calledOnce(options.preRead);\n          let args = options.preRead.args[0];\n          assert.equal(args.length, 3);\n          assert.equal(args[0].erm.result.count, 1);\n          assert.equal(args[0].erm.statusCode, 200);\n          assert.equal(typeof args[2], \"function\");\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer/:id 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer/${customer._id}`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          sinon.assert.calledOnce(options.preRead);\n          let args = options.preRead.args[0];\n          assert.equal(args.length, 3);\n          assert.equal(args[0].erm.result.name, \"Bob\");\n          assert.equal(args[0].erm.statusCode, 200);\n          assert.equal(typeof args[2], \"function\");\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer/:id/shallow 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer/${customer._id}/shallow`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          sinon.assert.calledOnce(options.preRead);\n          let args = options.preRead.args[0];\n          assert.equal(args.length, 3);\n          assert.equal(args[0].erm.result.name, \"Bob\");\n          assert.equal(args[0].erm.statusCode, 200);\n          assert.equal(typeof args[2], \"function\");\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"preUpdate\", () => {\n    let app = createFn();\n    let server;\n    let customer;\n    let options = {\n      preUpdate: sinon.spy((req, res, next) => {\n        next();\n      }),\n      restify: app.isRestify,\n    };\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, options);\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    beforeEach((done) => {\n      db.reset((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        db.models.Customer.create({\n          name: \"Bob\",\n        })\n          .then((createdCustomer) => {\n            customer = createdCustomer;\n          })\n          .then(done)\n          .catch(done);\n      });\n    });\n\n    afterEach(() => {\n      options.preUpdate.resetHistory();\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    updateMethods.forEach((method) => {\n      it(`${method} /Customer/:id 200`, (done) => {\n        request(\n          {\n            method,\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            json: {\n              name: \"Bobby\",\n            },\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            sinon.assert.calledOnce(options.preUpdate);\n            let args = options.preUpdate.args[0];\n            assert.equal(args.length, 3);\n            assert.equal(args[0].erm.result.name, \"Bobby\");\n            assert.equal(args[0].erm.statusCode, 200);\n            assert.equal(typeof args[2], \"function\");\n            done();\n          }\n        );\n      });\n\n      it(`${method} /Customer/:id 400 - not called (missing content type)`, (done) => {\n        request(\n          {\n            method,\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 400);\n            assert.deepEqual(JSON.parse(body), {\n              name: \"Error\",\n              message: \"missing_content_type\",\n            });\n            sinon.assert.notCalled(options.preUpdate);\n            done();\n          }\n        );\n      });\n\n      it(`${method} /Customer/:id 400 - not called (invalid content type)`, (done) => {\n        request(\n          {\n            method,\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            formData: {},\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 400);\n            assert.deepEqual(JSON.parse(body), {\n              name: \"Error\",\n              message: \"invalid_content_type\",\n            });\n            sinon.assert.notCalled(options.preUpdate);\n            done();\n          }\n        );\n      });\n    });\n  });\n\n  describe(\"preDelete\", () => {\n    let app = createFn();\n    let server;\n    let customer;\n    let options = {\n      preDelete: sinon.spy((req, res, next) => {\n        next();\n      }),\n      restify: app.isRestify,\n    };\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, options);\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    beforeEach((done) => {\n      db.reset((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        db.models.Customer.create({\n          name: \"Bob\",\n        })\n          .then((createdCustomer) => {\n            customer = createdCustomer;\n          })\n          .then(done)\n          .catch(done);\n      });\n    });\n\n    afterEach(() => {\n      options.preDelete.resetHistory();\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"DELETE /Customer 204\", (done) => {\n      request.del(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 204);\n          sinon.assert.calledOnce(options.preDelete);\n          let args = options.preDelete.args[0];\n          assert.equal(args.length, 3);\n          assert.equal(args[0].erm.result, undefined);\n          assert.equal(args[0].erm.statusCode, 204);\n          assert.equal(typeof args[2], \"function\");\n          done();\n        }\n      );\n    });\n\n    it(\"DELETE /Customer/:id 204\", (done) => {\n      request.del(\n        {\n          url: `${testUrl}/api/v1/Customer/${customer._id}`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 204);\n          sinon.assert.calledOnce(options.preDelete);\n          let args = options.preDelete.args[0];\n          assert.equal(args.length, 3);\n          assert.equal(args[0].erm.result, undefined);\n          assert.equal(args[0].erm.statusCode, 204);\n          assert.equal(typeof args[2], \"function\");\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"postCreate/Read/Update/Delete - undefined\", () => {\n    let app = createFn();\n    let server;\n    let customer;\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, {\n          restify: app.isRestify,\n        });\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    beforeEach((done) => {\n      db.reset((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        db.models.Customer.create({\n          name: \"Bob\",\n        })\n          .then((createdCustomer) => {\n            customer = createdCustomer;\n          })\n          .then(done)\n          .catch(done);\n      });\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"POST /Customer 201\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: {\n            name: \"John\",\n          },\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 201);\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          done();\n        }\n      );\n    });\n\n    updateMethods.forEach((method) => {\n      it(`${method} /Customer/:id 200`, (done) => {\n        request.post(\n          {\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            json: {\n              name: \"Bobby\",\n            },\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            done();\n          }\n        );\n      });\n    });\n\n    it(\"DELETE /Customer/:id 204\", (done) => {\n      request.del(\n        {\n          url: `${testUrl}/api/v1/Customer/${customer._id}`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 204);\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"postCreate\", () => {\n    let app = createFn();\n    let server;\n    let options = {\n      postCreate: sinon.spy((req, res, next) => {\n        next();\n      }),\n      restify: app.isRestify,\n    };\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, options);\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    afterEach(() => {\n      options.postCreate.resetHistory();\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"POST /Customer 201\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: {\n            name: \"Bob\",\n          },\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 201);\n          sinon.assert.calledOnce(options.postCreate);\n          let args = options.postCreate.args[0];\n          assert.equal(args.length, 3);\n          assert.equal(args[0].erm.result.name, \"Bob\");\n          assert.equal(args[0].erm.statusCode, 201);\n          assert.equal(typeof args[2], \"function\");\n          done();\n        }\n      );\n    });\n\n    it(\"POST /Customer 400 - missing required field\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: {\n            comment: \"Bar\",\n          },\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 400);\n          assert.deepEqual(body, {\n            name: \"ValidationError\",\n            _message: \"Customer validation failed\",\n            message:\n              \"Customer validation failed: name: Path `name` is required.\",\n            errors: {\n              name: {\n                kind: \"required\",\n                message: \"Path `name` is required.\",\n                name: \"ValidatorError\",\n                path: \"name\",\n                properties: {\n                  fullPath: \"name\",\n                  message: \"Path `name` is required.\",\n                  path: \"name\",\n                  type: \"required\",\n                },\n              },\n            },\n          });\n          sinon.assert.notCalled(options.postCreate);\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"postRead\", () => {\n    let app = createFn();\n    let server;\n    let customer;\n    let options = {\n      postRead: sinon.spy((req, res, next) => {\n        next();\n      }),\n      restify: app.isRestify,\n    };\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, options);\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    beforeEach((done) => {\n      db.reset((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        db.models.Customer.create({\n          name: \"Bob\",\n        })\n          .then((createdCustomer) => {\n            customer = createdCustomer;\n          })\n          .then(done)\n          .catch(done);\n      });\n    });\n\n    afterEach(() => {\n      options.postRead.resetHistory();\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"GET /Customer 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          sinon.assert.calledOnce(options.postRead);\n          let args = options.postRead.args[0];\n          assert.equal(args.length, 3);\n          assert.equal(args[0].erm.result[0].name, \"Bob\");\n          assert.equal(args[0].erm.statusCode, 200);\n          assert.equal(typeof args[2], \"function\");\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer/count 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer/count`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          sinon.assert.calledOnce(options.postRead);\n          let args = options.postRead.args[0];\n          assert.equal(args.length, 3);\n          assert.equal(args[0].erm.result.count, 1);\n          assert.equal(args[0].erm.statusCode, 200);\n          assert.equal(typeof args[2], \"function\");\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer/:id 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer/${customer._id}`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          sinon.assert.calledOnce(options.postRead);\n          let args = options.postRead.args[0];\n          assert.equal(args.length, 3);\n          assert.equal(args[0].erm.result.name, \"Bob\");\n          assert.equal(args[0].erm.statusCode, 200);\n          assert.equal(typeof args[2], \"function\");\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer/:id 404\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer/${randomId}`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 404);\n          sinon.assert.notCalled(options.postRead);\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer/:id 404 - invalid id\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer/${invalidId}`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 404);\n          sinon.assert.notCalled(options.postRead);\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer/:id/shallow 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer/${customer._id}/shallow`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          sinon.assert.calledOnce(options.postRead);\n          let args = options.postRead.args[0];\n          assert.equal(args.length, 3);\n          assert.equal(args[0].erm.result.name, \"Bob\");\n          assert.equal(args[0].erm.statusCode, 200);\n          assert.equal(typeof args[2], \"function\");\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"postUpdate\", () => {\n    let app = createFn();\n    let server;\n    let customer;\n    let options = {\n      postUpdate: sinon.spy((req, res, next) => {\n        next();\n      }),\n      restify: app.isRestify,\n    };\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, options);\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    beforeEach((done) => {\n      db.reset((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        db.models.Customer.create({\n          name: \"Bob\",\n        })\n          .then((createdCustomer) => {\n            customer = createdCustomer;\n          })\n          .then(done)\n          .catch(done);\n      });\n    });\n\n    afterEach(() => {\n      options.postUpdate.resetHistory();\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    updateMethods.forEach((method) => {\n      it(`${method} /Customer/:id 200`, (done) => {\n        request(\n          {\n            method,\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            json: {\n              name: \"Bobby\",\n            },\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            sinon.assert.calledOnce(options.postUpdate);\n            let args = options.postUpdate.args[0];\n            assert.equal(args.length, 3);\n            assert.equal(args[0].erm.result.name, \"Bobby\");\n            assert.equal(args[0].erm.statusCode, 200);\n            assert.equal(typeof args[2], \"function\");\n            done();\n          }\n        );\n      });\n\n      it(`${method} /Customer/:id 404 - random id`, (done) => {\n        request(\n          {\n            method,\n            url: `${testUrl}/api/v1/Customer/${randomId}`,\n            json: {\n              name: \"Bobby\",\n            },\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 404);\n            sinon.assert.notCalled(options.postUpdate);\n            done();\n          }\n        );\n      });\n\n      it(`${method} /Customer/:id 404 - invalid id`, (done) => {\n        request(\n          {\n            method,\n            url: `${testUrl}/api/v1/Customer/${invalidId}`,\n            json: {\n              name: \"Bobby\",\n            },\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 404);\n            sinon.assert.notCalled(options.postUpdate);\n            done();\n          }\n        );\n      });\n\n      it(`${method} /Customer/:id 400 - not called (missing content type)`, (done) => {\n        request(\n          {\n            method,\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 400);\n            assert.deepEqual(JSON.parse(body), {\n              name: \"Error\",\n              message: \"missing_content_type\",\n            });\n            sinon.assert.notCalled(options.postUpdate);\n            done();\n          }\n        );\n      });\n\n      it(`${method} /Customer/:id 400 - not called (invalid content type)`, (done) => {\n        request(\n          {\n            method,\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            formData: {},\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 400);\n            assert.deepEqual(JSON.parse(body), {\n              name: \"Error\",\n              message: \"invalid_content_type\",\n            });\n            sinon.assert.notCalled(options.postUpdate);\n            done();\n          }\n        );\n      });\n    });\n  });\n\n  describe(\"postDelete\", () => {\n    let app = createFn();\n    let server;\n    let customer;\n    let options = {\n      postDelete: sinon.spy((req, res, next) => {\n        next();\n      }),\n      restify: app.isRestify,\n    };\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, options);\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    beforeEach((done) => {\n      db.reset((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        db.models.Customer.create({\n          name: \"Bob\",\n        })\n          .then((createdCustomer) => {\n            customer = createdCustomer;\n          })\n          .then(done)\n          .catch(done);\n      });\n    });\n\n    afterEach(() => {\n      options.postDelete.resetHistory();\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"DELETE /Customer 204\", (done) => {\n      request.del(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 204);\n          sinon.assert.calledOnce(options.postDelete);\n          let args = options.postDelete.args[0];\n          assert.equal(args.length, 3);\n          assert.equal(args[0].erm.result, undefined);\n          assert.equal(args[0].erm.statusCode, 204);\n          assert.equal(typeof args[2], \"function\");\n          done();\n        }\n      );\n    });\n\n    it(\"DELETE /Customer/:id 204\", (done) => {\n      request.del(\n        {\n          url: `${testUrl}/api/v1/Customer/${customer._id}`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 204);\n          sinon.assert.calledOnce(options.postDelete);\n          let args = options.postDelete.args[0];\n          assert.equal(args.length, 3);\n          assert.equal(args[0].erm.result, undefined);\n          assert.equal(args[0].erm.statusCode, 204);\n          assert.equal(typeof args[2], \"function\");\n          done();\n        }\n      );\n    });\n\n    it(\"DELETE /Customer/:id 404\", (done) => {\n      request.del(\n        {\n          url: `${testUrl}/api/v1/Customer/${randomId}`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 404);\n          sinon.assert.notCalled(options.postDelete);\n          done();\n        }\n      );\n    });\n\n    it(\"DELETE /Customer/:id 404 - invalid id\", (done) => {\n      request.del(\n        {\n          url: `${testUrl}/api/v1/Customer/${invalidId}`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 404);\n          sinon.assert.notCalled(options.postDelete);\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"postCreate yields an error\", () => {\n    let app = createFn();\n    let server;\n    let options = {\n      postCreate: sinon.spy((req, res, next) => {\n        next(new Error(\"Something went wrong\"));\n      }),\n      postProcess: sinon.spy(),\n      restify: app.isRestify,\n    };\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, options);\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    afterEach(() => {\n      options.postCreate.resetHistory();\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    // TODO: This test is weird\n    it(\"POST /Customer 201\", (done) => {\n      request.post(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: {\n            name: \"Bob\",\n          },\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 400);\n          sinon.assert.calledOnce(options.postCreate);\n          let args = options.postCreate.args[0];\n          assert.equal(args.length, 3);\n          assert.equal(args[0].erm.result.name, \"Bob\");\n          assert.equal(args[0].erm.statusCode, 400);\n          assert.equal(typeof args[2], \"function\");\n          sinon.assert.notCalled(options.postProcess);\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"postProcess\", () => {\n    let app = createFn();\n    let server;\n    let options = {\n      postProcess: sinon.spy(),\n      restify: app.isRestify,\n    };\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, options);\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    afterEach(() => {\n      options.postProcess.resetHistory();\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"GET /Customer 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          sinon.assert.calledOnce(options.postProcess);\n          let args = options.postProcess.args[0];\n          assert.equal(args.length, 2);\n          assert.deepEqual(args[0].erm.result, []);\n          assert.equal(args[0].erm.statusCode, 200);\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"postProcess (async outputFn)\", () => {\n    let app = createFn();\n    let server;\n    let options = {\n      outputFn: (req, res) => {\n        if (app.isRestify) {\n          res.send(200);\n        } else {\n          res.sendStatus(200);\n        }\n\n        return Promise.resolve();\n      },\n      postProcess: sinon.spy(),\n      restify: app.isRestify,\n    };\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, options);\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    afterEach(() => {\n      options.postProcess.resetHistory();\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"GET /Customer 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          sinon.assert.calledOnce(options.postProcess);\n          let args = options.postProcess.args[0];\n          assert.equal(args.length, 2);\n          assert.deepEqual(args[0].erm.result, []);\n          assert.equal(args[0].erm.statusCode, 200);\n          done();\n        }\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "test/integration/options.mjs",
    "content": "import assert from \"assert\";\nimport request from \"request\";\nimport sinon from \"sinon\";\nimport { serve } from \"../../dist/express-restify-mongoose.js\";\n\nimport setupDb from \"./setup.mjs\";\n\nexport default function (createFn, setup, dismantle) {\n  const db = setupDb();\n\n  const testPort = 30023;\n  const testUrl = `http://localhost:${testPort}`;\n  const updateMethods = [\"PATCH\", \"POST\", \"PUT\"];\n\n  describe(\"no options\", () => {\n    let app = createFn();\n    let server;\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(\n          app,\n          db.models.Customer,\n          app.isRestify\n            ? {\n                restify: app.isRestify,\n              }\n            : undefined\n        );\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"GET /Customer 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"defaults - version set in defaults\", () => {\n    let app = createFn();\n    let server;\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        const defaults = {\n          version: \"/custom\",\n        };\n\n        serve(app, db.models.Customer, {\n          ...defaults,\n          restify: app.isRestify,\n        });\n\n        serve(app, db.models.Invoice, {\n          ...defaults,\n          restify: app.isRestify,\n        });\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"GET /Customer 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/custom/Customer`,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Invoice 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/custom/Invoice`,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"totalCountHeader - boolean (default header)\", () => {\n    let app = createFn();\n    let server;\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, {\n          totalCountHeader: true,\n          restify: app.isRestify,\n        });\n\n        db.models.Customer.create([\n          {\n            name: \"Bob\",\n          },\n          {\n            name: \"John\",\n          },\n          {\n            name: \"Mike\",\n          },\n        ])\n          .then(() => {\n            server = app.listen(testPort, done);\n          })\n          .catch(done);\n      });\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"GET /Customer?limit=1 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          qs: {\n            limit: 1,\n          },\n          json: true,\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          assert.equal(res.headers[\"x-total-count\"], 3);\n          assert.equal(body.length, 1);\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer?skip=1 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          qs: {\n            skip: 1,\n          },\n          json: true,\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          assert.equal(res.headers[\"x-total-count\"], 3);\n          assert.equal(body.length, 2);\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer?limit=1&skip=1 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          qs: {\n            limit: 1,\n            skip: 1,\n          },\n          json: true,\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          assert.equal(res.headers[\"x-total-count\"], 3);\n          assert.equal(body.length, 1);\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer?distinct=name 200 - ignore total count header\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          qs: {\n            distinct: \"name\",\n          },\n          json: true,\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          assert.equal(body.length, 3);\n          assert.equal(res.headers[\"x-total-count\"], undefined);\n          assert.equal(body[0], \"Bob\");\n          assert.equal(body[1], \"John\");\n          assert.equal(body[2], \"Mike\");\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"totalCountHeader - boolean (default header) + contextFilter\", () => {\n    let app = createFn();\n    let server;\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, {\n          totalCountHeader: true,\n          contextFilter: (model, req, done) => done(model.find()),\n          restify: app.isRestify,\n        });\n\n        db.models.Customer.create([\n          {\n            name: \"Bob\",\n          },\n          {\n            name: \"John\",\n          },\n          {\n            name: \"Mike\",\n          },\n        ])\n          .then(() => {\n            server = app.listen(testPort, done);\n          })\n          .catch(done);\n      });\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"GET /Customer?limit=1 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          qs: {\n            limit: 1,\n          },\n          json: true,\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          assert.equal(res.headers[\"x-total-count\"], 3);\n          assert.equal(body.length, 1);\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"totalCountHeader - string (custom header)\", () => {\n    let app = createFn();\n    let server;\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, {\n          totalCountHeader: \"X-Custom-Count\",\n          restify: app.isRestify,\n        });\n\n        db.models.Customer.create([\n          {\n            name: \"Bob\",\n          },\n          {\n            name: \"John\",\n          },\n          {\n            name: \"Mike\",\n          },\n        ])\n          .then(() => {\n            server = app.listen(testPort, done);\n          })\n          .catch(done);\n      });\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"GET /Customer?limit=1 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          qs: {\n            limit: 1,\n          },\n          json: true,\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          assert.equal(res.headers[\"x-custom-count\"], 3);\n          assert.equal(body.length, 1);\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"limit\", () => {\n    let app = createFn();\n    let server;\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, {\n          limit: 2,\n          restify: app.isRestify,\n        });\n\n        db.models.Customer.create([\n          {\n            name: \"Bob\",\n          },\n          {\n            name: \"John\",\n          },\n          {\n            name: \"Mike\",\n          },\n        ])\n          .then(() => {\n            server = app.listen(testPort, done);\n          })\n          .catch(done);\n      });\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"GET /Customer 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: true,\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          assert.equal(body.length, 2);\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer 200 - override limit in options (query.limit === 0)\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          qs: {\n            limit: 0,\n          },\n          json: true,\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          assert.equal(body.length, 2);\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer 200 - override limit in options (query.limit < options.limit)\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          qs: {\n            limit: 1,\n          },\n          json: true,\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          assert.equal(body.length, 1);\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer 200 - override limit in query (options.limit < query.limit)\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          qs: {\n            limit: 3,\n          },\n          json: true,\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          assert.equal(body.length, 2);\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer/count 200 - ignore limit\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer/count`,\n          json: true,\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          assert.equal(body.count, 3);\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"name\", () => {\n    let app = createFn();\n    let server;\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, {\n          name: \"Client\",\n          restify: app.isRestify,\n        });\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"GET /Client 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Client`,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"prefix\", () => {\n    let app = createFn();\n    let server;\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, {\n          prefix: \"/applepie\",\n          restify: app.isRestify,\n        });\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"GET /applepie/v1/Customer 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/applepie/v1/Customer`,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"version\", () => {\n    describe(\"v8\", () => {\n      let app = createFn();\n      let server;\n\n      before((done) => {\n        setup((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          serve(app, db.models.Customer, {\n            version: \"/v8\",\n            restify: app.isRestify,\n          });\n\n          server = app.listen(testPort, done);\n        });\n      });\n\n      after((done) => {\n        dismantle(app, server, done);\n      });\n\n      it(\"GET /v8/Customer 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v8/Customer`,\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            done();\n          }\n        );\n      });\n    });\n\n    describe(\"custom id location\", () => {\n      let app = createFn();\n      let server;\n      let customer;\n\n      before((done) => {\n        setup((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          serve(app, db.models.Customer, {\n            version: \"/v8/Entities/:id\",\n            restify: app.isRestify,\n          });\n\n          db.models.Customer.create({\n            name: \"Bob\",\n          })\n            .then((createdCustomer) => {\n              customer = createdCustomer;\n              server = app.listen(testPort, done);\n            })\n            .catch(done);\n        });\n      });\n\n      after((done) => {\n        dismantle(app, server, done);\n      });\n\n      it(\"GET /v8/Entities/Customer 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v8/Entities/Customer`,\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            done();\n          }\n        );\n      });\n\n      it(\"GET /v8/Entities/:id/Customer 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v8/Entities/${customer._id}/Customer`,\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            done();\n          }\n        );\n      });\n\n      it(\"GET /v8/Entities/:id/Customer/shallow 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v8/Entities/${customer._id}/Customer/shallow`,\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            done();\n          }\n        );\n      });\n\n      it(\"GET /v8/Entities/Customer/count 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v8/Entities/Customer/count`,\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            done();\n          }\n        );\n      });\n    });\n  });\n\n  describe(\"defaults - preUpdate with falsy findOneAndUpdate\", () => {\n    let app = createFn();\n    let server;\n    let customer;\n    let options = {\n      findOneAndUpdate: false,\n      preUpdate: [\n        sinon.spy((req, res, next) => {\n          next();\n        }),\n        sinon.spy((req, res, next) => {\n          next();\n        }),\n      ],\n    };\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Product, {\n          ...options,\n          restify: app.isRestify,\n        });\n\n        // order is important, test the second attached model to potentially reproduce the error.\n        serve(app, db.models.Customer, {\n          ...options,\n          restify: app.isRestify,\n        });\n\n        db.models.Customer.create({\n          name: \"Bob\",\n        })\n          .then((createdCustomer) => {\n            customer = createdCustomer;\n            server = app.listen(testPort, done);\n          })\n          .catch(done);\n      });\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    updateMethods.forEach((method) => {\n      it(`${method} /Customer/:id 200`, (done) => {\n        request(\n          {\n            method,\n            url: `${testUrl}/api/v1/Customer/${customer._id}`,\n            json: {\n              age: 12,\n            },\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.name, \"Bob\");\n            assert.equal(body.age, 12);\n            assert.equal(options.preUpdate.length, 2);\n            sinon.assert.calledOnce(options.preUpdate[0]);\n            sinon.assert.calledOnce(options.preUpdate[1]);\n\n            options.preUpdate[0].resetHistory();\n            options.preUpdate[1].resetHistory();\n\n            done();\n          }\n        );\n      });\n    });\n  });\n\n  describe(\"defaults - preDelete with falsy findOneAndRemove\", () => {\n    let app = createFn();\n    let server;\n    let customer;\n    let options = {\n      findOneAndRemove: false,\n      preDelete: [\n        sinon.spy((req, res, next) => {\n          next();\n        }),\n        sinon.spy((req, res, next) => {\n          next();\n        }),\n      ],\n    };\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Product, {\n          ...options,\n          restify: app.isRestify,\n        });\n\n        // order is important, test the second attached model to potentially reproduce the error.\n        serve(app, db.models.Customer, {\n          ...options,\n          restify: app.isRestify,\n        });\n\n        db.models.Customer.create({\n          name: \"Bob\",\n        })\n          .then((createdCustomer) => {\n            customer = createdCustomer;\n            server = app.listen(testPort, done);\n          })\n          .catch(done);\n      });\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"DELETE /Customer/:id 204\", (done) => {\n      request.del(\n        {\n          url: `${testUrl}/api/v1/Customer/${customer._id}`,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 204);\n          assert.equal(options.preDelete.length, 2);\n          sinon.assert.calledOnce(options.preDelete[0]);\n          sinon.assert.calledOnce(options.preDelete[1]);\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"idProperty\", () => {\n    let app = createFn();\n    let server;\n    let customer;\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, {\n          idProperty: \"name\",\n          restify: app.isRestify,\n        });\n\n        db.models.Customer.create({\n          name: \"Bob\",\n        })\n          .then((createdCustomer) => {\n            customer = createdCustomer;\n            server = app.listen(testPort, done);\n          })\n          .catch(done);\n      });\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"GET /Customer/:name 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer/${customer.name}`,\n          json: true,\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          assert.equal(body.name, \"Bob\");\n          done();\n        }\n      );\n    });\n\n    updateMethods.forEach((method) => {\n      it(`${method} /Customer/:name 200`, (done) => {\n        request(\n          {\n            method,\n            url: `${testUrl}/api/v1/Customer/${customer.name}`,\n            json: {\n              age: 12,\n            },\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.name, \"Bob\");\n            assert.equal(body.age, 12);\n            done();\n          }\n        );\n      });\n    });\n\n    it(\"DELETE /Customer/:name 204\", (done) => {\n      request.del(\n        {\n          url: `${testUrl}/api/v1/Customer/${customer.name}`,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 204);\n          done();\n        }\n      );\n    });\n  });\n\n  describe(\"allowRegex\", () => {\n    let app = createFn();\n    let server;\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, {\n          allowRegex: false,\n          restify: app.isRestify,\n        });\n\n        db.models.Customer.create({\n          name: \"Bob\",\n        })\n          .then(() => {\n            server = app.listen(testPort, done);\n          })\n          .catch(done);\n      });\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"GET /Customer 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          qs: {\n            query: JSON.stringify({\n              name: { $regex: \"^B\" },\n            }),\n          },\n          json: true,\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 400);\n          delete body.reason;\n          assert.deepEqual(body, {\n            message: \"invalid_json_query\",\n            name: \"Error\",\n          });\n          done();\n        }\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "test/integration/read.mjs",
    "content": "import assert from \"assert\";\nimport mongoose from \"mongoose\";\nimport request from \"request\";\nimport { serve } from \"../../dist/express-restify-mongoose.js\";\n\nimport setupDb from \"./setup.mjs\";\n\nexport default function (createFn, setup, dismantle) {\n  const db = setupDb();\n\n  const testPort = 30023;\n  const testUrl = `http://localhost:${testPort}`;\n  const invalidId = \"invalid-id\";\n  const randomId = new mongoose.Types.ObjectId().toHexString();\n\n  describe(\"Read documents\", () => {\n    let app = createFn();\n    let server;\n    let customers;\n\n    before((done) => {\n      setup((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        serve(app, db.models.Customer, {\n          allowRegex: true,\n          restify: app.isRestify,\n        });\n\n        serve(app, db.models.Invoice, {\n          restify: app.isRestify,\n        });\n\n        server = app.listen(testPort, done);\n      });\n    });\n\n    beforeEach((done) => {\n      db.reset((err) => {\n        if (err) {\n          return done(err);\n        }\n\n        db.models.Product.create({\n          name: \"Bobsleigh\",\n        })\n          .then((createdProduct) => {\n            return db.models.Customer.create([\n              {\n                name: \"Bob\",\n                age: 12,\n                favorites: {\n                  animal: \"Boar\",\n                  color: \"Black\",\n                  purchase: {\n                    item: createdProduct._id,\n                    number: 1,\n                  },\n                },\n                coordinates: [45.2667, 72.15],\n              },\n              {\n                name: \"John\",\n                age: 24,\n                favorites: {\n                  animal: \"Jaguar\",\n                  color: \"Jade\",\n                  purchase: {\n                    item: createdProduct._id,\n                    number: 2,\n                  },\n                },\n              },\n              {\n                name: \"Mike\",\n                age: 36,\n                favorites: {\n                  animal: \"Medusa\",\n                  color: \"Maroon\",\n                  purchase: {\n                    item: createdProduct._id,\n                    number: 3,\n                  },\n                },\n              },\n            ]);\n          })\n          .then((createdCustomers) => {\n            customers = createdCustomers;\n\n            return db.models.Invoice.create([\n              {\n                customer: customers[0]._id,\n                amount: 100,\n                receipt: \"A\",\n              },\n              {\n                customer: customers[1]._id,\n                amount: 200,\n                receipt: \"B\",\n              },\n              {\n                customer: customers[2]._id,\n                amount: 300,\n                receipt: \"C\",\n              },\n            ]);\n          })\n          .then(() => done())\n          .catch(done);\n      });\n    });\n\n    after((done) => {\n      dismantle(app, server, done);\n    });\n\n    it(\"GET /Customer 200\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer`,\n          json: true,\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          assert.equal(body.length, 3);\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer/:id 200 - created id\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer/${customers[0]._id}`,\n          json: true,\n        },\n        (err, res, body) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 200);\n          assert.equal(body.name, \"Bob\");\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer/:id 404 - invalid id\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer/${invalidId}`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 404);\n          done();\n        }\n      );\n    });\n\n    it(\"GET /Customer/:id 404 - random id\", (done) => {\n      request.get(\n        {\n          url: `${testUrl}/api/v1/Customer/${randomId}`,\n          json: true,\n        },\n        (err, res) => {\n          assert.ok(!err);\n          assert.equal(res.statusCode, 404);\n          done();\n        }\n      );\n    });\n\n    describe(\"ignore unknown parameters\", () => {\n      it(\"GET /Customer?foo=bar 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              foo: \"bar\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 3);\n            done();\n          }\n        );\n      });\n    });\n\n    describe(\"limit\", () => {\n      it(\"GET /Customer?limit=1 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              limit: 1,\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 1);\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer?limit=foo 400 - evaluates to NaN\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              limit: \"foo\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 400);\n            assert.deepEqual(body, {\n              name: \"Error\",\n              message: \"invalid_json_query\",\n            });\n            done();\n          }\n        );\n      });\n    });\n\n    describe(\"skip\", () => {\n      it(\"GET /Customer?skip=1 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              skip: 1,\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 2);\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer?skip=foo 400 - evaluates to NaN\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              skip: \"foo\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 400);\n            assert.deepEqual(body, {\n              name: \"Error\",\n              message: \"invalid_json_query\",\n            });\n            done();\n          }\n        );\n      });\n    });\n\n    describe(\"sort\", () => {\n      it(\"GET /Customer?sort=name 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              sort: \"name\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 3);\n            assert.equal(body[0].name, \"Bob\");\n            assert.equal(body[1].name, \"John\");\n            assert.equal(body[2].name, \"Mike\");\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer?sort=-name 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              sort: \"-name\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 3);\n            assert.equal(body[0].name, \"Mike\");\n            assert.equal(body[1].name, \"John\");\n            assert.equal(body[2].name, \"Bob\");\n            done();\n          }\n        );\n      });\n\n      it('GET /Customer?sort={\"name\":1} 200', (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              sort: {\n                name: 1,\n              },\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 3);\n            assert.equal(body[0].name, \"Bob\");\n            assert.equal(body[1].name, \"John\");\n            assert.equal(body[2].name, \"Mike\");\n            done();\n          }\n        );\n      });\n\n      it('GET /Customer?sort={\"name\":-1} 200', (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              sort: {\n                name: -1,\n              },\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 3);\n            assert.equal(body[0].name, \"Mike\");\n            assert.equal(body[1].name, \"John\");\n            assert.equal(body[2].name, \"Bob\");\n            done();\n          }\n        );\n      });\n    });\n\n    describe(\"query\", () => {\n      it(\"GET /Customer?query={} 200 - empty object\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              query: JSON.stringify({}),\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 3);\n            done();\n          }\n        );\n      });\n\n      it('GET /Customer?query={\"$near\": { \"$geometry\": { \"coordinates\": [45.2667, 72.1500] } }} 200 - coordinates', (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              query: JSON.stringify({\n                coordinates: {\n                  $near: {\n                    $geometry: {\n                      type: \"Point\",\n                      coordinates: [45.2667, 72.15],\n                    },\n                    $maxDistance: 1000,\n                  },\n                },\n              }),\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 1);\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer?query=invalidJson 400 - invalid json\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              query: \"invalidJson\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 400);\n            assert.deepEqual(body, {\n              name: \"Error\",\n              message: \"invalid_json_query\",\n            });\n            done();\n          }\n        );\n      });\n\n      describe(\"string\", () => {\n        it('GET /Customer?query={\"name\":\"John\"} 200 - exact match', (done) => {\n          request.get(\n            {\n              url: `${testUrl}/api/v1/Customer`,\n              qs: {\n                query: JSON.stringify({\n                  name: \"John\",\n                }),\n              },\n              json: true,\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.length, 1);\n              assert.equal(body[0].name, \"John\");\n              done();\n            }\n          );\n        });\n\n        it('GET /Customer?query={\"favorites.animal\":\"Jaguar\"} 200 - exact match (nested property)', (done) => {\n          request.get(\n            {\n              url: `${testUrl}/api/v1/Customer`,\n              qs: {\n                query: JSON.stringify({\n                  \"favorites.animal\": \"Jaguar\",\n                }),\n              },\n              json: true,\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.length, 1);\n              assert.equal(body[0].favorites.animal, \"Jaguar\");\n              done();\n            }\n          );\n        });\n\n        it('GET /Customer?query={\"name\":{\"$regex\":\"^J\"}} 200 - name starting with', (done) => {\n          request.get(\n            {\n              url: `${testUrl}/api/v1/Customer`,\n              qs: {\n                query: JSON.stringify({\n                  name: { $regex: \"^J\" },\n                }),\n              },\n              json: true,\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.length, 1);\n              assert.ok(body[0].name[0] === \"J\");\n              done();\n            }\n          );\n        });\n\n        it('GET /Customer?query={\"name\":[\"Bob\",\"John\"]}&sort=name 200 - in', (done) => {\n          request.get(\n            {\n              url: `${testUrl}/api/v1/Customer`,\n              qs: {\n                query: JSON.stringify({\n                  name: [\"Bob\", \"John\"],\n                }),\n                sort: \"name\",\n              },\n              json: true,\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.length, 2);\n              assert.equal(body[0].name, \"Bob\");\n              assert.equal(body[1].name, \"John\");\n              done();\n            }\n          );\n        });\n      });\n\n      describe(\"number\", () => {\n        it('GET /Customer?query={\"age\":\"24\"} 200 - exact match', (done) => {\n          request.get(\n            {\n              url: `${testUrl}/api/v1/Customer`,\n              qs: {\n                query: JSON.stringify({\n                  age: 24,\n                }),\n              },\n              json: true,\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.length, 1);\n              assert.equal(body[0].age, 24);\n              done();\n            }\n          );\n        });\n\n        it('GET /Customer?query={\"age\":[\"12\",\"24\"]}&sort=age 200 - in', (done) => {\n          request.get(\n            {\n              url: `${testUrl}/api/v1/Customer`,\n              qs: {\n                query: JSON.stringify({\n                  age: [\"12\", \"24\"],\n                }),\n                sort: \"age\",\n              },\n              json: true,\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.length, 2);\n              assert.equal(body[0].age, 12);\n              assert.equal(body[1].age, 24);\n              done();\n            }\n          );\n        });\n      });\n    });\n\n    describe(\"select\", () => {\n      it('GET /Customer?select=[\"name\"] 200 - only include', (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              select: [\"name\"],\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 3);\n            body.forEach((item) => {\n              assert.equal(Object.keys(item).length, 2);\n              assert.ok(item._id);\n              assert.ok(item.name);\n            });\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer?select=name 200 - only include\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              select: \"name\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 3);\n            body.forEach((item) => {\n              assert.equal(Object.keys(item).length, 2);\n              assert.ok(item._id);\n              assert.ok(item.name);\n            });\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer?select=favorites.animal 200 - only include (nested field)\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              select: \"favorites.animal\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 3);\n            body.forEach((item) => {\n              assert.equal(Object.keys(item).length, 2);\n              assert.ok(item._id);\n              assert.ok(item.favorites);\n              assert.ok(item.favorites.animal);\n              assert.ok(item.favorites.color === undefined);\n            });\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer?select=-name 200 - exclude name\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              select: \"-name\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 3);\n            body.forEach((item) => {\n              assert.ok(item.name === undefined);\n            });\n            done();\n          }\n        );\n      });\n\n      it('GET /Customer?select={\"name\":1} 200 - only include name', (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              select: JSON.stringify({\n                name: 1,\n              }),\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 3);\n            body.forEach((item) => {\n              assert.equal(Object.keys(item).length, 2);\n              assert.ok(item._id);\n              assert.ok(item.name);\n            });\n            done();\n          }\n        );\n      });\n\n      it('GET /Customer?select={\"name\":0} 200 - exclude name', (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              select: JSON.stringify({\n                name: 0,\n              }),\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 3);\n            body.forEach((item) => {\n              assert.ok(item.name === undefined);\n            });\n            done();\n          }\n        );\n      });\n    });\n\n    describe(\"populate\", () => {\n      it(\"GET /Invoice?populate=customer 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Invoice`,\n            qs: {\n              populate: \"customer\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 3);\n            body.forEach((invoice) => {\n              assert.ok(invoice.customer);\n              assert.ok(invoice.customer._id);\n              assert.ok(invoice.customer.name);\n              assert.ok(invoice.customer.age);\n            });\n            done();\n          }\n        );\n      });\n\n      it('GET /Invoice?populate={path:\"customer\"} 200', (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Invoice`,\n            qs: {\n              populate: JSON.stringify({\n                path: \"customer\",\n              }),\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 3);\n            body.forEach((invoice) => {\n              assert.ok(invoice.customer);\n              assert.ok(invoice.customer._id);\n              assert.ok(invoice.customer.name);\n              assert.ok(invoice.customer.age);\n            });\n            done();\n          }\n        );\n      });\n\n      it('GET /Invoice?populate=[{path:\"customer\"}] 200', (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Invoice`,\n            qs: {\n              populate: JSON.stringify([\n                {\n                  path: \"customer\",\n                },\n              ]),\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 3);\n            body.forEach((invoice) => {\n              assert.ok(invoice.customer);\n              assert.ok(invoice.customer._id);\n              assert.ok(invoice.customer.name);\n              assert.ok(invoice.customer.age);\n            });\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer?populate=favorites.purchase.item 200 - nested field\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              populate: \"favorites.purchase.item\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 3);\n            body.forEach((customer) => {\n              assert.ok(customer.favorites.purchase);\n              assert.ok(customer.favorites.purchase.item);\n              assert.ok(customer.favorites.purchase.item._id);\n              assert.ok(customer.favorites.purchase.item.name);\n              assert.ok(customer.favorites.purchase.number);\n            });\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Invoice?populate=customer.account 200 - ignore deep populate\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Invoice`,\n            qs: {\n              populate: \"customer.account\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 3);\n            body.forEach((invoice) => {\n              assert.ok(invoice.customer);\n              assert.equal(typeof invoice.customer, \"string\");\n            });\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Invoice?populate=evilCustomer 200 - ignore unknown field\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Invoice`,\n            qs: {\n              populate: \"evilCustomer\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 3);\n            done();\n          }\n        );\n      });\n\n      describe(\"with select\", () => {\n        it(\"GET Invoices?populate=customer&select=amount 200 - only include amount and customer document\", (done) => {\n          request.get(\n            {\n              url: `${testUrl}/api/v1/Invoice`,\n              qs: {\n                populate: \"customer\",\n                select: \"amount\",\n              },\n              json: true,\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.length, 3);\n              body.forEach((invoice) => {\n                assert.ok(invoice.amount);\n                assert.ok(invoice.customer);\n                assert.ok(invoice.customer._id);\n                assert.ok(invoice.customer.name);\n                assert.ok(invoice.customer.age);\n                assert.equal(invoice.receipt, undefined);\n              });\n              done();\n            }\n          );\n        });\n\n        it(\"GET Invoices?populate=customer&select=amount,customer.name 200 - only include amount and customer name\", (done) => {\n          request.get(\n            {\n              url: `${testUrl}/api/v1/Invoice`,\n              qs: {\n                populate: \"customer\",\n                select: \"amount,customer.name\",\n              },\n              json: true,\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.length, 3);\n              body.forEach((invoice) => {\n                assert.ok(invoice.amount);\n                assert.ok(invoice.customer);\n                assert.ok(invoice.customer._id);\n                assert.ok(invoice.customer.name);\n                assert.equal(invoice.customer.age, undefined);\n                assert.equal(invoice.receipt, undefined);\n              });\n              done();\n            }\n          );\n        });\n\n        it(\"GET Invoices?populate=customer&select=customer.name 200 - include all invoice fields, but only include customer name\", (done) => {\n          request.get(\n            {\n              url: `${testUrl}/api/v1/Invoice`,\n              qs: {\n                populate: \"customer\",\n                select: \"customer.name\",\n              },\n              json: true,\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.length, 3);\n              body.forEach((invoice) => {\n                assert.ok(invoice.amount);\n                assert.ok(invoice.receipt);\n                assert.ok(invoice.customer);\n                assert.ok(invoice.customer._id);\n                assert.ok(invoice.customer.name);\n                assert.equal(invoice.customer.age, undefined);\n              });\n              done();\n            }\n          );\n        });\n\n        it(\"GET Invoices?populate=customer&select=-customer.name 200 - include all invoice and fields, but exclude customer name\", (done) => {\n          request.get(\n            {\n              url: `${testUrl}/api/v1/Invoice`,\n              qs: {\n                populate: \"customer\",\n                select: \"-customer.name\",\n              },\n              json: true,\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.length, 3);\n              body.forEach((invoice) => {\n                assert.ok(invoice.amount);\n                assert.ok(invoice.receipt);\n                assert.ok(invoice.customer);\n                assert.ok(invoice.customer._id);\n                assert.ok(invoice.customer.age);\n                assert.equal(invoice.customer.name, undefined);\n              });\n              done();\n            }\n          );\n        });\n\n        it(\"GET Invoices?populate=customer&select=amount,-customer.-id,customer.name 200 - only include amount and customer name and exclude customer _id\", (done) => {\n          request.get(\n            {\n              url: `${testUrl}/api/v1/Invoice`,\n              qs: {\n                populate: \"customer\",\n                select: \"amount,-customer._id,customer.name\",\n              },\n              json: true,\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.length, 3);\n              body.forEach((invoice) => {\n                assert.ok(invoice.amount);\n                assert.ok(invoice.customer);\n                assert.ok(invoice.customer.name);\n                assert.equal(invoice.receipt, undefined);\n                assert.equal(invoice.customer._id, undefined);\n                assert.equal(invoice.customer.age, undefined);\n              });\n              done();\n            }\n          );\n        });\n\n        it(\"GET Invoices?populate=customer&select=customer.name,customer.age 200 - only include customer name and age\", (done) => {\n          request.get(\n            {\n              url: `${testUrl}/api/v1/Invoice`,\n              qs: {\n                populate: \"customer\",\n                select: \"customer.name,customer.age\",\n              },\n              json: true,\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.length, 3);\n              body.forEach((invoice) => {\n                assert.ok(invoice.amount);\n                assert.ok(invoice.receipt);\n                assert.ok(invoice.customer);\n                assert.ok(invoice.customer._id);\n                assert.ok(invoice.customer.name);\n                assert.ok(invoice.customer.age);\n              });\n              done();\n            }\n          );\n        });\n      });\n    });\n\n    describe(\"distinct\", () => {\n      it(\"GET /Customer?distinct=name 200 - array of unique names\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            qs: {\n              distinct: \"name\",\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 3);\n            assert.equal(body[0], \"Bob\");\n            assert.equal(body[1], \"John\");\n            assert.equal(body[2], \"Mike\");\n            done();\n          }\n        );\n      });\n    });\n\n    describe(\"count\", () => {\n      it(\"GET /Customer/count 200\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer/count`,\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.count, 3);\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer/count 200 - ignores sort\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer/count`,\n            qs: {\n              sort: {\n                _id: 1,\n              },\n            },\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.count, 3);\n            done();\n          }\n        );\n      });\n    });\n\n    describe(\"shallow\", () => {\n      it(\"GET /Customer/:id/shallow 200 - created id\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer/${customers[0]._id}/shallow`,\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.name, \"Bob\");\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer/:id/shallow 404 - invalid id\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer/${invalidId}/shallow`,\n            json: true,\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 404);\n            done();\n          }\n        );\n      });\n\n      it(\"GET /Customer/:id/shallow 404 - random id\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer/${randomId}/shallow`,\n            json: true,\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 404);\n            done();\n          }\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "test/integration/resource_filter.mjs",
    "content": "import assert from \"assert\";\nimport mongoose from \"mongoose\";\nimport { Filter } from \"../../dist/resource_filter.js\";\n\nimport setupDb from \"./setup.mjs\";\n\nconst db = setupDb();\n\ndescribe(\"Resource filter\", () => {\n  const filter = new Filter();\n\n  before((done) => {\n    db.initialize((err) => {\n      if (err) {\n        return done(err);\n      }\n\n      filter.add(db.models.Customer, {\n        filteredKeys: {\n          private: [\n            \"comment\",\n            \"address\",\n            \"favorites.purchase.number\",\n            \"purchases.number\",\n            \"purchases.item.price\",\n          ],\n          protected: [],\n        },\n      });\n\n      filter.add(db.models.Invoice, {\n        filteredKeys: {\n          private: [\"amount\", \"customer.address\", \"products.price\"],\n          protected: [],\n        },\n      });\n\n      filter.add(db.models.Product, {\n        filteredKeys: {\n          private: [\"price\", \"department.code\"],\n          protected: [],\n        },\n      });\n\n      db.reset(done);\n    });\n  });\n\n  after((done) => {\n    db.close(done);\n  });\n\n  describe(\"lean\", () => {\n    describe(\"with populated docs\", () => {\n      it(\"excludes fields from populated items\", () => {\n        let invoice = {\n          customer: {\n            name: \"John\",\n            address: \"123 Drury Lane\",\n          },\n          amount: 42,\n        };\n\n        invoice = filter.filterObject(invoice, {\n          access: \"public\",\n          modelName: db.models.Invoice.modelName,\n          populate: [\n            {\n              path: \"customer\",\n            },\n          ],\n        });\n        assert.ok(\n          invoice.amount === undefined,\n          \"Invoice amount should be excluded\"\n        );\n        assert.ok(\n          invoice.customer.address === undefined,\n          \"Customer address should be excluded\"\n        );\n      });\n\n      it(\"iterates through array of populated objects\", () => {\n        let invoice = {\n          customer: \"objectid\",\n          amount: 240,\n          products: [\n            {\n              name: \"Squirt Gun\",\n              price: 42,\n            },\n            {\n              name: \"Water Balloons\",\n              price: 1,\n            },\n            {\n              name: \"Garden Hose\",\n              price: 10,\n            },\n          ],\n        };\n\n        invoice = filter.filterObject(invoice, {\n          access: \"public\",\n          modelName: db.models.Invoice.modelName,\n          populate: [\n            {\n              path: \"products\",\n            },\n          ],\n        });\n\n        invoice.products.forEach((product) => {\n          assert.ok(\n            product.name !== undefined,\n            \"product name should be populated\"\n          );\n          assert.ok(\n            product.price === undefined,\n            \"product price should be excluded\"\n          );\n        });\n      });\n\n      it(\"filters multiple populated models\", () => {\n        let invoice = {\n          customer: {\n            name: \"John\",\n            address: \"123 Drury Lane\",\n          },\n          amount: 240,\n          products: [\n            {\n              name: \"Squirt Gun\",\n              price: 42,\n            },\n            {\n              name: \"Water Balloons\",\n              price: 1,\n            },\n            {\n              name: \"Garden Hose\",\n              price: 10,\n            },\n          ],\n        };\n\n        invoice = filter.filterObject(invoice, {\n          access: \"public\",\n          modelName: db.models.Invoice.modelName,\n          populate: [\n            {\n              path: \"customer\",\n            },\n            {\n              path: \"products\",\n            },\n          ],\n        });\n        assert.equal(\n          invoice.customer.name,\n          \"John\",\n          \"customer name should be populated\"\n        );\n        assert.ok(\n          invoice.customer.address === undefined,\n          \"customer address should be excluded\"\n        );\n\n        invoice.products.forEach((product) => {\n          assert.ok(\n            product.name !== undefined,\n            \"product name should be populated\"\n          );\n          assert.ok(\n            product.price === undefined,\n            \"product price should be excluded\"\n          );\n        });\n      });\n\n      it(\"filters nested populated docs\", () => {\n        let customer = {\n          name: \"John\",\n          favorites: {\n            purchase: {\n              item: { name: \"Squirt Gun\", price: 42 },\n              number: 2,\n            },\n          },\n        };\n\n        customer = filter.filterObject(customer, {\n          access: \"public\",\n          modelName: db.models.Customer.modelName,\n          populate: [\n            {\n              path: \"favorites.purchase.item\",\n            },\n          ],\n        });\n\n        assert.ok(\n          customer.favorites.purchase.item,\n          \"Purchased item should be included\"\n        );\n        assert.ok(\n          customer.favorites.purchase.item.name !== undefined,\n          \"Purchased item name should be included\"\n        );\n        assert.ok(\n          customer.favorites.purchase.item.price === undefined,\n          \"Purchased item price should be excluded\"\n        );\n        assert.ok(\n          customer.favorites.purchase.number === undefined,\n          \"Purchased item number should be excluded\"\n        );\n      });\n\n      it(\"filters embedded array of populated docs\", () => {\n        let customer = {\n          name: \"John\",\n          purchases: [\n            {\n              item: { name: \"Squirt Gun\", price: 42 },\n              number: 2,\n            },\n            {\n              item: { name: \"Water Balloons\", price: 1 },\n              number: 200,\n            },\n            {\n              item: { name: \"Garden Hose\", price: 10 },\n              number: 1,\n            },\n          ],\n        };\n\n        customer = filter.filterObject(customer, {\n          access: \"public\",\n          modelName: db.models.Customer.modelName,\n          populate: [\n            {\n              path: \"purchases.item\",\n            },\n          ],\n        });\n\n        customer.purchases.forEach((p) => {\n          assert.ok(\n            p.number === undefined,\n            \"Purchase number should be excluded\"\n          );\n          assert.ok(p.item, \"Item should be included\");\n          assert.ok(p.item.name !== undefined, \"Item name should be populated\");\n          assert.ok(\n            p.item.price === undefined,\n            \"Item price should be excluded\"\n          );\n        });\n      });\n    });\n  });\n\n  describe(\"not lean\", () => {\n    it(\"excludes items in the excluded string\", () => {\n      let customer = new db.models.Customer({\n        name: \"John\",\n        address: \"123 Drury Lane\",\n        comment: \"Has a big nose\",\n      });\n\n      customer = filter.filterObject(customer, {\n        access: \"public\",\n        modelName: db.models.Customer.modelName,\n      });\n      assert.equal(customer.name, \"John\", \"Customer name should be John\");\n      assert.ok(\n        customer.address === undefined,\n        \"Customer address should be excluded\"\n      );\n      assert.ok(\n        customer.comment === undefined,\n        \"Customer comment should be excluded\"\n      );\n    });\n\n    it(\"excludes fields from embedded documents\", () => {\n      let product = new db.models.Product({\n        name: \"Garden Hose\",\n        department: {\n          name: \"Gardening\",\n          code: 435,\n        },\n      });\n\n      product = filter.filterObject(product, {\n        access: \"public\",\n        modelName: db.models.Product.modelName,\n      });\n      assert.equal(\n        product.name,\n        \"Garden Hose\",\n        \"Product name should be included\"\n      );\n      assert.equal(\n        product.department.name,\n        \"Gardening\",\n        \"Deparment name should be included\"\n      );\n      assert.ok(\n        product.department.code === undefined,\n        \"Deparment code should be excluded\"\n      );\n    });\n\n    it(\"excludes fields from embedded arrays\", () => {\n      let customer = new db.models.Customer({\n        name: \"John\",\n        purchases: [\n          {\n            item: new mongoose.Types.ObjectId(),\n            number: 2,\n          },\n          {\n            item: new mongoose.Types.ObjectId(),\n            number: 100,\n          },\n          {\n            item: new mongoose.Types.ObjectId(),\n            number: 1,\n          },\n        ],\n      });\n\n      customer = filter.filterObject(customer, {\n        access: \"public\",\n        modelName: db.models.Customer.modelName,\n      });\n\n      customer.purchases.forEach((purchase) => {\n        assert.ok(purchase.item !== undefined, \"item should be included\");\n        assert.ok(purchase.number === undefined, \"number should be excluded\");\n      });\n    });\n\n    describe(\"with populated docs\", () => {\n      let products;\n      let invoiceId;\n      let customerId;\n\n      before((done) => {\n        products = [\n          {\n            name: \"Squirt Gun\",\n            department: {\n              code: 51,\n            },\n            price: 42,\n          },\n          {\n            name: \"Water Balloons\",\n            department: {\n              code: 819,\n            },\n            price: 1,\n          },\n          {\n            name: \"Garden Hose\",\n            department: {\n              code: 555,\n            },\n            price: 10,\n          },\n        ];\n\n        let createdProducts;\n\n        db.models.Product.create(products)\n          .then((products) => {\n            assert.ok(products);\n            createdProducts = products;\n\n            return new db.models.Customer({\n              name: \"John\",\n              address: \"123 Drury Lane\",\n              purchases: [\n                {\n                  item: createdProducts[0]._id,\n                  number: 2,\n                },\n                {\n                  item: createdProducts[1]._id,\n                  number: 100,\n                },\n                {\n                  item: createdProducts[2]._id,\n                  number: 1,\n                },\n              ],\n              favorites: {\n                purchase: {\n                  item: createdProducts[0]._id,\n                  number: 2,\n                },\n              },\n            }).save();\n          })\n          .then((customer) => {\n            assert.ok(customer);\n            customerId = customer._id;\n\n            return new db.models.Invoice({\n              customer: customer._id,\n              amount: 42,\n              products: [\n                createdProducts[0]._id,\n                createdProducts[1]._id,\n                createdProducts[2]._id,\n              ],\n            }).save();\n          })\n          .then((invoice) => {\n            assert.ok(invoice);\n            invoiceId = invoice._id;\n            done();\n          })\n          .catch(done);\n\n      });\n\n      after((done) => {\n        db.models.Customer.deleteMany()\n        .then(() => db.models.Invoice.deleteMany())\n        .then(() => db.models.Product.deleteMany())\n        .then(()=>{done();})\n        .catch(done);\n      });\n\n      it(\"excludes fields from populated items\", (done) => {\n        db.models.Invoice.findById(invoiceId)\n          .populate(\"customer\")\n          .exec()\n          .then((invoice) => {\n            assert.ok(invoice);\n            invoice = filter.filterObject(invoice, {\n              access: \"public\",\n              modelName: db.models.Invoice.modelName,\n              populate: [\n                {\n                  path: \"customer\",\n                },\n              ],\n            });\n            assert.ok(\n              invoice.amount === undefined,\n              \"Invoice amount should be excluded\"\n            );\n            assert.ok(\n              invoice.customer.name !== undefined,\n              \"Customer name should be included\"\n            );\n            assert.ok(\n              invoice.customer.address === undefined,\n              \"Customer address should be excluded\"\n            );\n            done();\n          })\n          .catch(done);\n      });\n\n      it(\"iterates through array of populated objects\", (done) => {\n        db.models.Invoice.findById(invoiceId)\n          .populate(\"products\")\n          .exec()\n          .then((invoice) => {\n            assert.ok(invoice);\n            invoice = filter.filterObject(invoice, {\n              access: \"public\",\n              modelName: db.models.Invoice.modelName,\n              populate: [\n                {\n                  path: \"products\",\n                },\n              ],\n            });\n\n            invoice.products.forEach((product) => {\n              assert.ok(\n                product.name !== undefined,\n                \"Product name should be populated\"\n              );\n              assert.ok(\n                product.price === undefined,\n                \"Product price should be excluded\"\n              );\n            });\n\n            done();\n          })\n          .catch(done);\n      });\n\n      it(\"filters multiple populated models\", (done) => {\n        db.models.Invoice.findById(invoiceId)\n          .populate(\"products customer\")\n          .exec()\n          .then((invoice) => {\n            assert.ok(invoice);\n            invoice = filter.filterObject(invoice, {\n              access: \"public\",\n              modelName: db.models.Invoice.modelName,\n              populate: [\n                {\n                  path: \"customer\",\n                },\n                {\n                  path: \"products\",\n                },\n              ],\n            });\n\n            assert.equal(\n              invoice.customer.name,\n              \"John\",\n              \"Customer name should be populated\"\n            );\n            assert.ok(\n              invoice.customer.address === undefined,\n              \"Customer address should be excluded\"\n            );\n\n            invoice.products.forEach((product) => {\n              assert.ok(\n                product.name !== undefined,\n                \"Product name should be populated\"\n              );\n              assert.ok(\n                product.price === undefined,\n                \"Product price should be excluded\"\n              );\n            });\n\n            done();\n          })\n          .catch(done);\n      });\n\n      it(\"filters nested populated docs\", (done) => {\n        db.models.Customer.findById(customerId)\n          .populate(\"favorites.purchase.item\")\n          .exec()\n          .then((customer) => {\n            assert.ok(customer);\n            customer = filter.filterObject(customer, {\n              access: \"public\",\n              modelName: db.models.Customer.modelName,\n              populate: [\n                {\n                  path: \"favorites.purchase.item\",\n                },\n              ],\n            });\n\n            assert.ok(\n              customer.favorites.purchase.item,\n              \"Purchased item should be included\"\n            );\n            assert.ok(\n              customer.favorites.purchase.item.number === undefined,\n              \"Purchased item number should be excluded\"\n            );\n            assert.ok(\n              customer.favorites.purchase.item.name !== undefined,\n              \"Purchased item name should be included\"\n            );\n            assert.ok(\n              customer.favorites.purchase.item.price === undefined,\n              \"Purchased item price should be excluded\"\n            );\n\n            done();\n          })\n          .catch(done);\n      });\n\n      it(\"filters embedded array of populated docs\", (done) => {\n        db.models.Customer.findById(customerId)\n          .populate(\"purchases.item\")\n          .exec()\n          .then((customer) => {\n            assert.ok(customer);\n            customer = filter.filterObject(customer, {\n              access: \"public\",\n              modelName: db.models.Customer.modelName,\n              populate: [\n                {\n                  path: \"purchases.item\",\n                },\n              ],\n            });\n\n            customer.purchases.forEach((p, i) => {\n              assert.ok(\n                p.number === undefined,\n                \"Purchase number should be excluded\"\n              );\n              assert.equal(\n                p.item.name,\n                products[i].name,\n                \"Item name should be populated\"\n              );\n              assert.ok(\n                p.item.price === undefined,\n                \"Item price should be excluded\"\n              );\n              assert.ok(p.item.department);\n              assert.ok(p.item.department.code === undefined);\n            });\n\n            done();\n          })\n          .catch(done);\n      });\n    });\n  });\n\n  describe(\"protected fields\", () => {\n    it(\"defaults to not including any\", () => {\n      const filter = new Filter();\n\n      filter.add(db.models.Invoice, {\n        filteredKeys: {\n          private: [\"amount\"],\n          protected: [\"products\"],\n        },\n      });\n\n      let invoice = {\n        customer: \"objectid\",\n        amount: 240,\n        products: [\"objectid\"],\n      };\n\n      invoice = filter.filterObject(invoice, {\n        access: \"public\",\n        modelName: db.models.Invoice.modelName,\n      });\n      assert.equal(invoice.customer, \"objectid\");\n      assert.ok(invoice.amount === undefined, \"Amount should be excluded\");\n      assert.ok(invoice.products === undefined, \"Products should be excluded\");\n    });\n\n    it(\"returns protected fields\", () => {\n      const filter = new Filter();\n\n      filter.add(db.models.Invoice, {\n        filteredKeys: {\n          private: [\"amount\"],\n          protected: [\"products\"],\n        },\n      });\n\n      let invoice = {\n        customer: \"objectid\",\n        amount: 240,\n        products: [\"objectid\"],\n      };\n\n      invoice = filter.filterObject(invoice, {\n        access: \"protected\",\n        modelName: db.models.Invoice.modelName,\n      });\n\n      assert.equal(invoice.customer, \"objectid\");\n      assert.ok(invoice.amount === undefined, \"Amount should be excluded\");\n      assert.equal(\n        invoice.products[0],\n        \"objectid\",\n        \"Products should be included\"\n      );\n    });\n  });\n\n  describe(\"descriminated schemas\", () => {\n    const filter = new Filter();\n\n    before((done) => {\n      filter.add(db.models.Account, {\n        filteredKeys: {\n          private: [\"accountNumber\"],\n          protected: [],\n        },\n      });\n\n      filter.add(db.models.RepeatCustomer, {\n        filteredKeys: {\n          private: [],\n          protected: [],\n        },\n      });\n\n      db.models.Account.create({\n        accountNumber: \"123XYZ\",\n        points: 244,\n      })\n        .then((account) => {\n          assert.ok(account);\n          return db.models.RepeatCustomer.create({\n            name: \"John Smith\",\n            account: account._id,\n          });\n        })\n        .then(() => done())\n        .catch(done);      \n    });\n\n    after((done) => {\n      db.models.Account.deleteMany()\n        .then(() => db.models.Customer.deleteMany())\n        .then(()=>{done();})\n        .catch(done);\n    });\n\n    it.skip(\"should filter populated from subschema\", (done) => {\n      db.models.RepeatCustomer.findOne()\n        .populate(\"account\")\n        .exec((err, doc) => {\n          assert.ok(!err);\n          let customer = filter.filterObject(doc, {\n            access: \"public\",\n            modelName: db.models.Customer.modelName,\n            populate: [\n              {\n                path: \"account\",\n              },\n            ],\n          });\n          assert.equal(customer.name, \"John Smith\");\n          assert.equal(customer.account.points, 244);\n          assert.ok(\n            customer.account.accountNumber === undefined,\n            \"account number should be excluded\"\n          );\n          done();\n        });\n    });\n\n    it.skip(\"should filter populated from base schema\", (done) => {\n      db.models.Customer.findOne()\n        .populate(\"account\")\n        .exec((err, doc) => {\n          assert.ok(!err);\n          doc.populate(\"account\", (err, doc) => {\n            assert.ok(!err);\n            let customer = filter.filterObject(doc, {\n              access: \"public\",\n              modelName: db.models.Customer.modelName,\n              populate: [\n                {\n                  path: \"account\",\n                },\n              ],\n            });\n            assert.equal(customer.name, \"John Smith\");\n            assert.equal(customer.account.points, 244);\n            assert.ok(\n              customer.account.accountNumber === undefined,\n              \"account number should be excluded\"\n            );\n            done();\n          });\n        });\n    });\n  });\n});\n"
  },
  {
    "path": "test/integration/setup.mjs",
    "content": "import mongoose, { Schema } from \"mongoose\";\n\nexport default function () {\n  const ProductSchema = new Schema({\n    name: { type: String, required: true },\n    department: {\n      name: { type: String },\n      code: { type: Number },\n    },\n    price: { type: Number },\n  });\n\n  class BaseCustomerSchema extends Schema {\n    constructor(definition, options) {\n      const def = Object.assign(definition, {\n        account: { type: Schema.Types.ObjectId, ref: \"Account\" },\n        name: { type: String, required: true, unique: true },\n        comment: { type: String },\n        address: { type: String },\n        age: { type: Number },\n        favorites: {\n          animal: { type: String },\n          color: { type: String },\n          purchase: {\n            item: { type: Schema.Types.ObjectId, ref: \"Product\" },\n            number: { type: Number },\n          },\n        },\n        purchases: [\n          {\n            item: { type: Schema.Types.ObjectId, ref: \"Product\" },\n            number: { type: Number },\n          },\n        ],\n        returns: [{ type: Schema.Types.ObjectId, ref: \"Product\" }],\n        creditCard: { type: String, access: \"protected\" },\n        ssn: { type: String, access: \"private\" },\n        coordinates: { type: [Number], index: \"2dsphere\" },\n      });\n\n      super(def, options);\n    }\n  }\n\n  const CustomerSchema = new BaseCustomerSchema(\n    {},\n    {\n      toObject: { virtuals: true },\n      toJSON: { virtuals: true },\n    }\n  );\n\n  CustomerSchema.virtual(\"info\").get(function () {\n    return this.name + \" is awesome\";\n  });\n\n  const InvoiceSchema = new Schema(\n    {\n      customer: { type: Schema.Types.ObjectId, ref: \"Customer\" },\n      amount: { type: Number },\n      receipt: { type: String },\n      products: [{ type: Schema.Types.ObjectId, ref: \"Product\" }],\n    },\n    {\n      toObject: { virtuals: true },\n      toJSON: { virtuals: true },\n      versionKey: \"__version\",\n    }\n  );\n\n  const RepeatCustomerSchema = new BaseCustomerSchema({\n    account: { type: Schema.Types.ObjectId, ref: \"Account\" },\n    visits: { type: Number },\n    status: { type: String },\n    job: { type: String },\n  });\n\n  const AccountSchema = new Schema({\n    accountNumber: String,\n    points: Number,\n  });\n\n  const HooksSchema = new Schema({\n    preSaveError: Boolean,\n    postSaveError: Boolean,\n  });\n\n  HooksSchema.pre(\"save\", true, function (next, done) {\n    next();\n    setTimeout(() => {\n      done(this.preSaveError ? new Error(\"AsyncPreSaveError\") : null);\n    }, 42);\n  });\n\n  HooksSchema.post(\"save\", function (doc, next) {\n    setTimeout(() => {\n      next(doc.postSaveError ? new Error(\"AsyncPostSaveError\") : null);\n    }, 42);\n  });\n\n  function initialize(opts, callback) {\n    if (typeof opts === \"function\") {\n      callback = opts;\n      opts = {};\n    }\n\n    opts = {\n      connect: true,\n      ...opts,\n    };\n\n    if (!mongoose.models.Customer) {\n      mongoose.model(\"Customer\", CustomerSchema);\n    }\n\n    if (!mongoose.models.Invoice) {\n      mongoose.model(\"Invoice\", InvoiceSchema);\n    }\n\n    if (!mongoose.models.Product) {\n      mongoose.model(\"Product\", ProductSchema);\n    }\n\n    if (!mongoose.models.RepeatCustomer) {\n      mongoose.models.Customer.discriminator(\n        \"RepeatCustomer\",\n        RepeatCustomerSchema\n      );\n    }\n\n    if (!mongoose.models.Account) {\n      mongoose.model(\"Account\", AccountSchema);\n    }\n\n    if (!mongoose.models.Hook) {\n      mongoose.model(\"Hook\", HooksSchema);\n    }\n\n    if (opts.connect) {\n      const uri = process.env.MONGO_URL || \"mongodb://localhost/database\";\n      mongoose.connect(uri).then(function () {\n        callback();\n      });\n    } else if (typeof callback === \"function\") {\n      callback();\n    }\n  }\n\n  function reset(callback) {\n    Promise.all([\n      mongoose.models.Customer.deleteMany().exec(),\n      mongoose.models.Invoice.deleteMany().exec(),\n      mongoose.models.Product.deleteMany().exec(),\n      mongoose.models.RepeatCustomer.deleteMany().exec(),\n      mongoose.models.Account.deleteMany().exec(),\n    ])\n      .then(() => callback())\n      .catch(callback);\n  }\n\n  function close(callback) {\n    mongoose.connection.close()\n      .then(()=>{\n        callback()\n      })\n      .catch(callback)\n  }\n\n  return {\n    initialize: initialize,\n    models: mongoose.models,\n    reset: reset,\n    close: close,\n  };\n}\n"
  },
  {
    "path": "test/integration/update.mjs",
    "content": "import assert from \"assert\";\nimport mongoose from \"mongoose\";\nimport request from \"request\";\nimport { serve } from \"../../dist/express-restify-mongoose.js\";\n\nimport setupDb from \"./setup.mjs\";\n\nexport default function (createFn, setup, dismantle) {\n  const db = setupDb();\n\n  const testPort = 30023;\n  const testUrl = `http://localhost:${testPort}`;\n  const invalidId = \"invalid-id\";\n  const randomId = new mongoose.Types.ObjectId().toHexString();\n  const updateMethods = [\"PATCH\", \"POST\", \"PUT\"];\n\n  describe(\"Update documents\", () => {\n    describe(\"findOneAndUpdate: true\", () => {\n      let app = createFn();\n      let server;\n      let customers;\n      let products;\n      let invoice;\n\n      before((done) => {\n        setup((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          serve(app, db.models.Customer, {\n            findOneAndUpdate: true,\n            restify: app.isRestify,\n          });\n\n          serve(app, db.models.Invoice, {\n            findOneAndUpdate: true,\n            restify: app.isRestify,\n          });\n\n          server = app.listen(testPort, done);\n        });\n      });\n\n      beforeEach((done) => {\n        db.reset((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          db.models.Customer.create([\n            {\n              name: \"Bob\",\n            },\n            {\n              name: \"John\",\n            },\n          ])\n            .then((createdCustomers) => {\n              customers = createdCustomers;\n\n              return db.models.Product.create([\n                {\n                  name: \"Bobsleigh\",\n                },\n                {\n                  name: \"Jacket\",\n                },\n              ]);\n            })\n            .then((createdProducts) => {\n              products = createdProducts;\n\n              return db.models.Invoice.create({\n                customer: customers[0]._id,\n                products: createdProducts,\n                amount: 100,\n              });\n            })\n            .then((createdInvoice) => {\n              invoice = createdInvoice;\n\n              return db.models.Customer.create({\n                name: \"Jane\",\n                purchases: [\n                  {\n                    item: products[0]._id,\n                    number: 1,\n                  },\n                  {\n                    item: products[1]._id,\n                    number: 3,\n                  },\n                ],\n                returns: [products[0]._id, products[1]._id],\n              });\n            })\n            .then((customer) => {\n              customers.push(customer);\n            })\n            .then(done)\n            .catch(done);\n        });\n      });\n\n      after((done) => {\n        dismantle(app, server, done);\n      });\n\n      updateMethods.forEach((method) => {\n        it(`${method} /Customer/:id 200 - empty body`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${customers[0]._id}`,\n              json: {},\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.name, \"Bob\");\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Customer/:id 200 - created id`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${customers[0]._id}`,\n              json: {\n                name: \"Mike\",\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.name, \"Mike\");\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Customer/:id 400 - cast error`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${customers[0]._id}`,\n              json: {\n                age: \"not a number\",\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 400);\n              delete body.reason;\n              assert.deepEqual(body, {\n                kind: \"Number\",\n                message:\n                  'Cast to Number failed for value \"not a number\" (type string) at path \"age\"',\n                name: \"CastError\",\n                path: \"age\",\n                stringValue: '\"not a number\"',\n                value: \"not a number\",\n                valueType: \"string\",\n              });\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Customer/:id 400 - mongo error`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${customers[0]._id}`,\n              json: {\n                name: \"John\",\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 400);\n              assert.equal(body.name, \"MongoServerError\");\n              // Remove extra whitespace and allow code 11001 for MongoDB < 3\n              assert.ok(\n                body.message\n                  .replace(/\\s+/g, \" \")\n                  .replace(\"exception: \", \"\")\n                  .match(\n                    /E11000 duplicate key error (?:index|collection): database.customers(?:\\.\\$| index: )name_1 dup key: { (?:name|): \"John\" }/\n                  ) !== null\n              );\n              assert.ok(body.code === 11000 || body.code === 11001);\n              assert.ok(!body.codeName || body.codeName === \"DuplicateKey\"); // codeName is optional\n              assert.equal(body.ok, 0);\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Customer/:id 400 - missing content type`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${customers[0]._id}`,\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 400);\n              assert.deepEqual(JSON.parse(body), {\n                name: \"Error\",\n                message: \"missing_content_type\",\n              });\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Customer/:id 400 - invalid content type`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${customers[0]._id}`,\n              formData: {},\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 400);\n              assert.deepEqual(JSON.parse(body), {\n                name: \"Error\",\n                message: \"invalid_content_type\",\n              });\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Customer/:id 404 - invalid id`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${invalidId}`,\n              json: {\n                name: \"Mike\",\n              },\n            },\n            (err, res) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 404);\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Customer/:id 404 - random id`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${randomId}`,\n              json: {\n                name: \"Mike\",\n              },\n            },\n            (err, res) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 404);\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Invoice/:id 200 - referencing customer and product ids as strings`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Invoice/${invoice._id}`,\n              json: {\n                customer: customers[1]._id.toHexString(),\n                products: products[1]._id.toHexString(),\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.customer, customers[1]._id);\n              assert.equal(body.products[0], products[1]._id);\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Invoice/:id 200 - referencing customer and products ids as strings`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Invoice/${invoice._id}`,\n              json: {\n                customer: customers[1]._id.toHexString(),\n                products: [products[1]._id.toHexString()],\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.customer, customers[1]._id);\n              assert.equal(body.products[0], products[1]._id);\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Invoice/:id 200 - referencing customer and product ids`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Invoice/${invoice._id}`,\n              json: {\n                customer: customers[1]._id,\n                products: products[1]._id,\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.customer, customers[1]._id);\n              assert.equal(body.products[0], products[1]._id);\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Invoice/:id 200 - referencing customer and products ids`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Invoice/${invoice._id}`,\n              json: {\n                customer: customers[1]._id,\n                products: [products[1]._id],\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.customer, customers[1]._id);\n              assert.equal(body.products[0], products[1]._id);\n              done();\n            }\n          );\n        });\n\n        describe(\"populated subdocument\", () => {\n          it(`${method} /Invoice/:id 200 - update with populated customer`, (done) => {\n            db.models.Invoice.findById(invoice._id)\n              .populate(\"customer\")\n              .exec()\n              .then((invoice) => {\n                assert.notEqual(invoice.amount, 200);\n                invoice.amount = 200;\n\n                request(\n                  {\n                    method,\n                    url: `${testUrl}/api/v1/Invoice/${invoice._id}`,\n                    json: invoice,\n                  },\n                  (err, res, body) => {\n                    assert.ok(!err);\n                    assert.equal(res.statusCode, 200);\n                    assert.equal(body.amount, 200);\n                    assert.equal(body.customer, invoice.customer._id);\n                    done();\n                  }\n                );\n              })\n              .catch(done);\n          });\n\n          it(`${method} /Invoice/:id 200 - update with populated products`, (done) => {\n            db.models.Invoice.findById(invoice._id)\n              .populate(\"products\")\n              .exec()\n              .then((invoice) => {\n                assert.notEqual(invoice.amount, 200);\n                invoice.amount = 200;\n\n                request(\n                  {\n                    method,\n                    url: `${testUrl}/api/v1/Invoice/${invoice._id}`,\n                    json: invoice,\n                  },\n                  (err, res, body) => {\n                    assert.ok(!err);\n                    assert.equal(res.statusCode, 200);\n                    assert.equal(body.amount, 200);\n                    assert.deepEqual(body.products, [\n                      invoice.products[0]._id.toHexString(),\n                      invoice.products[1]._id.toHexString(),\n                    ]);\n                    done();\n                  }\n                );\n              })\n              .catch(done);\n          });\n\n          it(`${method} /Invoice/:id?populate=customer,products 200 - update with populated customer`, (done) => {\n            db.models.Invoice.findById(invoice._id)\n              .populate(\"customer products\")\n              .exec()\n              .then((invoice) => {\n                request(\n                  {\n                    method,\n                    url: `${testUrl}/api/v1/Invoice/${invoice._id}`,\n                    qs: {\n                      populate: \"customer,products\",\n                    },\n                    json: invoice,\n                  },\n                  (err, res, body) => {\n                    assert.ok(!err);\n                    assert.equal(res.statusCode, 200);\n                    assert.ok(body.customer);\n                    assert.equal(body.customer._id, invoice.customer._id);\n                    assert.equal(body.customer.name, invoice.customer.name);\n                    assert.ok(body.products);\n                    assert.equal(\n                      body.products[0]._id,\n                      invoice.products[0]._id.toHexString()\n                    );\n                    assert.equal(\n                      body.products[0].name,\n                      invoice.products[0].name\n                    );\n                    assert.equal(\n                      body.products[1]._id,\n                      invoice.products[1]._id.toHexString()\n                    );\n                    assert.equal(\n                      body.products[1].name,\n                      invoice.products[1].name\n                    );\n                    done();\n                  }\n                );\n              })\n              .catch(done);\n          });\n\n          it(`${method} /Customer/:id 200 - update with reduced count of populated returns`, (done) => {\n            db.models.Customer.findOne({ name: \"Jane\" })\n              .populate(\"purchases returns\")\n              .exec()\n              .then((customer) => {\n                customer.returns = [customer.returns[1]];\n                request(\n                  {\n                    method,\n                    url: `${testUrl}/api/v1/Customer/${customer._id}`,\n                    qs: {\n                      populate: \"returns,purchases.item\",\n                    },\n                    json: customer,\n                  },\n                  (err, res, body) => {\n                    assert.ok(!err);\n                    assert.equal(res.statusCode, 200);\n                    assert.ok(body.returns);\n                    assert.equal(body.returns.length, 1);\n                    assert.equal(body.returns[0]._id, products[1]._id);\n                    done();\n                  }\n                );\n              })\n              .catch(done);\n          });\n        });\n      });\n\n      it(\"PATCH /Customer 404 (Express), 405 (Restify)\", (done) => {\n        request.patch(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            json: {},\n          },\n          (err, res) => {\n            assert.ok(!err);\n            if (app.isRestify) {\n              assert.equal(res.statusCode, 405);\n            } else {\n              assert.equal(res.statusCode, 404);\n            }\n            done();\n          }\n        );\n      });\n\n      it(\"PUT /Customer 404 (Express), 405 (Restify)\", (done) => {\n        request.put(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            json: {},\n          },\n          (err, res) => {\n            assert.ok(!err);\n            if (app.isRestify) {\n              assert.equal(res.statusCode, 405);\n            } else {\n              assert.equal(res.statusCode, 404);\n            }\n            done();\n          }\n        );\n      });\n    });\n\n    describe(\"findOneAndUpdate: false\", () => {\n      let app = createFn();\n      let server;\n      let customers;\n      let products;\n      let invoice;\n\n      before((done) => {\n        setup((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          serve(app, db.models.Customer, {\n            findOneAndUpdate: false,\n            restify: app.isRestify,\n          });\n\n          serve(app, db.models.Invoice, {\n            findOneAndUpdate: false,\n            restify: app.isRestify,\n          });\n\n          server = app.listen(testPort, done);\n        });\n      });\n\n      beforeEach((done) => {\n        db.reset((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          db.models.Customer.create([\n            {\n              name: \"Bob\",\n            },\n            {\n              name: \"John\",\n            },\n          ])\n            .then((createdCustomers) => {\n              customers = createdCustomers;\n\n              return db.models.Product.create([\n                {\n                  name: \"Bobsleigh\",\n                },\n                {\n                  name: \"Jacket\",\n                },\n              ]);\n            })\n            .then((createdProducts) => {\n              products = createdProducts;\n\n              return db.models.Invoice.create({\n                customer: customers[0]._id,\n                products: createdProducts,\n                amount: 100,\n              });\n            })\n            .then((createdInvoice) => {\n              invoice = createdInvoice;\n\n              return db.models.Customer.create({\n                name: \"Jane\",\n                purchases: [\n                  {\n                    item: products[0]._id,\n                    number: 1,\n                  },\n                  {\n                    item: products[1]._id,\n                    number: 3,\n                  },\n                ],\n                returns: [products[0]._id, products[1]._id],\n              });\n            })\n            .then((customer) => {\n              customers.push(customer);\n            })\n            .then(done)\n            .catch(done);\n        });\n      });\n\n      after((done) => {\n        dismantle(app, server, done);\n      });\n\n      updateMethods.forEach((method) => {\n        it(`${method} /Customer/:id 200 - empty body`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${customers[0]._id}`,\n              json: {},\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.name, \"Bob\");\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Customer/:id 200 - created id`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${customers[0]._id}`,\n              json: {\n                name: \"Mike\",\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.name, \"Mike\");\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Customer/:id 400 - validation error`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${customers[0]._id}`,\n              json: {\n                age: \"not a number\",\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 400);\n              assert.deepEqual(body, {\n                name: \"ValidationError\",\n                _message: \"Customer validation failed\",\n                message:\n                  'Customer validation failed: age: Cast to Number failed for value \"not a number\" (type string) at path \"age\"',\n                errors: {\n                  age: {\n                    kind: \"Number\",\n                    message:\n                      'Cast to Number failed for value \"not a number\" (type string) at path \"age\"',\n                    name: \"CastError\",\n                    path: \"age\",\n                    stringValue: '\"not a number\"',\n                    value: \"not a number\",\n                    valueType: \"string\",\n                  },\n                },\n              });\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Customer/:id 400 - mongo error`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${customers[0]._id}`,\n              json: {\n                name: \"John\",\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 400);\n              // Remove extra whitespace, allow 6, 8, or 9 keys and code 11001 for MongoDB < 3\n              assert.equal(body.name, \"MongoServerError\");\n              assert.ok(\n                body.message\n                  .replace(/\\s+/g, \" \")\n                  .replace(\"exception: \", \"\")\n                  .match(\n                    /E11000 duplicate key error (?:index|collection): database.customers(?:\\.\\$| index: )name_1 dup key: { (?:name|): \"John\" }/\n                  ) !== null\n              );\n              assert.ok(body.code === 11000 || body.code === 11001);\n              assert.ok(!body.writeErrors || body.writeErrors.length === 1);\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Customer/:id 400 - missing content type`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${customers[0]._id}`,\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 400);\n              assert.deepEqual(JSON.parse(body), {\n                name: \"Error\",\n                message: \"missing_content_type\",\n              });\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Customer/:id 400 - invalid content type`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${customers[0]._id}`,\n              formData: {\n                name: \"Mike\",\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 400);\n              assert.deepEqual(JSON.parse(body), {\n                name: \"Error\",\n                message: \"invalid_content_type\",\n              });\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Customer/:id 404 - invalid id`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${invalidId}`,\n              json: {\n                name: \"Mike\",\n              },\n            },\n            (err, res) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 404);\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Customer/:id 404 - random id`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Customer/${randomId}`,\n              json: {\n                name: \"Mike\",\n              },\n            },\n            (err, res) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 404);\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Invoice/:id 200 - referencing customer and product ids as strings`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Invoice/${invoice._id}`,\n              json: {\n                customer: customers[1]._id.toHexString(),\n                products: products[1]._id.toHexString(),\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.customer, customers[1]._id);\n              assert.equal(body.products[0], products[1]._id);\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Invoice/:id 200 - referencing customer and products ids as strings`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Invoice/${invoice._id}`,\n              json: {\n                customer: customers[1]._id.toHexString(),\n                products: [products[1]._id.toHexString()],\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.customer, customers[1]._id);\n              assert.equal(body.products[0], products[1]._id);\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Invoice/:id 200 - referencing customer and product ids`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Invoice/${invoice._id}`,\n              json: {\n                customer: customers[1]._id,\n                products: products[1]._id,\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.customer, customers[1]._id);\n              assert.equal(body.products[0], products[1]._id);\n              done();\n            }\n          );\n        });\n\n        it(`${method} /Invoice/:id 200 - referencing customer and products ids`, (done) => {\n          request(\n            {\n              method,\n              url: `${testUrl}/api/v1/Invoice/${invoice._id}`,\n              json: {\n                customer: customers[1]._id,\n                products: [products[1]._id],\n              },\n            },\n            (err, res, body) => {\n              assert.ok(!err);\n              assert.equal(res.statusCode, 200);\n              assert.equal(body.customer, customers[1]._id);\n              assert.equal(body.products[0], products[1]._id);\n              done();\n            }\n          );\n        });\n\n        describe(\"populated subdocument\", () => {\n          it(`${method} /Invoice/:id 200 - update with populated customer`, (done) => {\n            db.models.Invoice.findById(invoice._id)\n              .populate(\"customer\")\n              .exec()\n              .then((invoice) => {\n                assert.notEqual(invoice.amount, 200);\n                invoice.amount = 200;\n\n                request(\n                  {\n                    method,\n                    url: `${testUrl}/api/v1/Invoice/${invoice._id}`,\n                    json: invoice,\n                  },\n                  (err, res, body) => {\n                    assert.ok(!err);\n                    assert.equal(res.statusCode, 200);\n                    assert.equal(body.amount, 200);\n                    assert.equal(body.customer, invoice.customer._id);\n                    done();\n                  }\n                );\n              })\n              .catch(done);\n          });\n\n          it(`${method} /Invoice/:id 200 - update with populated products`, (done) => {\n            db.models.Invoice.findById(invoice._id)\n              .populate(\"products\")\n              .exec()\n              .then((invoice) => {\n                assert.notEqual(invoice.amount, 200);\n                invoice.amount = 200;\n\n                request(\n                  {\n                    method,\n                    url: `${testUrl}/api/v1/Invoice/${invoice._id}`,\n                    json: invoice,\n                  },\n                  (err, res, body) => {\n                    assert.ok(!err);\n                    assert.equal(res.statusCode, 200);\n                    assert.equal(body.amount, 200);\n                    assert.deepEqual(body.products, [\n                      invoice.products[0]._id.toHexString(),\n                      invoice.products[1]._id.toHexString(),\n                    ]);\n                    done();\n                  }\n                );\n              })\n              .catch(done);\n          });\n\n          it(`${method} /Invoice/:id?populate=customer,products 200 - update with populated customer`, (done) => {\n            db.models.Invoice.findById(invoice._id)\n              .populate(\"customer products\")\n              .exec()\n              .then((invoice) => {\n                request(\n                  {\n                    method,\n                    url: `${testUrl}/api/v1/Invoice/${invoice._id}`,\n                    qs: {\n                      populate: \"customer,products\",\n                    },\n                    json: invoice,\n                  },\n                  (err, res, body) => {\n                    assert.ok(!err);\n                    assert.equal(res.statusCode, 200);\n                    assert.ok(body.customer);\n                    assert.equal(body.customer._id, invoice.customer._id);\n                    assert.equal(body.customer.name, invoice.customer.name);\n                    assert.ok(body.products);\n                    assert.equal(\n                      body.products[0]._id,\n                      invoice.products[0]._id.toHexString()\n                    );\n                    assert.equal(\n                      body.products[0].name,\n                      invoice.products[0].name\n                    );\n                    assert.equal(\n                      body.products[1]._id,\n                      invoice.products[1]._id.toHexString()\n                    );\n                    assert.equal(\n                      body.products[1].name,\n                      invoice.products[1].name\n                    );\n                    done();\n                  }\n                );\n              })\n              .catch(done);\n          });\n\n          it(`${method} /Customer/:id 200 - update with reduced count of populated returns`, (done) => {\n            db.models.Customer.findOne({ name: \"Jane\" })\n              .populate(\"purchases returns\")\n              .exec()\n              .then((customer) => {\n                customer.returns = [customer.returns[1]];\n                request(\n                  {\n                    method,\n                    url: `${testUrl}/api/v1/Customer/${customer._id}`,\n                    qs: {\n                      populate: \"returns,purchases.item\",\n                    },\n                    json: customer,\n                  },\n                  (err, res, body) => {\n                    assert.ok(!err);\n                    assert.equal(res.statusCode, 200);\n                    assert.ok(body.returns);\n                    assert.equal(body.returns.length, 1);\n                    assert.equal(body.returns[0]._id, products[1]._id);\n                    done();\n                  }\n                );\n              })\n              .catch(done);\n          });\n        });\n      });\n\n      it(\"PATCH /Customer 404 (Express), 405 (Restify)\", (done) => {\n        request.patch(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            json: {},\n          },\n          (err, res) => {\n            assert.ok(!err);\n            if (app.isRestify) {\n              assert.equal(res.statusCode, 405);\n            } else {\n              assert.equal(res.statusCode, 404);\n            }\n            done();\n          }\n        );\n      });\n\n      it(\"PUT /Customer 404 (Express), 405 (Restify)\", (done) => {\n        request.put(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            json: {},\n          },\n          (err, res) => {\n            assert.ok(!err);\n            if (app.isRestify) {\n              assert.equal(res.statusCode, 405);\n            } else {\n              assert.equal(res.statusCode, 404);\n            }\n            done();\n          }\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "test/integration/virtuals.mjs",
    "content": "import assert from \"assert\";\nimport request from \"request\";\nimport { serve } from \"../../dist/express-restify-mongoose.js\";\n\nimport setupDb from \"./setup.mjs\";\n\nexport default function (createFn, setup, dismantle) {\n  const db = setupDb();\n\n  const testPort = 30023;\n  const testUrl = `http://localhost:${testPort}`;\n\n  describe(\"virtuals\", () => {\n    describe(\"lean: true\", () => {\n      let app = createFn();\n      let server;\n\n      before((done) => {\n        setup((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          serve(app, db.models.Customer, {\n            lean: true,\n            restify: app.isRestify,\n          });\n\n          server = app.listen(testPort, done);\n        });\n      });\n\n      beforeEach((done) => {\n        db.reset((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          db.models.Customer.create({\n            name: \"Bob\",\n          })\n            .then(() => done())\n            .catch(done);\n        });\n      });\n\n      after((done) => {\n        dismantle(app, server, done);\n      });\n\n      it(\"GET /Customer 200 - unavailable\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 1);\n            assert.equal(body[0].info, undefined);\n            done();\n          }\n        );\n      });\n    });\n\n    describe(\"lean: false\", () => {\n      let app = createFn();\n      let server;\n\n      before((done) => {\n        setup((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          serve(app, db.models.Customer, {\n            lean: false,\n            restify: app.isRestify,\n          });\n\n          server = app.listen(testPort, done);\n        });\n      });\n\n      beforeEach((done) => {\n        db.reset((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          db.models.Customer.create({\n            name: \"Bob\",\n          })\n            .then(() => done())\n            .catch(done);\n        });\n      });\n\n      after((done) => {\n        dismantle(app, server, done);\n      });\n\n      it(\"GET /Customer 200 - available\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            json: true,\n          },\n          (err, res, body) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            assert.equal(body.length, 1);\n            assert.equal(body[0].info, \"Bob is awesome\");\n            done();\n          }\n        );\n      });\n    });\n\n    describe(\"readPreference: secondary\", () => {\n      let app = createFn();\n      let server;\n\n      before((done) => {\n        setup((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          serve(app, db.models.Customer, {\n            readPreference: \"secondary\",\n            restify: app.isRestify,\n          });\n\n          server = app.listen(testPort, done);\n        });\n      });\n\n      beforeEach((done) => {\n        db.reset((err) => {\n          if (err) {\n            return done(err);\n          }\n\n          db.models.Customer.create({\n            name: \"Bob\",\n          })\n            .then(() => done())\n            .catch(done);\n        });\n      });\n\n      after((done) => {\n        dismantle(app, server, done);\n      });\n\n      it(\"GET /Customer 200 - available\", (done) => {\n        request.get(\n          {\n            url: `${testUrl}/api/v1/Customer`,\n            json: true,\n          },\n          (err, res) => {\n            assert.ok(!err);\n            assert.equal(res.statusCode, 200);\n            done();\n          }\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "test/restify.mjs",
    "content": "import restify from \"restify\";\n\nimport accessTests from \"./integration/access.mjs\";\nimport contextFilterTests from \"./integration/contextFilter.mjs\";\nimport createTests from \"./integration/create.mjs\";\nimport deleteTests from \"./integration/delete.mjs\";\nimport hookTests from \"./integration/hooks.mjs\";\nimport middlewareTests from \"./integration/middleware.mjs\";\nimport optionsTests from \"./integration/options.mjs\";\nimport readTests from \"./integration/read.mjs\";\nimport updateTests from \"./integration/update.mjs\";\nimport virtualsTests from \"./integration/virtuals.mjs\";\n\nimport setupDb from \"./integration/setup.mjs\";\n\nconst db = setupDb();\n\nfunction Restify() {\n  let app = restify.createServer();\n  app.use(restify.plugins.queryParser());\n  app.use(restify.plugins.bodyParser());\n  app.isRestify = true;\n  return app;\n}\n\nfunction setup(callback) {\n  db.initialize((err) => {\n    if (err) {\n      return callback(err);\n    }\n\n    db.reset(callback);\n  });\n}\n\nfunction dismantle(app, server, callback) {\n  db.close((err) => {\n    if (err) {\n      return callback(err);\n    }\n\n    if (app.close) {\n      return app.close(callback);\n    }\n\n    server.close(callback);\n  });\n}\n\nfunction runTests(createFn) {\n  describe(createFn.name, () => {\n    createTests(createFn, setup, dismantle);\n    readTests(createFn, setup, dismantle);\n    updateTests(createFn, setup, dismantle);\n    deleteTests(createFn, setup, dismantle);\n    accessTests(createFn, setup, dismantle);\n    contextFilterTests(createFn, setup, dismantle);\n    hookTests(createFn, setup, dismantle);\n    middlewareTests(createFn, setup, dismantle);\n    optionsTests(createFn, setup, dismantle);\n    virtualsTests(createFn, setup, dismantle);\n  });\n}\n\nrunTests(Restify);\n"
  },
  {
    "path": "test/unit/buildQuery.mjs",
    "content": "import assert from \"assert\";\nimport sinon from \"sinon\";\nimport { getBuildQuery } from \"../../dist/buildQuery.js\";\n\ndescribe(\"buildQuery\", () => {\n  let query = {\n    where: sinon.spy(),\n    skip: sinon.spy(),\n    limit: sinon.spy(),\n    sort: sinon.spy(),\n    select: sinon.spy(),\n    populate: sinon.spy(),\n    distinct: sinon.spy(),\n  };\n\n  afterEach(() => {\n    for (let key in query) {\n      query[key].resetHistory();\n    }\n  });\n\n  it(\"does not call any methods and returns a query object\", () => {\n    return getBuildQuery({})(query).then((result) => {\n      for (let key in query) {\n        sinon.assert.notCalled(query[key]);\n      }\n\n      assert.equal(result, query);\n    });\n  });\n\n  describe(\"query\", () => {\n    it(\"calls where and returns a query object\", () => {\n      let queryOptions = {\n        query: \"foo\",\n      };\n\n      return getBuildQuery({})(query, queryOptions).then((result) => {\n        sinon.assert.calledOnce(query.where);\n        sinon.assert.calledWithExactly(query.where, queryOptions.query);\n        sinon.assert.notCalled(query.skip);\n        sinon.assert.notCalled(query.limit);\n        sinon.assert.notCalled(query.sort);\n        sinon.assert.notCalled(query.select);\n        sinon.assert.notCalled(query.populate);\n        sinon.assert.notCalled(query.distinct);\n        assert.equal(result, query);\n      });\n    });\n  });\n\n  describe(\"skip\", () => {\n    it(\"calls skip and returns a query object\", () => {\n      let queryOptions = {\n        skip: \"1\",\n      };\n\n      return getBuildQuery({})(query, queryOptions).then((result) => {\n        sinon.assert.calledOnce(query.skip);\n        sinon.assert.calledWithExactly(query.skip, queryOptions.skip);\n        sinon.assert.notCalled(query.where);\n        sinon.assert.notCalled(query.limit);\n        sinon.assert.notCalled(query.sort);\n        sinon.assert.notCalled(query.select);\n        sinon.assert.notCalled(query.populate);\n        sinon.assert.notCalled(query.distinct);\n        assert.equal(result, query);\n      });\n    });\n  });\n\n  describe(\"limit\", () => {\n    it(\"calls limit and returns a query object\", () => {\n      let queryOptions = {\n        limit: \"1\",\n      };\n\n      return getBuildQuery({})(query, queryOptions).then((result) => {\n        sinon.assert.calledOnce(query.limit);\n        sinon.assert.calledWithExactly(query.limit, queryOptions.limit);\n        sinon.assert.notCalled(query.where);\n        sinon.assert.notCalled(query.skip);\n        sinon.assert.notCalled(query.sort);\n        sinon.assert.notCalled(query.select);\n        sinon.assert.notCalled(query.populate);\n        sinon.assert.notCalled(query.distinct);\n        assert.equal(result, query);\n      });\n    });\n\n    it(\"calls limit and returns a query object\", () => {\n      let options = {\n        limit: 1,\n      };\n\n      let queryOptions = {\n        limit: \"2\",\n      };\n\n      return getBuildQuery(options)(query, queryOptions).then((result) => {\n        sinon.assert.calledOnce(query.limit);\n        sinon.assert.calledWithExactly(query.limit, options.limit);\n        sinon.assert.notCalled(query.where);\n        sinon.assert.notCalled(query.skip);\n        sinon.assert.notCalled(query.sort);\n        sinon.assert.notCalled(query.select);\n        sinon.assert.notCalled(query.populate);\n        sinon.assert.notCalled(query.distinct);\n        assert.equal(result, query);\n      });\n    });\n\n    it(\"does not call limit on count endpoint and returns a query object\", () => {\n      let queryOptions = {\n        limit: \"2\",\n      };\n\n      query.op = \"countDocuments\";\n\n      return getBuildQuery({})(query, queryOptions).then((result) => {\n        delete query.op;\n\n        for (let key in query) {\n          sinon.assert.notCalled(query[key]);\n        }\n\n        assert.equal(result, query);\n      });\n    });\n\n    it(\"does not call limit on count endpoint and returns a query object\", () => {\n      let options = {\n        limit: 1,\n      };\n\n      let queryOptions = {\n        limit: \"2\",\n      };\n\n      query.op = \"countDocuments\";\n\n      return getBuildQuery(options)(query, queryOptions).then((result) => {\n        delete query.op;\n\n        for (let key in query) {\n          sinon.assert.notCalled(query[key]);\n        }\n\n        assert.equal(result, query);\n      });\n    });\n\n    it(\"does not call limit on queries that have a distinct option set and returns the query object\", () => {\n      let options = {\n        limit: 1,\n      };\n\n      let queryOptions = {\n        distinct: \"name\",\n      };\n\n      return getBuildQuery(options)(query, queryOptions).then((result) => {\n        for (let key in query) {\n          if (key === \"distinct\") continue;\n          sinon.assert.notCalled(query[key]);\n        }\n        sinon.assert.called(query.distinct);\n\n        assert.equal(result, query);\n      });\n    });\n  });\n\n  describe(\"sort\", () => {\n    it(\"calls sort and returns a query object\", () => {\n      let queryOptions = {\n        sort: \"foo\",\n      };\n\n      return getBuildQuery({})(query, queryOptions).then((result) => {\n        sinon.assert.calledOnce(query.sort);\n        sinon.assert.calledWithExactly(query.sort, queryOptions.sort);\n        sinon.assert.notCalled(query.where);\n        sinon.assert.notCalled(query.skip);\n        sinon.assert.notCalled(query.limit);\n        sinon.assert.notCalled(query.select);\n        sinon.assert.notCalled(query.populate);\n        sinon.assert.notCalled(query.distinct);\n        assert.equal(result, query);\n      });\n    });\n  });\n\n  describe(\"select\", () => {\n    it(\"accepts an object\", () => {\n      let queryOptions = {\n        select: {\n          foo: 1,\n          bar: 0,\n        },\n      };\n\n      return getBuildQuery({})(query, queryOptions).then((result) => {\n        sinon.assert.calledOnce(query.select);\n        sinon.assert.calledWithExactly(query.select, {\n          foo: 1,\n          bar: 0,\n        });\n        sinon.assert.notCalled(query.where);\n        sinon.assert.notCalled(query.skip);\n        sinon.assert.notCalled(query.limit);\n        sinon.assert.notCalled(query.sort);\n        sinon.assert.notCalled(query.populate);\n        sinon.assert.notCalled(query.distinct);\n        assert.equal(result, query);\n      });\n    });\n  });\n\n  describe(\"populate\", () => {\n    it(\"accepts an object wrapped in an array to populate a path\", () => {\n      let queryOptions = {\n        populate: [\n          {\n            path: \"foo.bar\",\n            select: \"baz\",\n            match: { qux: \"quux\" },\n            options: { sort: \"baz\" },\n          },\n        ],\n      };\n\n      return getBuildQuery({})(query, queryOptions).then((result) => {\n        sinon.assert.calledOnce(query.populate);\n        sinon.assert.calledWithExactly(query.populate, [\n          {\n            path: \"foo.bar\",\n            select: \"baz\",\n            match: { qux: \"quux\" },\n            options: { sort: \"baz\" },\n          },\n        ]);\n        sinon.assert.notCalled(query.where);\n        sinon.assert.notCalled(query.skip);\n        sinon.assert.notCalled(query.limit);\n        sinon.assert.notCalled(query.select);\n        sinon.assert.notCalled(query.sort);\n        sinon.assert.notCalled(query.distinct);\n        assert.equal(result, query);\n      });\n    });\n  });\n\n  describe(\"distinct\", () => {\n    it(\"calls distinct and returns a query object\", () => {\n      let queryOptions = {\n        distinct: \"foo\",\n      };\n\n      return getBuildQuery({})(query, queryOptions).then((result) => {\n        sinon.assert.calledOnce(query.distinct);\n        sinon.assert.calledWithExactly(query.distinct, \"foo\");\n        sinon.assert.notCalled(query.where);\n        sinon.assert.notCalled(query.skip);\n        sinon.assert.notCalled(query.limit);\n        sinon.assert.notCalled(query.sort);\n        sinon.assert.notCalled(query.populate);\n        sinon.assert.notCalled(query.select);\n        assert.equal(result, query);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/unit/detective.mjs",
    "content": "import assert from \"assert\";\nimport mongoose, { Schema } from \"mongoose\";\nimport { detective } from \"../../dist/detective.js\";\n\ndescribe(\"detective\", () => {\n  const InvoiceSchema = new Schema({\n    customer: { type: Schema.Types.ObjectId, ref: \"Customer\" },\n    very: {\n      deep: {\n        ref: { type: Schema.Types.ObjectId, ref: \"Reference\" },\n      },\n    },\n    products: [{ type: Schema.Types.ObjectId, ref: \"Product\" }],\n  });\n\n  mongoose.model(\"Invoice\", InvoiceSchema);\n\n  it(\"returns undefined when path does not exist\", () => {\n    const modelName = detective(mongoose.models.Invoice, \"foo.bar\");\n\n    assert.equal(modelName, undefined);\n  });\n\n  it(\"returns undefined when path is not a ref\", () => {\n    const modelName = detective(mongoose.models.Invoice, \"_id\");\n\n    assert.equal(modelName, undefined);\n  });\n\n  it(\"returns the referenced model name\", () => {\n    const modelName = detective(mongoose.models.Invoice, \"customer\");\n\n    assert.equal(modelName, \"Customer\");\n  });\n\n  it(\"returns the referenced model name when ref is an array\", () => {\n    const modelName = detective(mongoose.models.Invoice, \"products\");\n\n    assert.equal(modelName, \"Product\");\n  });\n\n  it(\"returns the referenced model name at a deep path\", () => {\n    const modelName = detective(mongoose.models.Invoice, \"very.deep.ref\");\n\n    assert.equal(modelName, \"Reference\");\n  });\n});\n"
  },
  {
    "path": "test/unit/errorHandler.mjs",
    "content": "import assert from \"assert\";\nimport mongoose from \"mongoose\";\nimport sinon from \"sinon\";\nimport { getErrorHandler } from \"../../dist/errorHandler.js\";\n\ndescribe(\"errorHandler\", () => {\n  it(\"is a function\", () => {\n    assert.equal(typeof getErrorHandler, \"function\");\n  });\n\n  it(\"returns a function\", () => {\n    assert.equal(typeof getErrorHandler(), \"function\");\n  });\n\n  it(\"sets statusCode 400 and calls onError\", () => {\n    const options = {\n      onError: sinon.spy(),\n    };\n\n    const req = {\n      erm: {},\n      params: {},\n    };\n\n    const err = new Error(\"Something went wrong\");\n\n    getErrorHandler(options)(err, req);\n\n    sinon.assert.calledOnce(options.onError);\n    assert.equal(req.erm.statusCode, 400);\n  });\n\n  it(\"sets statusCode 400 and calls onError\", () => {\n    const options = {\n      onError: sinon.spy(),\n      idProperty: \"42\",\n    };\n\n    const req = {\n      erm: {},\n      params: {\n        id: \"42\",\n      },\n    };\n\n    const err = new Error(\"Something went wrong\");\n\n    getErrorHandler(options)(err, req);\n\n    sinon.assert.calledOnce(options.onError);\n    assert.equal(req.erm.statusCode, 400);\n  });\n\n  it(\"sets statusCode 404 and calls onError\", () => {\n    const options = {\n      onError: sinon.spy(),\n      idProperty: \"_id\",\n    };\n\n    const req = {\n      erm: {},\n      params: {\n        id: \"42\",\n      },\n    };\n\n    const err = new mongoose.CastError(\"type\", \"42\", \"_id\");\n\n    getErrorHandler(options)(err, req);\n\n    sinon.assert.calledOnce(options.onError);\n    assert.equal(req.erm.statusCode, 404);\n  });\n});\n"
  },
  {
    "path": "test/unit/middleware/access.mjs",
    "content": "import assert from \"assert\";\nimport sinon from \"sinon\";\nimport { getAccessHandler } from \"../../../dist/middleware/access.js\";\n\ndescribe(\"access\", () => {\n  let next = sinon.spy();\n  let onError = sinon.spy();\n\n  afterEach(() => {\n    next.resetHistory();\n    onError.resetHistory();\n  });\n\n  describe(\"returns (sync)\", () => {\n    it(\"adds access field to req\", () => {\n      let req = {\n        erm: {},\n      };\n\n      getAccessHandler({\n        access: () => {\n          return \"private\";\n        },\n      })(req, {}, next);\n\n      sinon.assert.calledOnce(next);\n      sinon.assert.calledWithExactly(next);\n      assert.equal(req.access, \"private\");\n    });\n\n    it(\"throws an exception with unsupported parameter\", () => {\n      let req = {\n        erm: {},\n      };\n\n      assert.throws(() => {\n        getAccessHandler({\n          access: () => {\n            return \"foo\";\n          },\n        })(req, {}, next);\n      }, Error('Unsupported access, must be \"public\", \"private\" or \"protected\"'));\n\n      sinon.assert.notCalled(next);\n      assert.equal(req.access, undefined);\n    });\n  });\n\n  describe(\"yields (async)\", (done) => {\n    it(\"adds access field to req\", () => {\n      let req = {\n        erm: {},\n      };\n\n      getAccessHandler({\n        access: () => {\n          return Promise.resolve(\"private\");\n        },\n      })(req, {}, () => {\n        assert.equal(req.access, \"private\");\n        done();\n      });\n    });\n\n    it(\"calls onError\", (done) => {\n      let req = {\n        erm: {},\n      };\n      let err = new Error(\"Something bad happened\");\n\n      getAccessHandler({\n        access: () => {\n          return Promise.reject(err);\n        },\n        onError,\n      })(req, {}, next);\n\n      setTimeout(() => {\n        sinon.assert.calledOnce(onError);\n        sinon.assert.calledWithExactly(onError, err, req, {}, next);\n        sinon.assert.notCalled(next);\n        assert.equal(req.access, undefined);\n        done();\n      });\n    });\n\n    it(\"throws an exception with unsupported parameter\", (done) => {\n      let req = {\n        erm: {},\n      };\n\n      getAccessHandler({\n        access: () => {\n          return Promise.resolve(\"foo\");\n        },\n        onError,\n      })(req, {}, next);\n\n      setTimeout(() => {\n        sinon.assert.calledOnce(onError);\n        sinon.assert.notCalled(next);\n        assert.equal(req.access, undefined);\n        done();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/unit/middleware/ensureContentType.mjs",
    "content": "import assert from \"assert\";\nimport sinon from \"sinon\";\nimport { getEnsureContentTypeHandler } from \"../../../dist/middleware/ensureContentType.js\";\n\ndescribe(\"ensureContentType\", () => {\n  let onError = sinon.spy();\n  let next = sinon.spy();\n\n  afterEach(() => {\n    onError.resetHistory();\n    next.resetHistory();\n  });\n\n  it(\"calls next with an error (missing_content_type)\", () => {\n    let req = {\n      erm: {},\n      headers: {},\n      params: {},\n    };\n\n    getEnsureContentTypeHandler({ onError })(req, {}, next);\n\n    sinon.assert.calledOnce(onError);\n    sinon.assert.calledWithExactly(\n      onError,\n      sinon.match.instanceOf(Error) /*new Error('missing_content_type')*/,\n      req,\n      {},\n      next\n    );\n    sinon.assert.notCalled(next);\n    assert.equal(req.access, undefined);\n  });\n\n  it(\"calls next with an error (invalid_content_type)\", () => {\n    let req = {\n      erm: {},\n      headers: {\n        \"content-type\": \"invalid/type\",\n      },\n      params: {},\n    };\n\n    getEnsureContentTypeHandler({ onError })(req, {}, next);\n\n    sinon.assert.calledOnce(onError);\n    sinon.assert.calledWithExactly(\n      onError,\n      sinon.match.instanceOf(Error) /*new Error('invalid_content_type')*/,\n      req,\n      {},\n      next\n    );\n    sinon.assert.notCalled(next);\n    assert.equal(req.access, undefined);\n  });\n\n  it(\"calls next\", () => {\n    let req = {\n      headers: {\n        \"content-type\": \"application/json\",\n      },\n      params: {},\n    };\n\n    getEnsureContentTypeHandler({ onError })(req, {}, next);\n\n    sinon.assert.calledOnce(next);\n    sinon.assert.calledWithExactly(next);\n  });\n});\n"
  },
  {
    "path": "test/unit/middleware/onError.mjs",
    "content": "import sinon from \"sinon\";\nimport { getOnErrorHandler } from \"../../../dist/middleware/onError.js\";\n\ndescribe(\"onError\", () => {\n  const req = {\n    erm: {\n      statusCode: 500,\n    },\n  };\n\n  let res = {\n    setHeader: () => undefined,\n    status: function () {\n      return this;\n    },\n    send: () => undefined,\n  };\n\n  let setHeader = sinon.spy(res, \"setHeader\");\n  let status = sinon.spy(res, \"status\");\n  let send = sinon.spy(res, \"send\");\n  let next = sinon.spy();\n\n  afterEach(() => {\n    setHeader.resetHistory();\n    status.resetHistory();\n    send.resetHistory();\n    next.resetHistory();\n  });\n\n  it(\"with express\", () => {\n    getOnErrorHandler(true)(new Error(\"An error occurred\"), req, res, next);\n\n    sinon.assert.calledOnce(setHeader);\n    sinon.assert.calledWithExactly(\n      setHeader,\n      \"Content-Type\",\n      \"application/json\"\n    );\n    sinon.assert.calledOnce(status);\n    sinon.assert.calledWithExactly(status, 500);\n    sinon.assert.calledOnce(send);\n    sinon.assert.calledWithExactly(send, {\n      message: \"An error occurred\",\n      name: \"Error\",\n    });\n    sinon.assert.notCalled(next);\n  });\n\n  it(\"with restify\", () => {\n    getOnErrorHandler(false)(new Error(\"An error occurred\"), req, res, next);\n\n    sinon.assert.calledOnce(setHeader);\n    sinon.assert.calledWithExactly(\n      setHeader,\n      \"Content-Type\",\n      \"application/json\"\n    );\n    sinon.assert.notCalled(status);\n    sinon.assert.calledOnce(send);\n    sinon.assert.calledWithExactly(send, 500, {\n      message: \"An error occurred\",\n      name: \"Error\",\n    });\n    sinon.assert.notCalled(next);\n  });\n});\n"
  },
  {
    "path": "test/unit/middleware/outputFn.mjs",
    "content": "import sinon from \"sinon\";\nimport { getOutputFnHandler } from \"../../../dist/middleware/outputFn.js\";\n\ndescribe(\"outputFn\", () => {\n  let res = {\n    sendStatus: () => undefined,\n    status: function () {\n      return this;\n    },\n    json: () => undefined,\n    send: () => undefined,\n  };\n\n  let sendStatus = sinon.spy(res, \"sendStatus\");\n  let status = sinon.spy(res, \"status\");\n  let json = sinon.spy(res, \"json\");\n  let send = sinon.spy(res, \"send\");\n\n  afterEach(() => {\n    sendStatus.resetHistory();\n    status.resetHistory();\n    json.resetHistory();\n    send.resetHistory();\n  });\n\n  describe(\"express\", () => {\n    it(\"sends status code and message\", () => {\n      getOutputFnHandler(true)(\n        {\n          erm: {\n            statusCode: 200,\n          },\n        },\n        res\n      );\n\n      sinon.assert.calledOnce(sendStatus);\n      sinon.assert.calledWithExactly(sendStatus, 200);\n      sinon.assert.notCalled(status);\n      sinon.assert.notCalled(json);\n      sinon.assert.notCalled(send);\n    });\n\n    it(\"sends data and status code\", () => {\n      let req = {\n        erm: {\n          statusCode: 201,\n          result: {\n            name: \"Bob\",\n          },\n        },\n      };\n\n      getOutputFnHandler(true)(req, res);\n\n      sinon.assert.calledOnce(status);\n      sinon.assert.calledWithExactly(status, 201);\n      sinon.assert.calledOnce(json);\n      sinon.assert.calledWithExactly(json, {\n        name: \"Bob\",\n      });\n      sinon.assert.notCalled(sendStatus);\n      sinon.assert.notCalled(send);\n    });\n  });\n\n  describe(\"restify\", () => {\n    it(\"sends status code\", () => {\n      getOutputFnHandler(false)(\n        {\n          erm: {\n            statusCode: 200,\n          },\n        },\n        res\n      );\n\n      sinon.assert.calledOnce(send);\n      sinon.assert.calledWithExactly(send, 200, undefined);\n      sinon.assert.notCalled(sendStatus);\n      sinon.assert.notCalled(status);\n      sinon.assert.notCalled(json);\n    });\n\n    it(\"sends data and status code\", () => {\n      let req = {\n        erm: {\n          statusCode: 201,\n          result: {\n            name: \"Bob\",\n          },\n        },\n      };\n\n      getOutputFnHandler(false)(req, res);\n\n      sinon.assert.calledOnce(send);\n      sinon.assert.calledWithExactly(send, 201, {\n        name: \"Bob\",\n      });\n      sinon.assert.notCalled(sendStatus);\n      sinon.assert.notCalled(status);\n      sinon.assert.notCalled(json);\n    });\n  });\n});\n"
  },
  {
    "path": "test/unit/middleware/prepareOutput.mjs",
    "content": "import sinon from \"sinon\";\nimport { getPrepareOutputHandler } from \"../../../dist/middleware/prepareOutput.js\";\n\ndescribe(\"prepareOutput\", () => {\n  let onError = sinon.spy();\n  let outputFn = sinon.spy();\n  let outputFnPromise = sinon.spy(() => {\n    return Promise.resolve();\n  });\n  let postProcess = sinon.spy();\n  let next = sinon.spy();\n\n  afterEach(() => {\n    onError.resetHistory();\n    outputFn.resetHistory();\n    outputFnPromise.resetHistory();\n    next.resetHistory();\n  });\n\n  it(\"calls outputFn with default options and no post* middleware\", () => {\n    let req = {\n      method: \"GET\",\n      erm: {},\n    };\n\n    let options = {\n      onError: onError,\n      outputFn: outputFn,\n    };\n\n    getPrepareOutputHandler(options)(req, {}, next);\n\n    sinon.assert.calledOnce(outputFn);\n    sinon.assert.calledWithExactly(outputFn, req, {});\n    sinon.assert.notCalled(onError);\n    sinon.assert.notCalled(next);\n  });\n\n  it(\"calls outputFn with default options and no post* middleware (async)\", () => {\n    let req = {\n      method: \"GET\",\n      erm: {},\n    };\n\n    let options = {\n      onError: onError,\n      outputFn: outputFnPromise,\n    };\n\n    getPrepareOutputHandler(options)(req, {}, next);\n\n    sinon.assert.calledOnce(outputFnPromise);\n    sinon.assert.calledWithExactly(outputFnPromise, req, {});\n    sinon.assert.notCalled(onError);\n    sinon.assert.notCalled(next);\n  });\n\n  it(\"calls postProcess with default options and no post* middleware\", () => {\n    let req = {\n      method: \"GET\",\n      erm: {},\n    };\n\n    let options = {\n      onError: onError,\n      outputFn: outputFn,\n      postProcess: postProcess,\n    };\n\n    getPrepareOutputHandler(options)(req, {}, next);\n\n    sinon.assert.calledOnce(outputFn);\n    sinon.assert.calledWithExactly(outputFn, req, {});\n    sinon.assert.calledOnce(postProcess);\n    sinon.assert.calledWithExactly(postProcess, req, {});\n    sinon.assert.notCalled(onError);\n    sinon.assert.notCalled(next);\n  });\n\n  it(\"calls postProcess with default options and no post* middleware (async outputFn)\", () => {\n    let req = {\n      method: \"GET\",\n      erm: {},\n    };\n\n    let options = {\n      onError: onError,\n      outputFn: outputFnPromise,\n      postProcess: postProcess,\n    };\n\n    getPrepareOutputHandler(options)(req, {}, next);\n\n    sinon.assert.calledOnce(outputFnPromise);\n    sinon.assert.calledWithExactly(outputFnPromise, req, {});\n    sinon.assert.calledOnce(postProcess);\n    sinon.assert.calledWithExactly(postProcess, req, {});\n    sinon.assert.notCalled(onError);\n    sinon.assert.notCalled(next);\n  });\n});\n"
  },
  {
    "path": "test/unit/middleware/prepareQuery.mjs",
    "content": "import assert from \"assert\";\nimport sinon from \"sinon\";\nimport { getPrepareQueryHandler } from \"../../../dist/middleware/prepareQuery.js\";\n\ndescribe(\"prepareQuery\", () => {\n  let options = {\n    onError: sinon.spy(),\n    allowRegex: true,\n  };\n\n  let next = sinon.spy();\n\n  afterEach(() => {\n    options.onError.resetHistory();\n    next.resetHistory();\n  });\n\n  describe(\"jsonQueryParser\", () => {\n    it(\"converts $regex to undefined\", () => {\n      let req = {\n        query: {\n          query: '{\"foo\":{\"$regex\":\"bar\"}}',\n        },\n      };\n\n      getPrepareQueryHandler({ ...options, allowRegex: false })(req, {}, next);\n\n      sinon.assert.calledOnce(options.onError);\n      sinon.assert.calledWithExactly(\n        options.onError,\n        sinon.match.instanceOf(Error) /*new Error('invalid_json_query')*/,\n        req,\n        {},\n        next\n      );\n      sinon.assert.notCalled(next);\n    });\n\n    it(\"converts [] to $in\", () => {\n      let req = {\n        query: {\n          query: '{\"foo\":[\"bar\"]}',\n        },\n      };\n\n      getPrepareQueryHandler(options)(req, {}, next);\n\n      assert.deepEqual(req.erm.query, {\n        query: {\n          foo: { $in: [\"bar\"] },\n        },\n      });\n      sinon.assert.calledOnce(next);\n      sinon.assert.calledWithExactly(next);\n      sinon.assert.notCalled(options.onError);\n    });\n  });\n\n  it(\"calls next when query is empty\", () => {\n    getPrepareQueryHandler(options)({}, {}, next);\n\n    sinon.assert.calledOnce(next);\n    sinon.assert.calledWithExactly(next);\n    sinon.assert.notCalled(options.onError);\n  });\n\n  it(\"ignores keys that are not whitelisted and calls next\", () => {\n    let req = {\n      query: {\n        foo: \"bar\",\n      },\n    };\n\n    getPrepareQueryHandler(options)(req, {}, next);\n\n    sinon.assert.calledOnce(next);\n    sinon.assert.calledWithExactly(next);\n    sinon.assert.notCalled(options.onError);\n  });\n\n  it(\"calls next when query key is valid json\", () => {\n    let req = {\n      query: {\n        query: '{\"foo\":\"bar\"}',\n      },\n    };\n\n    getPrepareQueryHandler(options)(req, {}, next);\n\n    assert.deepEqual(req.erm.query, {\n      query: JSON.parse(req.query.query),\n    });\n    sinon.assert.calledOnce(next);\n    sinon.assert.calledWithExactly(next);\n    sinon.assert.notCalled(options.onError);\n  });\n\n  it(\"calls onError when query key is invalid json\", () => {\n    let req = {\n      erm: {},\n      params: {},\n      query: {\n        query: \"not json\",\n      },\n    };\n\n    getPrepareQueryHandler(options)(req, {}, next);\n\n    sinon.assert.calledOnce(options.onError);\n    sinon.assert.calledWithExactly(\n      options.onError,\n      sinon.match.instanceOf(Error) /*new Error('invalid_json_query')*/,\n      req,\n      {},\n      next\n    );\n    sinon.assert.notCalled(next);\n  });\n\n  it(\"calls next when sort key is valid json\", () => {\n    let req = {\n      query: {\n        sort: '{\"foo\":\"asc\"}',\n      },\n    };\n\n    getPrepareQueryHandler(options)(req, {}, next);\n\n    assert.deepEqual(req.erm.query, {\n      sort: JSON.parse(req.query.sort),\n    });\n    sinon.assert.calledOnce(next);\n    sinon.assert.calledWithExactly(next);\n    sinon.assert.notCalled(options.onError);\n  });\n\n  it(\"calls next when sort key is a string\", () => {\n    let req = {\n      query: {\n        sort: \"foo\",\n      },\n    };\n\n    getPrepareQueryHandler(options)(req, {}, next);\n\n    assert.deepEqual(req.erm.query, req.query);\n    sinon.assert.calledOnce(next);\n    sinon.assert.calledWithExactly(next);\n    sinon.assert.notCalled(options.onError);\n  });\n\n  it(\"calls next when skip key is a string\", () => {\n    let req = {\n      query: {\n        skip: \"1\",\n      },\n    };\n\n    getPrepareQueryHandler(options)(req, {}, next);\n\n    assert.deepEqual(req.erm.query, req.query);\n    sinon.assert.calledOnce(next);\n    sinon.assert.calledWithExactly(next);\n    sinon.assert.notCalled(options.onError);\n  });\n\n  it(\"calls next when limit key is a string\", () => {\n    let req = {\n      query: {\n        limit: \"1\",\n      },\n    };\n\n    getPrepareQueryHandler(options)(req, {}, next);\n\n    assert.deepEqual(req.erm.query, req.query);\n    sinon.assert.calledOnce(next);\n    sinon.assert.calledWithExactly(next);\n    sinon.assert.notCalled(options.onError);\n  });\n\n  it(\"calls next when distinct key is a string\", () => {\n    let req = {\n      query: {\n        distinct: \"foo\",\n      },\n    };\n\n    getPrepareQueryHandler(options)(req, {}, next);\n\n    assert.deepEqual(req.erm.query, req.query);\n    sinon.assert.calledOnce(next);\n    sinon.assert.calledWithExactly(next);\n    sinon.assert.notCalled(options.onError);\n  });\n\n  it(\"calls next when populate key is a string\", () => {\n    let req = {\n      query: {\n        populate: \"foo\",\n      },\n    };\n\n    getPrepareQueryHandler(options)(req, {}, next);\n\n    assert.deepEqual(req.erm.query, {\n      populate: [\n        {\n          path: \"foo\",\n          strictPopulate: false,\n        },\n      ],\n    });\n    sinon.assert.calledOnce(next);\n    sinon.assert.calledWithExactly(next);\n    sinon.assert.notCalled(options.onError);\n  });\n\n  describe(\"select\", () => {\n    it(\"parses a string to include fields\", () => {\n      let req = {\n        query: {\n          select: \"foo\",\n        },\n      };\n\n      getPrepareQueryHandler(options)(req, {}, next);\n\n      assert.deepEqual(req.erm.query, {\n        select: {\n          foo: 1,\n        },\n      });\n      sinon.assert.calledOnce(next);\n      sinon.assert.calledWithExactly(next);\n      sinon.assert.notCalled(options.onError);\n    });\n\n    it(\"parses a string to exclude fields\", () => {\n      let req = {\n        query: {\n          select: \"-foo\",\n        },\n      };\n\n      getPrepareQueryHandler(options)(req, {}, next);\n\n      assert.deepEqual(req.erm.query, {\n        select: {\n          foo: 0,\n        },\n      });\n      sinon.assert.calledOnce(next);\n      sinon.assert.calledWithExactly(next);\n      sinon.assert.notCalled(options.onError);\n    });\n\n    it(\"parses a comma separated list of fields to include\", () => {\n      let req = {\n        query: {\n          select: \"foo,bar\",\n        },\n      };\n\n      getPrepareQueryHandler(options)(req, {}, next);\n\n      assert.deepEqual(req.erm.query, {\n        select: {\n          foo: 1,\n          bar: 1,\n        },\n      });\n      sinon.assert.calledOnce(next);\n      sinon.assert.calledWithExactly(next);\n      sinon.assert.notCalled(options.onError);\n    });\n\n    it(\"parses a comma separated list of fields to exclude\", () => {\n      let req = {\n        query: {\n          select: \"-foo,-bar\",\n        },\n      };\n\n      getPrepareQueryHandler(options)(req, {}, next);\n\n      assert.deepEqual(req.erm.query, {\n        select: {\n          foo: 0,\n          bar: 0,\n        },\n      });\n      sinon.assert.calledOnce(next);\n      sinon.assert.calledWithExactly(next);\n      sinon.assert.notCalled(options.onError);\n    });\n\n    it(\"parses a comma separated list of nested fields\", () => {\n      let req = {\n        query: {\n          select: \"foo.bar,baz.qux.quux\",\n        },\n      };\n\n      getPrepareQueryHandler(options)(req, {}, next);\n\n      assert.deepEqual(req.erm.query, {\n        select: {\n          \"foo.bar\": 1,\n          \"baz.qux.quux\": 1,\n        },\n      });\n      sinon.assert.calledOnce(next);\n      sinon.assert.calledWithExactly(next);\n      sinon.assert.notCalled(options.onError);\n    });\n  });\n\n  describe(\"populate\", () => {\n    it(\"parses a string to populate a path\", () => {\n      let req = {\n        query: {\n          populate: \"foo\",\n        },\n      };\n\n      getPrepareQueryHandler(options)(req, {}, next);\n\n      assert.deepEqual(req.erm.query, {\n        populate: [\n          {\n            path: \"foo\",\n            strictPopulate: false,\n          },\n        ],\n      });\n      sinon.assert.calledOnce(next);\n      sinon.assert.calledWithExactly(next);\n      sinon.assert.notCalled(options.onError);\n    });\n\n    it(\"parses a string to populate multiple paths\", () => {\n      let req = {\n        query: {\n          populate: \"foo,bar\",\n        },\n      };\n\n      getPrepareQueryHandler(options)(req, {}, next);\n\n      assert.deepEqual(req.erm.query, {\n        populate: [\n          {\n            path: \"foo\",\n            strictPopulate: false,\n          },\n          {\n            path: \"bar\",\n            strictPopulate: false,\n          },\n        ],\n      });\n      sinon.assert.calledOnce(next);\n      sinon.assert.calledWithExactly(next);\n      sinon.assert.notCalled(options.onError);\n    });\n\n    it(\"accepts an object to populate a path\", () => {\n      let req = {\n        query: {\n          populate: {\n            path: \"foo.bar\",\n            select: \"baz\",\n            match: { qux: \"quux\" },\n            options: { sort: \"baz\" },\n          },\n        },\n      };\n\n      getPrepareQueryHandler(options)(req, {}, next);\n\n      assert.deepEqual(req.erm.query, {\n        populate: [\n          {\n            path: \"foo.bar\",\n            select: \"baz\",\n            match: { qux: \"quux\" },\n            options: { sort: \"baz\" },\n            strictPopulate: false,\n          },\n        ],\n      });\n      sinon.assert.calledOnce(next);\n      sinon.assert.calledWithExactly(next);\n      sinon.assert.notCalled(options.onError);\n    });\n\n    it(\"parses a string to populate and select fields\", () => {\n      let req = {\n        query: {\n          populate: \"foo\",\n          select: \"foo.bar,foo.baz\",\n        },\n      };\n\n      getPrepareQueryHandler(options)(req, {}, next);\n\n      assert.deepEqual(req.erm.query, {\n        populate: [\n          {\n            path: \"foo\",\n            select: \"bar baz\",\n            strictPopulate: false,\n          },\n        ],\n      });\n      sinon.assert.calledOnce(next);\n      sinon.assert.calledWithExactly(next);\n      sinon.assert.notCalled(options.onError);\n    });\n  });\n});\n"
  },
  {
    "path": "test/unit/moredots.mjs",
    "content": "import assert from \"assert\";\nimport { moredots } from \"../../dist/moredots.js\";\n\ndescribe(\"moredots\", () => {\n  it(\"recursively converts objects to dot notation\", () => {\n    const result = moredots({\n      foo: {\n        bar: {\n          baz: 42,\n        },\n      },\n    });\n\n    assert.strictEqual(result[\"foo.bar.baz\"], 42);\n  });\n});\n"
  },
  {
    "path": "test/unit/resourceFilter.mjs",
    "content": "import assert from \"assert\";\nimport { Filter } from \"../../dist/resource_filter.js\";\n\nimport setupDb from \"../integration/setup.mjs\";\n\ndescribe(\"resourceFilter\", () => {\n  describe(\"filterObject\", () => {\n    const db = setupDb();\n\n    db.initialize({\n      connect: false,\n    });\n\n    const filter = new Filter();\n\n    filter.add(db.models.Invoice, {\n      filteredKeys: {\n        private: [],\n        protected: [],\n      },\n    });\n\n    filter.add(db.models.Customer, {\n      filteredKeys: {\n        private: [\"name\"],\n        protected: [],\n      },\n    });\n\n    filter.add(db.models.Product, {\n      filteredKeys: {\n        private: [\"name\"],\n        protected: [],\n      },\n    });\n\n    it(\"removes keys in populated document\", () => {\n      let invoice = {\n        customer: {\n          name: \"John\",\n        },\n        amount: \"42\",\n      };\n\n      filter.filterObject(invoice, {\n        access: \"public\",\n        modelName: db.models.Invoice.modelName,\n        populate: [\n          {\n            path: \"customer\",\n          },\n        ],\n      });\n\n      assert.deepEqual(invoice, {\n        customer: {},\n        amount: \"42\",\n      });\n    });\n\n    it(\"removes keys in array with populated document\", () => {\n      let invoices = [\n        {\n          customer: {\n            name: \"John\",\n          },\n          amount: \"42\",\n        },\n        {\n          customer: {\n            name: \"Bob\",\n          },\n          amount: \"3.14\",\n        },\n      ];\n\n      filter.filterObject(invoices, {\n        access: \"public\",\n        modelName: db.models.Invoice.modelName,\n        populate: [\n          {\n            path: \"customer\",\n          },\n        ],\n      });\n\n      assert.deepEqual(invoices, [\n        {\n          customer: {},\n          amount: \"42\",\n        },\n        {\n          customer: {},\n          amount: \"3.14\",\n        },\n      ]);\n    });\n\n    it(\"ignores undefined path\", () => {\n      let invoice = {\n        amount: \"42\",\n      };\n\n      filter.filterObject(invoice, {\n        access: \"public\",\n        modelName: db.models.Invoice.modelName,\n        populate: [\n          {\n            path: \"customer\",\n          },\n        ],\n      });\n\n      assert.deepEqual(invoice, {\n        amount: \"42\",\n      });\n    });\n\n    it(\"skip when populate path is undefined\", () => {\n      let invoice = {\n        customer: {\n          name: \"John\",\n        },\n        amount: \"42\",\n      };\n\n      filter.filterObject(invoice, {\n        populate: [{}],\n      });\n\n      assert.deepEqual(invoice, {\n        customer: {\n          name: \"John\",\n        },\n        amount: \"42\",\n      });\n    });\n\n    it(\"removes keys in populated document array\", () => {\n      let invoice = {\n        products: [\n          {\n            name: \"Squirt Gun\",\n          },\n          {\n            name: \"Water Balloons\",\n          },\n        ],\n        amount: \"42\",\n      };\n\n      filter.filterObject(invoice, {\n        access: \"public\",\n        modelName: db.models.Invoice.modelName,\n        populate: [\n          {\n            path: \"products\",\n          },\n        ],\n      });\n\n      assert.deepEqual(invoice, {\n        products: [{}, {}],\n        amount: \"42\",\n      });\n    });\n\n    it(\"removes keys in populated document in array\", () => {\n      let customer = {\n        name: \"John\",\n        purchases: [\n          {\n            item: {\n              name: \"Squirt Gun\",\n            },\n          },\n        ],\n      };\n\n      filter.filterObject(customer, {\n        access: \"public\",\n        modelName: db.models.Customer.modelName,\n        populate: [\n          {\n            path: \"purchases.item\",\n          },\n        ],\n      });\n\n      assert.deepEqual(customer, {\n        purchases: [\n          {\n            item: {},\n          },\n        ],\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/unit/weedout.mjs",
    "content": "import assert from \"assert\";\nimport { weedout } from \"../../dist/weedout.js\";\n\ndescribe(\"weedout\", () => {\n  it(\"removes root keys\", () => {\n    const src = {\n      foo: \"bar\",\n    };\n\n    weedout(src, \"foo\");\n\n    assert.equal(src.foo, undefined);\n  });\n\n  it(\"ignores undefined root keys\", () => {\n    const src = {\n      foo: \"bar\",\n    };\n\n    weedout(src, \"bar\");\n\n    assert.deepEqual(src, {\n      foo: \"bar\",\n    });\n  });\n\n  it(\"removes nested keys\", () => {\n    const src = {\n      foo: {\n        bar: {\n          baz: \"42\",\n        },\n      },\n    };\n\n    weedout(src, \"foo.bar.baz\");\n\n    assert.deepEqual(src.foo.bar, {});\n  });\n\n  it(\"ignores undefined nested keys\", () => {\n    const src = {\n      foo: {\n        bar: {\n          baz: \"42\",\n        },\n      },\n    };\n\n    weedout(src, \"baz.bar.foo\");\n\n    assert.deepEqual(src, {\n      foo: {\n        bar: {\n          baz: \"42\",\n        },\n      },\n    });\n  });\n\n  it(\"removes keys inside object arrays\", () => {\n    const src = {\n      foo: [\n        {\n          bar: {\n            baz: \"3.14\",\n          },\n        },\n        {\n          bar: {\n            baz: \"pi\",\n          },\n        },\n      ],\n    };\n\n    weedout(src, \"foo.bar.baz\");\n\n    src.foo.forEach((foo) => {\n      assert.deepEqual(foo.bar, {});\n    });\n  });\n\n  it(\"removes keys inside object arrays inside object arrays\", () => {\n    const src = {\n      foo: [\n        {\n          bar: [\n            {\n              baz: \"to\",\n            },\n            {\n              baz: \"be\",\n            },\n          ],\n        },\n        {\n          bar: [\n            {\n              baz: \"or\",\n            },\n            {\n              baz: \"not\",\n            },\n          ],\n        },\n      ],\n    };\n\n    weedout(src, \"foo.bar.baz\");\n\n    src.foo.forEach((foo) => {\n      foo.bar.forEach((bar) => {\n        assert.deepEqual(bar, {});\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/unit.mjs",
    "content": "import \"./unit/buildQuery.mjs\";\nimport \"./unit/detective.mjs\";\nimport \"./unit/errorHandler.mjs\";\nimport \"./unit/middleware/access.mjs\";\nimport \"./unit/middleware/ensureContentType.mjs\";\nimport \"./unit/middleware/onError.mjs\";\nimport \"./unit/middleware/outputFn.mjs\";\nimport \"./unit/middleware/prepareOutput.mjs\";\nimport \"./unit/middleware/prepareQuery.mjs\";\nimport \"./unit/moredots.mjs\";\nimport \"./unit/resourceFilter.mjs\";\nimport \"./unit/weedout.mjs\";\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"esModuleInterop\": true,\n    \"exactOptionalPropertyTypes\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"moduleResolution\": \"Node\",\n    \"skipLibCheck\": true,\n    \"strict\": true\n  }\n}\n"
  }
]