# coverage.js

    const mongoose = require('mongoose');

    /**
     * @class Coverage
     * @property {string} run_at - the iso date when the coverage was send to the server
     * @property {Object[]} source_files - a list of all the source files related to the coverage report
     * @property {string} source_files[].name - the name of the file parsed
     * @property {string} source_files[].source - the content of the file parsed into a string
     * @property {Object[]} source_files[].coverage - an array of objects describing the state of the parsed file
     * @property {object} source_files[].coverage[].lines - an object descibing the lines covered in the file
     * @property {number} source_files[].coverage[].lines.found - total number of lines found in the file
     * @property {number} source_files[].coverage[].lines.hits - total number of lines covered in the file
     * @property {Object[]} source_files[].coverage[].lines.details - an array of points in the file that descibes the line and the amount of times it was covered
     * @property {number} source_files[].coverage[].lines.details.line - the line number that is covered
     * @property {number} source_files[].coverage[].lines.details.hit - how many times the line was hit
     * @property {object} source_files[].coverage[].functions - an object descibing the functions covered in the file
     * @property {number} source_files[].coverage[].functions.found - total number of functions found in the file
     * @property {number} source_files[].coverage[].functions.hits - total number of functions covered in the file
     * @property {Object[]} source_files[].coverage[].functions.details - an array of points in the file that descibes the line and the amount of times it was covered
     * @property {number} source_files[].coverage[].functions.details.line - the line number that is covered
     * @property {number} source_files[].coverage[].functions.details.hit - how many times the line was hit
     * @property {object} source_files[].coverage[].branches - an object descibing the branches covered in the file
     * @property {number} source_files[].coverage[].branches.found - total number of branches found in the file
     * @property {number} source_files[].coverage[].branches.hits - total number of branches covered in the file
     * @property {Object[]} source_files[].coverage[].branches.details - an array of points in the file that descibes the line and the amount of times it was covered
     * @property {number} source_files[].coverage[].branches.details.line - the line number that is covered
     * @property {number} source_files[].coverage[].branches.details.hit - how many times the line was hit
     * @property {object} git - the state of the git config at the time of sending coverage
     * @property {object} git.head - git head details
     * @property {string} git.head.id - the commit id
     * @property {string} git.head.committer_name - the committer name
     * @property {string} git.head.committer_email - the committer email
     * @property {string} git.head.message - the commit message
     * @property {string} git.head.author_name - the author of the commit's name
     * @property {string} git.head.author_email - the author of the commit's email
     * @property {string} git.head.branch - the current working branch
     * @property {Object[]} git.head.remotes - an array of all the remotes
     * @property {string} git.head.remotes[].name - the name of the remote
     * @property {string} git.head.remotes[].url - the url of the remote
     * @example
     *
     *{
     *   "source_files": [{
     *       "name": "util/lcov.js",
     *       "source": "function d() {\n  return \'hello\';\n}",
     *       "coverage": [{
     *           "lines": {
     *               "found": 53,
     *               "hit": 53,
     *               "details": [{
     *                   "line": 1,
     *                   "hit": 1
     *               }, {...}]
     *           },
     *           "functions": {
     *               "hit": 6,
     *               "found": 6,
     *               "details": [{
     *                   "name": "(anonymous_0)",
     *                   "line": 7,
     *                   "hit": 3
     *               }, {...}]
     *           },
     *           "branches": {
     *               "hit": 24,
     *               "found": 24,
     *               "details": [{
     *                   "line": 16,
     *                   "block": 0,
     *                   "branch": 0,
     *                   "taken": 4
     *               }, {...}]
     *           },
     *           "title": "",
     *           "source": "",
     *           "file": "/Users/gacsapo/Documents/temp/node-coverage-server/util/lcov.js"
     *       }]
     *   }],
     *   "git": {
     *       "head": {
     *           "id": "07e4ee9f38d7c41fed09a2b93f6ce23c4a2c49da",
     *           "committer_name": "Gabriel Csapo",
     *           "committer_email": "gabecsapo@gmail.com",
     *           "message": "Initial commit",
     *           "author_name": "Gabriel Csapo",
     *           "author_email": "gabecsapo@gmail.com"
     *       },
     *       "branch": "master",
     *       "remotes": [{
     *           "name": "origin",
     *           "url": "https://github.com/gabrielcsapo/node-coverage-server.git"
     *       }]
     *   },
     *   "run_at": "2017-01-17T23:18:16.248Z"
     *}
     **/

    const CoverageModel = new mongoose.Schema({}, { _id: false, strict: false });
    const Coverage = mongoose.model('coverages', CoverageModel);

    /**
    * saves a coverage model the collection
    * @function save
    * @memberof Coverage
    * @param  {Coverage} model - the coverage model
    * @return {Promise} - a promise that resolves with the model after it was inserted
    */
    module.exports.save = function save(model) {
      return new Promise((resolve, reject) => {
          const { git } = model;
          const _id = `${git.commit}#${git.branch}`;

          model['_id'] = _id;
          Coverage.findOneAndUpdate({
              _id
          }, model, { upsert: true }, (err, result) => {
              if(err) { return reject(err); }
              return resolve(result);
          });
      });
    };

    /**
     * gets a list of repos
     * @function repos
     * @memberof Coverage
     * @param  {string} repo - the url of the repo
     * @return {Promise} - a promise that resolves with the model after it was inserted
     */
    module.exports.repos = function repos(repo) {
      return new Promise((resolve, reject) => {
        let options = {};
        if(repo) {
          options = { "git.remotes.url": repo };
        }

        Coverage.distinct("git.remotes.url", options).exec((err, docs) => {
          if(err) { return reject(err); }
          docs = docs.map((u) => {
            if(u.indexOf('.git') > 0) {
                return u.substr(0, u.indexOf('.git'));
            } else {
              return u;
            }
          });
          return resolve([...new Set(docs)]);
        });
      });
    };

    /**
     * gets a repos coverage model or all coverage models
     * @function get
     * @memberof Coverage
     * @param  {string} repo - the url of the repo
     * @return {Coverage[]} - a promise that resolves with the model after it was inserted
     */
    module.exports.get = function get(repo) {
        return new Promise((resolve, reject) => {
            const options = [
              { $match: { "git.remotes.url": repo } },
              {
                $group: {
                    _id: "$git.remotes.url",
                    history: {
                        $push: "$$ROOT"
                    }
                }
            }];

            Coverage.aggregate(options, (err, docs) => {
                if(err) { return reject(err); }
                return resolve(docs);
            });
        });
    };