[
  {
    "path": ".gitignore",
    "content": "node_modules/\nbower_components/\njspm_packages/\n.idea/*\n_site/\ntypings/\ndist/\n*.log\n"
  },
  {
    "path": ".npmignore",
    "content": "node_modules/\nbower_components/\ntest/\nsrc/\nexamples/"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: false\nlanguage: node_js\ncache:\n  directories:\n    - node_modules\nbranches:\n  only:\n    - master\nnotifications:\n  email: false\nnode_js:\n  - '4.1'\nbefore_install:\n  - npm i -g npm@^2.0.0\n  - npm install -g grunt\n  - npm install -g bower\n  - npm install\n  - bower install\nbefore_script:\n  - npm prune\nafter_success:\n  - npm run semantic-release\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "Contributing\n============\n\nWe'd love to get contributions from your part...in the end that's the value behind sharing, right? :smile:\nHowever, for staying organized we'd like you to follow these simple guidelines:\n\n- [Issues](#issues)\n- [Commit Message Guidelines](#commit)\n- [Coding](#coding)\n\n## <a name=\"issues\"></a> Issues\n\nIf you have a bug or enhancement request, please file an issue.\n\nWhen submitting an issue, please include context from your test and\nyour application. If there's an error, please include the error text.\n\nThe best would be to submit a PR with a failing test :smiley:.\n\n## <a name=\"commit\"></a> Commit Message Guidelines\n\nThese guidelines have been taken and adapted from the [official Angular guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-guidelines). By following the rules also mentioned in [conventional-changelog](https://www.npmjs.com/package/conventional-changelog). This leads to much more readable and clearer commit messages.\n\n### Commit Message Format\nEach commit message consists of a **header**, a **body** and a **footer**.  The header has a special\nformat that includes a **type**, a **scope** and a **subject**:\n\n```\n<type>(<scope>): <subject>\n<BLANK LINE>\n<body>\n<BLANK LINE>\n<footer>\n```\n\nThe **header** is mandatory and the **scope** of the header is optional.\n\nAny line of the commit message cannot be longer than 100 characters! This allows the message to be easier\nto read on GitHub as well as in various git tools.\n\n### Revert\nIf the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.\n\n### Type\nMust be one of the following:\n\n* **feat**: A new feature\n* **fix**: A bug fix\n* **docs**: Documentation only changes\n* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing\n  semi-colons, etc)\n* **refactor**: A code change that neither fixes a bug nor adds a feature\n* **perf**: A code change that improves performance\n* **test**: Adding missing tests\n* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation\n  generation\n\n### Scope\nThe scope could be anything specifying place of the commit change. For example\n`olHelper`, `layer`, etc.\n\n### Subject\nThe subject contains succinct description of the change:\n\n* use the imperative, present tense: \"change\" not \"changed\" nor \"changes\"\n* don't capitalize first letter\n* no dot (.) at the end\n\n### Body\nJust as in the **subject**, use the imperative, present tense: \"change\" not \"changed\" nor \"changes\".\nThe body should include the motivation for the change and contrast this with previous behavior.\n\n### Footer\nThe footer should contain any information about **Breaking Changes** and is also the place to\nreference GitHub issues that this commit **Closes**.\n\n**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.\n\nA detailed explanation can be found in this [document][https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit].\n\n## <a name=\"coding\"></a> Coding\n\nGet a fresh copy of this repo.\n\n### Prepare your environment\n* Install [Node.js](http://nodejs.org/) and NPM (should come with)\n* Install global dev dependencies: `npm install -g bower grunt-cli`\n* Install local dev dependencies: `npm install && bower install` in repository directory\n\n### Development Commands\n* `grunt build` to concat and build\n* `grunt karma` for continuous testing mode with karma (useful during development as tests will be run on each change)\n* `grunt karma:ci` for a one-time execution of the tests (used by Travis)\n"
  },
  {
    "path": "Gruntfile.js",
    "content": "module.exports = function(grunt) {\n\n    grunt.loadNpmTasks('grunt-contrib-concat');\n    grunt.loadNpmTasks('grunt-contrib-uglify');\n    grunt.loadNpmTasks('grunt-karma');\n\n    var ngAnnotate = require(\"ng-annotate\");\n\n    grunt.initConfig({\n        pkg: grunt.file.readJSON('bower.json'),\n        npmpkg: grunt.file.readJSON('package.json'),\n\n        meta: {\n          banner: '/**\\n' +\n          ' * <%= pkg.description %>\\n' +\n          ' * @version v<%= npmpkg.version %> - <%= grunt.template.today(\"yyyy-mm-dd\") %>\\n' +\n          ' * @link <%= pkg.homepage %>\\n' +\n          ' * @author <%= pkg.authors.join(\", \") %>\\n' +\n          ' * @license MIT License, http://www.opensource.org/licenses/MIT\\n' +\n          ' */\\n'\n        },\n        bower: {\n          install: {}\n        },\n        concat: {\n          options: {\n            //banner: '<%= meta.banner %>\\n(function(angular, undefined) {\\n\\'use strict\\';\\n',\n            banner: '<%= meta.banner %>\\n',\n            footer: '',\n            process: function(src, filepath) {\n              var res = ngAnnotate(src, {\n                  add: true,\n              });\n\n              if (res.errors) {\n                  // do something with this, res.errors is now an array of strings\n                  throw new Error(res.errors.join(\"\\n\"));\n              } else {\n                  return res.src;\n              }\n            }\n          },\n          dist: {\n            files: {\n              'dist/<%= pkg.name %>.js': [\n                'src/**/*.js'\n              ],\n              'dist/<%= pkg.name %>-bundle.js': [\n                'bower_components/deep-diff/releases/deep-diff-0.2.0.min.js',\n                'bower_components/uri-templates/uri-templates.js',\n                'src/**/*.js'\n              ]\n            }\n          }\n        },\n        uglify: {\n          options: {\n            mangle: true,\n            banner: '<%= meta.banner %>'\n          },\n          dist: {\n            files: {\n              'dist/<%= pkg.name %>.min.js': 'dist/<%= pkg.name %>.js',\n              'dist/<%= pkg.name %>-bundle.min.js': 'dist/<%= pkg.name %>-bundle.js'\n            }\n          }\n        },\n        karma: {\n          dev: {\n            configFile: 'test/karma.conf.js',\n            singleRun: false,\n            autoWatch: true,\n            browsers: ['Chrome'],\n            reporters: ['mocha']\n          },\n          ie: {\n            configFile: 'test/karma.conf-ci.js',\n            singleRun: true,\n            autoWatch: false,\n            browsers: ['IE'],\n            reporters: ['mocha']\n          },\n          ci: {\n            configFile: 'test/karma.conf-ci.js',\n            singleRun: true,\n            autoWatch: false,\n            browsers: ['PhantomJS'],\n            reporters: ['mocha']\n          }\n        }\n    });\n\n    grunt.registerTask('build', ['concat', 'uglify']);\n\n    return grunt;\n};"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014 \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 all\ncopies 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 THE\nSOFTWARE.\n\n"
  },
  {
    "path": "README.md",
    "content": "# modelFactory \n\n[![Build Status](https://travis-ci.org/swimlane/angular-model-factory.svg?branch=master)](https://travis-ci.org/Swimlane/model-factory) [![npm version](https://badge.fury.io/js/angular-model-factory.svg)](http://badge.fury.io/js/angular-model-factory) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) [![Bower version](https://badge.fury.io/bo/angular-model-factory.svg)](http://badge.fury.io/bo/angular-model-factory) [![Codacy Badge](https://www.codacy.com/project/badge/d6659f50bd234f099738358a2a17bf9c)](https://www.codacy.com/public/amcdaniel2/model-factory) [![Join the chat at https://gitter.im/Swimlane/angular-model-factory](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Swimlane/angular-model-factory?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Dependency Status](https://david-dm.org/swimlane/angular-model-factory.svg)](https://david-dm.org/Swimlane/angular-model-factory) [![devDependency Status](https://david-dm.org/swimlane/angular-model-factory/dev-status.svg)](https://david-dm.org/Swimlane/angular-model-factory#info=devDependencies)\n\nA light-weight model layer that bridges the gap between AngularJS and your RESTful APIs.\n\nWhy would you use this over other available solutions?\n\n- Lightweight/Simple, the code simply does some basic copy/extending and prototypical instances; no magic required.\n- Patterns/Practices, the model definition closely resembles Angular's ngResource meaning its easy to swap out, replace later, eases scaling/transition, and its designed for Angular; not a backbone port!\n- Utilizes Angular at the core, it doesn't duplicate things Angular already does.  Any action can be passed a `$http` configuration option, all your interceptors still work, it uses Angular's cache, etc!\n- Compliant, URI Template matches the specs.\n- Small - 1.45KB gzipped/minified ( excludes depedencies )\n- Minimal Dependencies, only use URI template and deep-diff ( this isn't even required ) utility.  NO underscore, lodash, jquery, etc!\n- Its full of awesome features\n\n\nSee [wiki](https://github.com/swimlane/model-factory/wiki) for documentation.\n\n\n## Features\n\n- URI Templates (RFC6570)\n- Model instances\n- Collections\n- Single Datastore\n- Caching / Cache invalidation\n- Default value population\n- Pending / Completed Status\n- Relationships\n- Object Deep Diff / Reversion\n- Track active promises to prevent duplicate sends\n\n\n## Other Solutions\n\nAfter doing quite a bit of research before writing this, I took a look at other solutions.  Here is what I found and why I wrote my own.\n\n- [Restmod](https://github.com/platanus/angular-restmod)\nVery nice solution but very opinionated and hyper-active. 22kb min\n\n- [Modelizer](https://github.com/VasilioRuzanni/angular-modelizer)\nGood but requires Lodash. 23kb min\n\n- [ModelCore](https://github.com/klederson/ModelCore/)\nGood but not very well tested and not active.\n\n- [angular-watch-resource](https://github.com/marmorkuchen-net/angular-watch-resource) - Really only handles collections\n\n- [angular-restful](http://esdrasedu.github.io/angular-restful/#/) - Very basic but nice\n\n- [ngResource](https://docs.angularjs.org/api/ngResource/service/$resource)\nOut of the box model layer, very limited.\n\n- [angularjs-rails-resource](https://github.com/FineLinePrototyping/angularjs-rails-resource)\nToo rails-ish.\n\n- [angular-nested-resource](https://github.com/roypeled/angular-nested-resource) - Okay API, not loving the nested architecture.\n\n- [Aar.js](http://aarjs.com/)\nVery light, not sure what value this adds.\n\n- [Angular Activerecord](https://github.com/bfanger/angular-activerecord)\nA copy of BackboneModel but doesn't really work with Angular patterns.\n\n- [Angular-Data](http://angular-data.pseudobry.com/)\nNot really a model layer but a data store.  Very very heavy ( 67kb min )\n\n- [ngActiveResource](https://github.com/FacultyCreative/ngActiveResource)\nVery ruby-ish api.  Requires lodash.  Has validation but thats not needed in angular if you do it right.\n\n- [restangular](https://github.com/mgonto/restangular) \nI don't consider this a model layer; it feels moore like a fancy http layer that returns promises because everyone complains about ngResource not doing it.  It requires underscore.\n\n- [BreezeJS](http://www.breezejs.com/) \nThis is a very full featured model/cache/validation etc.  Its framework agnostic, which means it follows its own patterns and not angulars.  Its very heavy, requires server data massaging, and the API looks like Microsoft Entity Framework.\n\n- [ng-backbone](https://github.com/adrianlee44/ng-backbone)\nAnother backbone model clone.  This one actually requires backbone and lodash.\n\n## Install\n\nInstall via bower:\n\n```\n$ bower install angular-model-factory --save\n```\n\nInstall via npm:\n\n```\n$ npm install angular-model-factory --save\n```\n\nAlternatively you can download/clone the repo and link the files in `dist/`. \n\n### Dependencies\n\n- Angular >= 1.3\n- [deep-diff](https://github.com/flitbit/diff)\n- [uri-templates](https://github.com/geraintluff/uri-templates)\n\n\n## Contribute\n\nLibraries like this live and get better with an active community. Have something to contribute? We'd love to see it. Just head over to our [contribution guidelines](CONTRIBUTING.md).\n\n## Credits\n\n`angular-model-factory` is a [Swimlane](http://swimlane.com) open-source project; we believe in giving back to the open-source community by sharing some of the projects we build for our application. Swimlane is an automated cyber security operations and incident response platform that enables cyber security teams to leverage threat intelligence, speed up incident response and automate security operations.\n"
  },
  {
    "path": "bower.json",
    "content": "{\n  \"name\": \"angular-model-factory\",\n  \"authors\": [\n    \"Austin McDaniel <amcdaniel2@gmail.com>\",\n    \"Juri Strumpflohner <juri.strumpflohner@gmail.com>\"\n  ],\n  \"description\": \"modelFactory makes working with RESTful APIs in AngularJS easy\",\n  \"license\": \"MIT\",\n  \"homepage\": \"http://swimlane.github.io/angular-model-factory/\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/swimlane/model-factory.git\"\n  },\n  \"main\": \"./dist/angular-model-factory.js\",\n  \"ignore\": [\n    \"**/.*\",\n    \"node_modules\",\n    \"bower_components\",\n    \"src\",\n    \"examples\"\n  ],\n  \"dependencies\": {\n    \"uri-templates\": \"~0.1.5\",\n    \"deep-diff\": \"~0.2.0\",\n    \"angular\": \"1.3.x\"\n  },\n  \"devDependencies\": {\n    \"angular-mocks\": \"1.x\",\n    \"angular-scenario\": \"1.x\"\n  },\n  \"resolutions\": {\n    \"angular\": \"1.4.9\"\n  }\n}\n"
  },
  {
    "path": "examples/plain/index.html",
    "content": "<!doctype html>\n<html>\n<head>\n</head>\n<body ng-app=\"demo\">\n    <h1>Model Factory demo</h1>\n\n\n\n\n    <script type=\"text/javascript\" src=\"../../bower_components/angular/angular.js\"></script>\n    <script type=\"text/javascript\" src=\"../../bower_components/uri-templates/uri-templates.min.js\"></script>\n    <script type=\"text/javascript\" src=\"../../bower_components/deep-diff/releases/deep-diff-0.2.0.min.js\"></script>\n    <script type=\"text/javascript\" src=\"../../dist/angular-model-factory.js\"></script>\n\n    <script type=\"text/javascript\">\n        var app = angular.module('demo', ['modelFactory'])\n            .factory('PersonModel', function($modelFactory){\n                return $modelFactory('/api/people');\n            })\n            .controller('DemoCtrl', function(PersonModel){\n                var vm = this;\n            });\n    </script>\n</body>\n</html>"
  },
  {
    "path": "examples/simple.js",
    "content": "define(['angular', 'model-factory'], function (angular) {\n\n    var module = angular.module('myapp', ['modelFactory']);\n    \n    module.factory('AnimalModel', function() {\n        function Animal(val) {\n            angular.extend(this, val);\n        };\n\n        Animal.prototype.dateAdded = function(val) {\n            return new Date(val);\n        };\n\n        return Animal;\n    });\n\n    module.factory('ZooModel', function($modelFactory, AnimalModel){\n        return $modelFactory('api/zoo', {\n            defaults: {\n                zooName: 'New Zoo'\n            },\n            map: {\n                animals: function(animal){\n                    return animal.map(function(a){ return new AnimalModel(a); })\n                }    \n            },\n            actions: {\n                query: {\n                    cache: true\n                },\n                $copy: {\n                    method: 'POST',\n                    url: 'copy'\n                }\n            }\n        });\n    });\n\n    module.config(function ($stateProvider) {\n        $stateProvider.state('zoo', {\n            url: '/zoo',\n            templateUrl: 'zoo.tpl.html',\n            controller: 'ZooCtrl',\n            resolve: {\n                zoos: function (ZooModel) {\n                    return ZooModel.query();\n                }\n            }\n        });\n    });\n\n    module.controller('ZooCtrl', function ($scope, ZooModel, zoos) {\n\n        //-> zoos = [ ZooModel({ type: 'National', name: 'DC Zoo', id: '123' }) ]\n        $scope.zoos = zoos;\n\n        $scope.editZoo = function(zoo){\n            // UPDATE since we have an id\n            zoo.$save().then(function(model){\n\n                // cache was invalidated\n                alert('Zoo was updated');\n            });\n        };\n\n        $scope.deleteZoo = function(zoo){\n            zoo.$destroy().then(function(){\n\n                // cache was invalidated\n                alert('Zoo was deleted');\n\n                // zoo was automatically removed from array\n                //-> $scope.zoos = [];\n            });\n        };\n\n        $scope.createZoo = function(name){\n            var newZoo = new ZooModel({\n                name: name\n            });\n\n            // POST since no id\n            zooModel.$save().then(function(model){\n                //-> ZooModel({ name: 'whatever', newAnimal: true })\n\n                // push our new zoo into the model\n                // this will relate the zoo to the model\n                // so if a user destorys this model it will\n                // automatically be removed from the array too\n                $scope.zoos.push(model);\n            });\n        };\n\n        $scope.getZoo = function(id){\n            // get a zoo by id\n            ZooModel.get(id).then(function(model){\n                $scope.newZoo = model;\n            });\n        };\n\n        $scope.getPandas = function(){\n            // get a zoo by animalType of panda\n            // GET api/zoo?animalType=panda\n            ZooModel.query({ animalType: 'panda' }).then(function(models){\n                $scope.pandas = models;\n            });\n        };\n\n        $scope.refreshZoos = function(){\n            ZooModel.query().then(function(models){\n                // models = [ ZooModel({ type: 'National', name: 'DC Zoo', id: '123' }) ]\n                $scope.allZoos = models;\n            });\n        };\n\n    });\n\n    return module;\n});\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"angular-model-factory\",\n  \"author\": \"Austin McDaniel <amcdaniel2@gmail.com>\",\n  \"authors\": [\n    \"Juri Strumpflohner <juri.strumpflohner@gmail.com>\"\n  ],\n  \"description\": \"modelFactory makes working with RESTful APIs in AngularJS easy\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"grunt\": \"~0.4.5\",\n    \"grunt-bower\": \"*\",\n    \"grunt-cli\": \">= 0.1.7\",\n    \"grunt-contrib-concat\": \"*\",\n    \"grunt-contrib-jshint\": \"*\",\n    \"grunt-contrib-uglify\": \"*\",\n    \"grunt-karma\": \"~0.9.0\",\n    \"jasmine-core\": \"~2.1.2\",\n    \"karma\": \"~0.12.28\",\n    \"karma-chrome-launcher\": \"~0.1.5\",\n    \"karma-firefox-launcher\": \"~0.1.3\",\n    \"karma-ie-launcher\": \"^0.2.0\",\n    \"karma-jasmine\": \"~0.3.1\",\n    \"karma-mocha-reporter\": \"~0.3.1\",\n    \"karma-phantomjs-launcher\": \"~0.1.4\",\n    \"ng-annotate\": \"^1.0.2\",\n    \"publish-latest\": \"^1.1.2\",\n    \"semantic-release\": \"^4.3.5\"\n  },\n  \"main\": \"./dist/angular-model-factory.js\",\n  \"scripts\": {\n    \"build\": \"grunt build\",\n    \"prepublish\": \"npm run build\",\n    \"postpublish\": \"publish-latest\",\n    \"test\": \"grunt karma:ci\",\n    \"semantic-release\": \"semantic-release pre && npm publish && semantic-release post\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/Swimlane/angular-model-factory.git\"\n  }\n}\n"
  },
  {
    "path": "src/modelFactory.js",
    "content": "/* global angular:false */\n'use strict';\n\n(function(global, factory) {\n    if (typeof define === 'function' && define.amd) {\n        define(['angular', 'uri-templates', 'deep-diff'], factory);\n    } else if (typeof module !== 'undefined' && module.exports) {\n        module.exports = factory(require('angular'), require('uri-templates'), require('deep-diff'));\n    } else {\n        global.ModelFactory = factory(global.angular, global.UriTemplate, global.DeepDiff);\n    }\n})(this, function(angular, UriTemplate, DeepDiff) {\n\nvar module = angular.module('modelFactory', []);\n\n// compression\nvar isUndefined = angular.isUndefined,\n    forEach = angular.forEach,\n    extend = angular.extend,\n    copy = angular.copy;\n\n// keywords that are reserved for model instance\n// internal usage only and to be stripped\n// before sending to server\nvar instanceKeywords = [ '$$array', '$save', '$destroy',\n    '$pending', '$rollback', '$diff', '$update', '$commit', '$copy' ];\n\n// keywords that are reserved for the model static\n// these are used to determine if a attribute should be extended\n// to the model static class for like a helper that is not a http method\nvar staticKeywords = [ 'actions', 'instance', 'list', 'defaults',\n    'pk', 'stripTrailingSlashes', 'map'];\n\n// Deep extends\n// http://stackoverflow.com/questions/15310935/angularjs-extend-recursive\nvar extendDeep = function (dst) {\n    forEach(arguments, function (obj) {\n        if (obj !== dst) {\n            forEach(obj, function (value, key) {\n                if (instanceKeywords.indexOf(key) === -1) {\n                    if (dst[key]) {\n                        if (angular.isArray(dst[key])) {\n                            dst[key].concat(value.filter(function (v) {\n                                var vv = dst[key].indexOf(v) !== -1;\n                                if (vv) extendDeep(vv, v);\n                                return vv;\n                            }));\n                        } else if (angular.isObject(dst[key])) {\n                            extendDeep(dst[key], value);\n                        } else {\n                            // if value is a simple type like a string, boolean or number\n                            // then assign it\n                            dst[key] = value;\n                        }\n                    } else if (!angular.isFunction(dst[key])) {\n                        dst[key] = value;\n                    }\n                }\n            });\n        }\n    });\n    return dst;\n};\n\n// Create a shallow copy of an object and clear other fields from the destination\n// https://github.com/angular/angular.js/blob/master/src/ngResource/resource.js#L30\nvar shallowClearAndCopy = function(src, dst) {\n    dst = dst || {};\n\n    // Remove any properties in destination that were not\n    // returned from the source\n    forEach(dst, function (value, key) {\n        if(!src.hasOwnProperty(key) && key.charAt(0) !== '$') {\n            delete dst[key];\n        }\n    });\n\n    for(var key in src) {\n\n        if(src.hasOwnProperty(key) && key.charAt(0) !== '$')  {\n            // For properties common to both source and destination,\n            // check for object references and recurse as needed. Route around\n            // arrays to prevent value/order inconsistencies\n            if(angular.isObject(src[key]) && !angular.isArray(src[key])) {\n                dst[key] = shallowClearAndCopy(src[key], dst[key]);\n            } else {\n                // Not an object, so just overwrite with value from source\n                dst[key] = src[key];\n            }\n        }\n    }\n\n    return dst;\n};\n\n\nmodule.provider('$modelFactory', function(){\n    var provider = this;\n\n    provider.defaultOptions = {\n\n        /**\n         * URL Prefix for requests.  This should only really\n         * be used at the provider level, not an instance.\n         */\n        prefix: '',\n\n        /**\n         * Primary key of the model\n         */\n        pk: 'id',\n\n        /**\n         * By default, trailing slashes will be stripped\n         * from the calculated URLs.\n         */\n        stripTrailingSlashes: true,\n\n        /**\n         * Default values for a new instance.\n         * This will only be populated if the property\n         * is undefined.\n         *\n         * Example:\n         *      defaults: {\n         *          'create': new Date()\n         *      }\n         */\n        defaults: {},\n\n        /**\n         * Attribute mapping.  Tranposes attributes\n         * from a response to a different attribute.\n         *\n         * Also handles 'has many' and 'has one' relations.\n         *\n         * Example:\n         *      map: {\n         *          // transpose `animalId` to\n         *          // `id` on our instance\n         *          'id': 'animalId',\n         *\n         *          // transposes `animal` attribute\n         *          // to an array of `AnimalModel`'s\n         *          'animal': AnimalModel.List,\n         *\n         *          // transposes `location` attribute\n         *          // to an instance of `LocationModel`\n         *          'location': LocationModel\n         *      }\n         */\n        map:{},\n\n        /**\n         * Hash declaration of model actions.\n         *\n         * NOTE: Anything prefixed with `$` will be attached to the\n         * model instance rather than the static.\n         */\n        actions:{\n\n            /**\n             * Base options to be applied to all other actions by default.\n             * In addition to the methods listed here, any `$http` attribute\n             * is valid. https://docs.angularjs.org/api/ng/service/$http\n             *\n             * If the method is a `GET` and the arguments invoking it are a string or number,\n             * then the model automatically assumes you are wanting to pass those are the primary key.\n             *\n             * Action Agnostic Attributes:\n             *  - `override` Overrides the base url prefixing.\n             *  - `method` Case insensitive HTTP method (e.g. GET, POST, PUT, DELETE, JSONP, etc).\n             *  - `url` URL to be invoked by `$http`.  All urls are prefixed with the base url passed initally.  All templates are [URI Template](http://tools.ietf.org/html/rfc6570) spec.\n             */\n            'base': {\n                /**\n                 * Wrap the response from an action in a instance of the model.\n                 */\n                wrap: true,\n\n                /**\n                 * Callback before data is sent to server.\n                 * This allows developers to manipulate the\n                 * object before its sent to the server but\n                 * not effect the core object.\n                 */\n                beforeRequest: undefined,\n\n                /**\n                 * Callback after data recieved from server but\n                 * before the data is wrapped in an instance.\n                 */\n                afterRequest: undefined,\n\n                /**\n                 * By default, do not cache the requests.\n                 */\n                cache: false\n            },\n            'get': {\n                method: 'GET'\n            },\n            'query': {\n                method: 'GET',\n\n                /**\n                 * If true then the returned object for this action is an array.\n                 */\n                isArray: true\n            },\n\n            /**\n             * In theory `post`, `update`, and `delete` below would/should not be used,\n             * instead one would use `$save` or `$destroy` to be invoked\n             */\n            'post': {\n                method: 'POST',\n                invalidateCache: true\n            },\n            'update': {\n                method: 'PUT',\n                invalidateCache: true\n            },\n            'delete': {\n                method: 'DELETE',\n                invalidateCache: true\n            }\n        },\n\n        /**\n         * Instance level extensions/helpers.\n         *\n         * Example:\n         *      instance: {\n         *          'name': function() {\n         *              return this.first + ' ' + this.last\n         *          }\n         *      }\n         */\n        instance: {},\n\n        /**\n         * List level extensions/helpers.\n         *\n         * Example:\n         *\n         *      list: {\n         *          'namesById': function(id){\n         *              return this.find(function(u){ return u.id === id; });\n         *          }\n         *      }\n         *\n         */\n        list: {}\n    };\n\n    provider.$get = ['$rootScope', '$http', '$q', '$log', '$cacheFactory', function($rootScope, $http, $q, $log, $cacheFactory) {\n\n        /**\n         * Model factory.\n         *\n         * Example usages:\n         *       $modelFactory('api/zoo');\n         *       $modelFactory('api/zoo', { ... });\n         */\n        function modelFactory(url, options) {\n\n            /**\n             * Prevents multiple calls of the exact same type.\n             *\n             *      { key: url, value: promise }\n             *\n             */\n            var promiseTracker = {};\n\n            /**\n             * Make a pretty name from the url\n             * for the event emitters\n             */\n            var nameSplit = url.split('/'),\n                prettyName = nameSplit[nameSplit.length - 1];\n\n            // copy so we also extend our defaults and not override\n            //var actions = angular.extend({}, defaultOptions.actions, options.actions);\n            options = extendDeep({}, copy(provider.defaultOptions), options);\n\n            //\n            // Collection\n            // ------------------------------------------------------------\n            //\n\n            /**\n             * Model list instance.\n             * All raw objects passed will be converted to an instance of this model.\n             *\n             * If we `push` a item into an existing collection, a pointer will be made\n             * so on a destroy items will be removed from the array as well.\n             *\n             * Example usages:\n             *       var zoos = new Zoo.List([ {}, ... ]);\n             */\n            function ModelCollection(value){\n                value = value || [];\n\n                // wrap each obj\n                value.forEach(function(v, i){\n                    // this should not happen but prevent blow up\n                    if(v === null || v === undefined) return;\n\n                    // reset to new instance\n                    value[i] = wrapAsNewModelInstance(v, value);\n                });\n\n                // override push to set an instance\n                // of the list on the model so destroys will chain\n                var __oldPush = value.push;\n                value.push = function(){\n                    // Array.push(..) allows to pass in multiple params\n                    var args = Array.prototype.slice.call(arguments);\n\n                    for(var i=0; i<args.length; i++){\n                        args[i] = wrapAsNewModelInstance(args[i], value);\n                    }\n\n                    __oldPush.apply(value, args);\n                };\n\n                // add list helpers\n                if(options.list){\n                    extend(value, options.list);\n                }\n\n                return value;\n            };\n\n            // helper function for creating a new instance of a model from\n            // a raw JavaScript obj. If it is already a model, it will be left\n            // as it is\n            var  wrapAsNewModelInstance = function(rawObj, arrayInst){\n                // create an instance\n                var inst = rawObj.constructor === Model ?\n                    rawObj : new Model(rawObj);\n\n                // set a pointer to the array\n                inst.$$array = arrayInst;\n\n                return inst;\n            };\n\n            // ES5, IE compatible version to retrieve the name of a function. ES6\n            // would permit to do something like functionRef.name\n            var functionName = function(fun){\n                var ret = fun.toString();\n                ret = ret.substr('function '.length);\n                ret = ret.substr(0, ret.indexOf('('));\n                return ret;\n            };\n\n            //\n            // Model Instance\n            // ------------------------------------------------------------\n\n            /**\n             * Model instance.\n             *\n             * Example usages:\n             *       var zoo = new Zoo({ ... });\n             */\n            function Model(value) {\n                var instance = this,\n                    commits = [];\n\n                // if the value is undefined, create a empty obj\n                value = value || {};\n\n                // build the defaults but only on new instances\n                forEach(options.defaults, function(v, k){\n                    // only populates when not already defined\n                    if(value[k] === undefined){\n                        if(typeof v === 'function'){\n                            // pass the value so you can combine things\n                            // this could be tricky if you have defaults that rely on other defaults ...\n                            // like: { name: function(val) { return val.firstName + val.lastName }) }\n                            value[k] = copy(v(value));\n                        } else {\n                            value[k] = copy(v);\n                        }\n                    }\n                });\n\n                // Map all the objects to new names or relationships\n                forEach(options.map, function(v, k){\n                    if (functionName(v) === functionName(Model) || functionName(v) === functionName(ModelCollection)) {\n                        value[k] = new v(value[k]); // jshint ignore:line\n                    } else if (typeof v === 'function') {\n                        // if its a function, invoke it,\n                        // this would be helpful for seralizers\n                        // like: map: { date: function(val){ return moment(val) } }\n                        value[k] = v(value[k], value);\n                    } else {\n                        value[k] = value[v];\n                        delete value[v];\n                    }\n                });\n\n                // attach instance actions\n                forEach(options.actions, function(v, k){\n                    if(k[0] === '$'){\n                        instance[k] = function(){\n                            return Model.$buildRequest(k, v, instance);\n                        };\n                    }\n                });\n\n                // copy values to the instance\n                extend(instance, value);\n\n                // copy instance level helpers to this instance\n                extend(instance, copy(options.instance));\n\n                /**\n                 * Save the instance to the server.  Posts the instance unless\n                 * the instance has the `pk` attribute already then it will do a put.\n                 */\n                instance.$save = function(){\n                     var actionType = instance[options.pk] ? 'update' : 'post',\n                         promise = Model[actionType](this);\n\n                    instance.$pending = true;\n\n                    promise.then(function(value){\n                        instance.$pending = false;\n\n                        // extend the value from the server to me\n                        if (value) {\n                            instance.$update(value);\n                        }\n\n                        var broadcastName = actionType === 'post' ? 'created' : 'updated';\n                        $rootScope.$broadcast(prettyName + '-' + broadcastName, instance);\n\n                        // commit the change for reversion\n                        commits.push(angular.toJson(instance));\n                    }, function () {\n                        // rejected\n                        instance.$pending = false;\n                    });\n\n                    return promise;\n                };\n\n                /**\n                 * Delete the instance.  Performs a DELETE on this instance performing\n                 * the delete action passing an instance of itself.\n                 *\n                 * If the item is associated with an array, it will automatically be removed\n                 * on successful delete.\n                 */\n                instance.$destroy = function(){\n                    // keep a local pointer since we strip before send\n\n                    var promise = Model.delete(this);\n                    instance.$pending = true;\n\n                    promise.then(function(){\n                        instance.$pending = false;\n\n                        var arr = instance.$$array;\n                        if(arr){\n                            arr.splice(arr.indexOf(instance), 1);\n                        }\n\n                        $rootScope.$broadcast(prettyName + '-destroyed', instance);\n                    }, function(){\n                        // rejected\n                        instance.$pending = false;\n                    });\n\n                    return promise;\n                };\n\n                /**\n                 * Display the difference between the original data and the\n                 * current instance.\n                 * https://github.com/flitbit/diff\n                 */\n                instance.$diff = function(version){\n                    var prevCommit = commits[version || commits.length - 1],\n                        currCommit = angular.toJson(instance);\n\n                    return DeepDiff.diff(JSON.parse(prevCommit), JSON.parse(currCommit), function(path, key) {\n                        return key[0] === '$';\n                    });\n                };\n\n\n                /**\n                 * Commits the change the commits bucket for rollback later if needed.\n                 */\n                instance.$commit = function () {\n                    // stringify it so you have a clean instance\n                    commits.push(angular.toJson(instance));\n                    return instance;\n                };\n\n                /**\n                 * Reverts the current instance back either the latest instance\n                 * or you can pass a specific instance on the commits stack.\n                 */\n                instance.$rollback = function(version) {\n                    var prevCommit = commits[version || commits.length - 1];\n                    instance.$update(JSON.parse(prevCommit));\n                    return instance;\n                };\n\n                /**\n                 * Extends the properties of the new object onto\n                 * the current object without replacing it.  Helpful\n                 * when copying and then re-copying new props back\n                 */\n                instance.$update = function(n){\n                    shallowClearAndCopy(n, instance);\n                    return instance;\n                };\n\n\n                /**\n                 * Creates a copy by taking the raw data values and by\n                 * creating a new instance of the model.\n                 */\n                instance.$copy = function(){\n                  // get the raw data of the model\n                  var rawData = angular.toJson(this);\n\n                  // ..then wrap it into a new instance to create a clone\n                  return new Model(angular.fromJson(rawData));\n                };\n\n                // Create a copy of the value last so we get all the goodies,\n                // like default values and whatnot.\n                instance.$commit();\n            }\n\n            //\n            // Model Static\n            // ------------------------------------------------------------\n\n            /**\n             * Create an instance of a cache factory\n             * for tracking data of this instance type.\n             * https://docs.angularjs.org/api/ng/service/$cacheFactory\n             */\n            Model.$cache = $cacheFactory(url);\n\n            // attach actions\n            forEach(options.actions, function(v, k){\n                // don't do base or $\n                if(k === 'base' || k[0] === '$') return;\n                Model[k] = function(){\n                    //http://stackoverflow.com/questions/2091138/why-doesnt-join-work-with-function-arguments\n                    var args = Array.prototype.slice.call(arguments);\n                    return Model.$buildRequest.apply(this, [k, v].concat(args));\n                };\n            });\n\n            /**\n             * Builds the request for a set of actions.\n             */\n            Model.$buildRequest = function(action, param, data, extras){\n                var clone = copy(options.actions.base);\n                extend(clone, copy(param));\n\n                // if we explicity call cache\n                // to true and don't pass a factory\n                // lets use our instance level for\n                // data storage means\n                if(clone.cache === true){\n                    clone.cache = Model.$cache;\n                }\n\n                // make sure we have a method specified, otherwise\n                // default to GET\n                clone.method = clone.method || 'GET';\n\n                // uri template to parameterize\n                var uri = options.prefix ? options.prefix + '/' : '';\n\n                // make sure we didn't override the base url prefixing\n                if(!clone.override){\n\n                    // set the uri to the base\n                    uri += url;\n\n                    // if we have a url defined, append to base\n                    if(clone.url) {\n                        //check if we need to add slash, or we only duplicate it\n                        var requiredSlash = !/^(\\/|{\\/)/.test(clone.url);\n                        uri += requiredSlash ? '/' + clone.url : clone.url;\n                    }\n\n\n                    // set the uri to the base\n                    uri = Model.$url(uri, data, clone.method);\n\n                    // attach the pk referece by default if it is a 'core' type\n                    if(action === 'get' || action === 'post' || action === 'update' || action === 'delete'){\n                        uri += '{/' + options.pk + '}';\n                    }\n\n                    if(clone.method === 'GET' && (angular.isString(data) || angular.isNumber(data))){\n                        // if we have a get method and its a number or a string\n                        // you can assume i'm wanting to do something like:\n                        // ZooModel.get(1234) instead of ZooModel.get({ id: 1234 });\n                        var obj = {};\n                        obj[options.pk] = data;\n                        data = obj;\n\n                        // if we have a extra argument on this case we should assume its a\n                        //\n                        if(extras){\n                            // data.param = extras;\n                            clone.params = extendDeep({}, clone.params, extras);\n                            // uri += '{?param*}';\n                        }\n                    } else if(clone.method === 'GET' && angular.isObject(data)){\n                        // if its a GET request and its not the above, we can assume\n                        // you want to do a query param like:\n                        // ZooModel.query({ type: 'panda' }) and do /api/zoo?type=panda\n                        // data = { param: data };\n                        clone.params = extendDeep({}, clone.params, data);\n                        // uri += '{?param*}';\n                    }\n                } else {\n                    uri = clone.url;\n                }\n\n                clone.url = Model.$url(uri, data, clone.method);\n\n                // don't include the payload for DELETE requests\n                if(action !== 'delete' && clone.method !== 'DELETE'){\n                    clone.data = data;\n                }\n\n                return Model.$call(clone);\n            };\n\n            /**\n             * Invokes `$http` given parameters and does some\n             * callback before/after and state setting.\n             */\n            Model.$call = function(params){\n                // if we have the promise in queue, return it\n                var signature = params.method + ':' + params.url;\n                if (promiseTracker[signature]) {\n                    return promiseTracker[signature];\n                }\n\n                var def = $q.defer();\n\n                // set the queue for this promise\n                promiseTracker[signature] = def.promise;\n\n                // copy the data so we can manipulate\n                // it before the request and not affect\n                // the core object\n                params.data = copy(params.data);\n\n                // before callbacks\n                params.beforeRequest &&\n                    params.beforeRequest(params);\n\n                // strip all the internal functions/etc\n                params.data = Model.$strip(params.data);\n\n                $http(params).then(function(response){\n                    // after callbacks\n                    if(params.afterRequest) {\n                        var transform = params.afterRequest(response.data);\n                        if(!isUndefined(transform)) {\n                            response.data = transform;\n                        }\n                    }\n\n                    // if we had a cache, remove it\n                    // this could be optimized to only do\n                    // the invalidation of things by id/etc\n                    if(params.invalidateCache){\n                        Model.$cache.removeAll();\n                    }\n\n                    if (response) {\n                        if (params.wrap) {\n                            if (params.isArray) {\n                                def.resolve(new Model.List(response.data));\n                            } else {\n                                def.resolve(new Model(response.data));\n                            }\n                        } else {\n                            def.resolve(response.data);\n                        }\n                    } else {\n                        def.resolve();\n                    }\n                }, def.reject).finally(function () {\n                    promiseTracker[signature] = undefined;\n                });\n\n                return def.promise;\n            };\n\n            /**\n             * Returns a url given the URI template and parameters.\n             *\n             * Examples:\n             *\n             *      // obj = { id: 2344 }\n             *      Model.$url('api/zoo/{id}', obj)\n             *      //-> 'api/zoo/2345'\n             *\n             *      // {}\n             *      Model.$url('api/zoo/{id}')\n             *      //-> 'api/zoo'\n             *\n             *      // { params: { type: 'panda' } }\n             *      Model.$url('api/zoo/{?params*}')\n             *      //-> 'api/zoo?type=panda'\n             *\n             * Optionally strips trailing `/`'s.\n             *\n             * Based on:\n             * https://github.com/geraintluff/uri-templates\n             */\n            Model.$url = function(u, params, method){\n                var uri = new UriTemplate(u || url)\n                            .fill(function(variableName){\n                                var resolvedVariable = params[variableName];\n\n                                // if we have a match, substitute and remove it\n                                // from the original params object\n                                if(resolvedVariable){\n                                    // only remove params on GET requests as the\n                                    // passed object is intended to be used\n                                    // as URL params. For persistent HTTP calls\n                                    // the object has to be left as it is (for now)\n                                    if(method === 'GET'){\n                                      delete params[variableName];\n                                    }\n\n                                    return resolvedVariable;\n                                }else{\n                                    // ?? log an error??\n                                    return null;\n                                }\n                            });\n                            // .fillFromObject(params || {});\n\n                if(options.stripTrailingSlashes){\n                    uri = uri.replace(/\\/+$/, '') || '/';\n                }\n\n                return uri;\n            };\n\n            /**\n             * Remove instances of reserved keywords\n             * before sending to server/json.\n             */\n            Model.$strip = function(args){\n                // todo: this needs to account for relationships too?\n                // either make recursive or chain invoked\n                if(args && typeof args === 'object'){\n                    forEach(args, function(v,k){\n                        if(instanceKeywords.indexOf(k) > -1){\n                            delete args[k];\n                        }\n                    });\n                }\n                return args;\n            };\n\n            // extend the static class with arguments that are not internal\n            forEach(options, function(v, k){\n                if(staticKeywords.indexOf(k) === -1){\n                    Model[k] = v;\n                }\n            });\n\n            // has to be at end for depedency reasons\n            Model.List = ModelCollection;\n\n            return Model;\n        }\n\n        return modelFactory;\n    }];\n});\n\nreturn module;\n});\n"
  },
  {
    "path": "test/.jshintrc",
    "content": "{\n  \"node\": true,\n  \"browser\": true,\n  \"esnext\": true,\n  \"bitwise\": true,\n  \"camelcase\": true,\n  \"curly\": true,\n  \"eqeqeq\": true,\n  \"immed\": true,\n  \"indent\": 2,\n  \"latedef\": true,\n  \"newcap\": true,\n  \"noarg\": true,\n  \"quotmark\": \"single\",\n  \"regexp\": true,\n  \"undef\": true,\n  \"unused\": true,\n  \"strict\": true,\n  \"trailing\": true,\n  \"smarttabs\": true,\n  \"globals\": {\n    \"after\": false,\n    \"afterEach\": false,\n    \"angular\": false,\n    \"before\": false,\n    \"beforeEach\": false,\n    \"browser\": false,\n    \"describe\": false,\n    \"expect\": false,\n    \"inject\": false,\n    \"it\": false,\n    \"jasmine\": false,\n    \"spyOn\": false\n  }\n}\n\n"
  },
  {
    "path": "test/karma.conf-ci.js",
    "content": "// Karma configuration\n// http://karma-runner.github.io/0.12/config/configuration-file.html\n// Generated on 2014-11-27 using\n// generator-karma 0.8.2\n\nmodule.exports = function(config) {\n  config.set({\n    // enable / disable watching file and executing tests whenever any file changes\n    autoWatch: true,\n\n    // base path, that will be used to resolve files and exclude\n    basePath: '../',\n\n    // testing framework to use (jasmine/mocha/qunit/...)\n    frameworks: ['jasmine'],\n\n    // list of files / patterns to load in the browser\n    files: [\n      'bower_components/angular/angular.js',\n      'bower_components/angular-mocks/angular-mocks.js',\n      // 'bower_components/angular-scenario/angular-scenario.js',\n      // 'bower_components/deep-diff/index.js',\n      // 'bower_components/uri-templates/uri-templates.js',\n      // 'bower_components/angular-animate/angular-animate.js',\n      // 'bower_components/angular-cookies/angular-cookies.js',\n      // 'bower_components/angular-resource/angular-resource.js',\n      // 'bower_components/angular-route/angular-route.js',\n      // 'bower_components/angular-sanitize/angular-sanitize.js',\n      // 'bower_components/angular-touch/angular-touch.js',\n      'dist/angular-model-factory-bundle.min.js',\n      'test/mock/**/*.js',\n      'test/spec/**/*.js'\n    ],\n\n    // list of files / patterns to exclude\n    exclude: [],\n\n    // web server port\n    port: 9876,\n\n    // Start these browsers, currently available:\n    // - Chrome\n    // - ChromeCanary\n    // - Firefox\n    // - Opera\n    // - Safari (only Mac)\n    // - PhantomJS\n    // - IE (only Windows)\n    browsers: [\n      'PhantomJS'\n    ],\n\n    // if the browser doesn't capture within the given ms, kill it\n    captureTimeout: 60000,\n\n    // Which plugins to enable\n    // plugins: [\n    //   'karma-phantomjs-launcher',\n    //   'karma-jasmine'\n    // ],\n\n    // Continuous Integration mode\n    // if true, it capture browsers, run tests and exit\n    singleRun: false,\n\n    colors: true,\n\n    // level of logging\n    // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG\n    logLevel: config.LOG_INFO,\n\n    // Uncomment the following lines if you are using grunt's server to run the tests\n    // proxies: {\n    //   '/': 'http://localhost:9000/'\n    // },\n    // URL root prevent conflicts with the site root\n    // urlRoot: '_karma_'\n  });\n};"
  },
  {
    "path": "test/karma.conf.js",
    "content": "// Karma configuration\n// http://karma-runner.github.io/0.12/config/configuration-file.html\n// Generated on 2014-11-27 using\n// generator-karma 0.8.2\n\nmodule.exports = function(config) {\n  config.set({\n    // enable / disable watching file and executing tests whenever any file changes\n    autoWatch: true,\n\n    // base path, that will be used to resolve files and exclude\n    basePath: '../',\n\n    // testing framework to use (jasmine/mocha/qunit/...)\n    frameworks: ['jasmine'],\n\n    // list of files / patterns to load in the browser\n    files: [\n      'bower_components/angular/angular.js',\n      'bower_components/angular-mocks/angular-mocks.js',\n      // 'bower_components/angular-scenario/angular-scenario.js',\n      'bower_components/deep-diff/index.js',\n      'bower_components/uri-templates/uri-templates.js',\n      // 'bower_components/angular-animate/angular-animate.js',\n      // 'bower_components/angular-cookies/angular-cookies.js',\n      // 'bower_components/angular-resource/angular-resource.js',\n      // 'bower_components/angular-route/angular-route.js',\n      // 'bower_components/angular-sanitize/angular-sanitize.js',\n      // 'bower_components/angular-touch/angular-touch.js',\n      'src/**/*.js',\n      'test/mock/**/*.js',\n      'test/spec/**/*.js'\n    ],\n\n    // list of files / patterns to exclude\n    exclude: [],\n\n    // web server port\n    port: 9876,\n\n    // Start these browsers, currently available:\n    // - Chrome\n    // - ChromeCanary\n    // - Firefox\n    // - Opera\n    // - Safari (only Mac)\n    // - PhantomJS\n    // - IE (only Windows)\n    browsers: [\n      'PhantomJS'\n    ],\n\n    // if the browser doesn't capture within the given ms, kill it\n    captureTimeout: 60000,\n\n    // Which plugins to enable\n    // plugins: [\n    //   'karma-phantomjs-launcher',\n    //   'karma-jasmine'\n    // ],\n\n    // Continuous Integration mode\n    // if true, it capture browsers, run tests and exit\n    singleRun: false,\n\n    colors: true,\n\n    // level of logging\n    // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG\n    logLevel: config.LOG_INFO,\n\n    // Uncomment the following lines if you are using grunt's server to run the tests\n    // proxies: {\n    //   '/': 'http://localhost:9000/'\n    // },\n    // URL root prevent conflicts with the site root\n    // urlRoot: '_karma_'\n  });\n};\n"
  },
  {
    "path": "test/spec/modelFactory.spec.js",
    "content": "'use strict';\n\n/*\n  Specs that test the inner workings of the model-factory. Regression\n  tests can be placed in here.\n*/\n\ndescribe('A person model defined using modelFactory', function() {\n    var PersonModel;\n    var $httpBackend;\n\n    beforeEach(angular.mock.module('modelFactory'));\n\n    beforeEach(function() {\n        angular.module('test-module', ['modelFactory'])\n            .factory('PersonModel', function($modelFactory) {\n                return $modelFactory('/api/people');\n            });\n    });\n\n    beforeEach(angular.mock.module('test-module'));\n\n    beforeEach(inject(function(_$httpBackend_, _PersonModel_) {\n        $httpBackend = _$httpBackend_;\n        PersonModel = _PersonModel_;\n    }));\n\n    afterEach(function() {\n        $httpBackend.verifyNoOutstandingExpectation();\n        $httpBackend.verifyNoOutstandingRequest();\n    });\n\n    // describe('when calling model.$strip', function(){\n    //     it('should remove all model-factory specific functions', function(){\n    //         var raw = {\n    //             id: 1,\n    //             name: 'Juri'\n    //         };\n\n    //         expect(new PersonModel(raw).$strip).toEqual(raw);\n    //     });\n    // });\n\n    describe('when copying a model object', function() {\n\n        it('calling $save on a new model should submit the copied values', function() {\n            var newModel = new PersonModel({\n                name: 'Juri'\n            });\n\n            var copied = newModel.$copy(); //angular.copy(newModel);\n            copied.name = 'Austin';\n\n            $httpBackend.expectPOST('/api/people', JSON.stringify(copied)).respond(200, '');\n\n            copied.$save();\n            $httpBackend.flush();\n        });\n\n        it('calling $save on it should submit the copied values', function() {\n            var newModel = new PersonModel({\n                name: 'Juri'\n            });\n\n            var copied = newModel.$copy(); //angular.copy(newModel);\n            copied.name = 'Austin';\n\n            $httpBackend.expectPOST('/api/people', JSON.stringify(copied)).respond(200, '');\n\n            copied.$save();\n            $httpBackend.flush();\n        });\n\n        it('calling $destroy on it should submit the copied values', function() {\n            var newModel = new PersonModel({\n                id: 1,\n                name: 'Juri'\n            });\n\n            var copied = newModel.$copy(); // angular.copy(newModel);\n            copied.id = 100;\n\n            $httpBackend.expectDELETE('/api/people/100').respond(200, '');\n\n            copied.$destroy();\n            $httpBackend.flush();\n        });\n\n        it('calling $destroy on a model list entry should not have any effect on the list', function(){\n\n            var modelList = new PersonModel.List([\n                {\n                    id: 1,\n                    name: 'Juri'\n                },\n                {\n                    id: 2,\n                    name: 'Austin'\n                },\n                {\n                    id: 3,\n                    name: 'Tim'\n                }\n            ]);\n\n            var copy = modelList[1].$copy(); // angular.copy(modelList[1]);\n\n            $httpBackend.expectDELETE('/api/people/2').respond(200,'');\n\n            copy.$destroy();\n            $httpBackend.flush();\n\n            expect(modelList.length).toEqual(3);\n        });\n\n    });\n\n    describe('when calling $diff', function() {\n        // Need to use `JSON.parse(JSON.stringify(...))` to clean up `deep-diff` array\n        function toPlainObject(value) {\n            return JSON.parse(JSON.stringify(value));\n        }\n\n        it('should return differences when model changed', function(){\n            var model = new PersonModel({\n                id: 1,\n                name: 'Juri'\n            });\n\n            delete model.id;\n\n            model.name = 'Marat';\n\n            expect(toPlainObject(model.$diff())).toEqual([{\n                kind: 'D',\n                path: ['id'],\n                lhs: 1\n            }, {\n                kind: 'E',\n                path: ['name'],\n                lhs: 'Juri',\n                rhs: 'Marat'\n            }]);\n        });\n    });\n});\n"
  },
  {
    "path": "test/spec/modelUsage.spec.js",
    "content": "/// <reference path=\"../../typings/jasmine/jasmine.d.ts\"/>\n'use strict';\n\n/*\n    High level unit/acceptance tests that\n    simulate the usage of modelFactory from the perspective\n    of a developer/library user, without testing\n    the inner workings of the modelFactory service.\n*/\n\ndescribe('A person model defined using modelFactory', function() {\n    var PersonModel, PersonWithMapModel;\n\n\n    beforeEach(angular.mock.module('modelFactory'));\n\n    describe('with the default configuration', function() {\n\n        beforeEach(function() {\n            angular.module('test-module', ['modelFactory'])\n                .factory('PersonModel', function($modelFactory) {\n                    return $modelFactory('/api/people', {\n                        actions: {\n                            '$customDelete': {\n                                method: 'DELETE',\n                                url: 'customDelete/{id}'\n                            }\n                        }\n                    });\n                })\n                .factory('PersonWithMapModel', function($modelFactory) {\n                    return $modelFactory('/api/peoplemodified', {\n                        pk: 'fooId'\n                    });\n                });\n        });\n\n        beforeEach(angular.mock.module('test-module'));\n\n        beforeEach(inject(function(_PersonModel_, _PersonWithMapModel_) {\n            PersonModel = _PersonModel_;\n            PersonWithMapModel = _PersonWithMapModel_;\n        }));\n\n        describe('when creating a new instance using the \"new\" keyword', function() {\n            var theModel;\n\n            beforeEach(function() {\n                theModel = new PersonModel();\n            });\n\n            it('we should get a proper instance', function() {\n                expect(theModel).toBeDefined();\n            });\n\n            it('should have a $save function', function() {\n                expect(theModel.$save).toBeDefined();\n            });\n\n        });\n\n        describe('when using the list helper', function() {\n            var modelList;\n\n            beforeEach(function() {\n                modelList = new PersonModel.List([{\n                    name: 'Juri'\n                }]);\n            });\n\n            it('should instantiate a new model list with some predefined objects', function() {\n                expect(modelList).toBeDefined();\n                expect(modelList.length).toEqual(1);\n            });\n\n            it('should contain wrapped model objects', function() {\n                expect(modelList[0] instanceof PersonModel).toBeTruthy();\n            });\n\n            // TODO this doesn't work right now...should it??\n            it('should wrap newly added JavaScript objects', function() {\n                modelList.push({\n                    name: 'Tom'\n                });\n\n                expect(modelList[1] instanceof PersonModel).toBeTruthy();\n            });\n\n            it('should account for Array.push(obj1, obj2,...) API; all passed obj should be wrapped as models', function() {\n                var newList = new PersonModel.List();\n\n                // act\n                newList.push({\n                    name: 'Juri'\n                }, {\n                    name: 'Austin'\n                });\n\n                // assert\n                expect(newList.length).toEqual(2);\n                expect(newList[0] instanceof PersonModel).toBeTruthy();\n                expect(newList[1] instanceof PersonModel).toBeTruthy();\n            });\n\n            it('should allow to define an empty list', function() {\n                var newEmptyList = new PersonModel.List();\n                expect(newEmptyList).toBeDefined();\n                expect(newEmptyList.length).toEqual(0);\n            });\n\n            it('should allow to add elements on a previously empty model list collection', function() {\n                var newList = new PersonModel.List();\n\n                newList.push({\n                    name: 'Juri'\n                });\n                expect(newList.length).toEqual(1);\n                expect(newList[0] instanceof PersonModel).toBeTruthy(); // wrapping should still work\n            });\n\n            it('should allow to add new models', function() {\n\n                modelList.push({\n                    name: 'Anna'\n                });\n\n                expect(modelList.length).toEqual(2);\n            });\n\n        });\n\n        describe('when calling query()', function() {\n            var $httpBackend,\n                backendListResponse;\n\n            beforeEach(inject(function(_$httpBackend_) {\n                $httpBackend = _$httpBackend_;\n\n                backendListResponse = [{\n                    name: 'Juri'\n                }, {\n                    name: 'Jack'\n                }, {\n                    name: 'Anne'\n                }];\n            }));\n\n            afterEach(function() {\n                $httpBackend.verifyNoOutstandingExpectation();\n                $httpBackend.verifyNoOutstandingRequest();\n            });\n\n            it('should return a list of people', function() {\n                PersonModel.query()\n                    .then(function(peopleList) {\n\n                        expect(peopleList).toBeDefined();\n                        expect(peopleList.length).toEqual(3);\n\n                    });\n\n                $httpBackend.expectGET('/api/people').respond(200, backendListResponse);\n                $httpBackend.flush();\n            });\n\n            it('should properly send parameters', function() {\n                PersonModel.query({\n                    name: 'Juri',\n                    age: 29\n                });\n\n                $httpBackend.expectGET('/api/people?age=29&name=Juri').respond(200, backendListResponse);\n                $httpBackend.flush();\n            });\n\n        });\n\n        describe('when calling get(..)', function() {\n            var $httpBackend;\n\n            beforeEach(inject(function(_$httpBackend_) {\n                $httpBackend = _$httpBackend_;\n\n                $httpBackend\n                    .whenGET('/api/people/123')\n                    .respond({\n                        id: 123,\n                        name: 'Juri'\n                    });\n            }));\n\n            afterEach(function() {\n                $httpBackend.verifyNoOutstandingExpectation();\n                $httpBackend.verifyNoOutstandingRequest();\n            });\n\n\n            it('should return the requested resource by its id (as number)', function() {\n                PersonModel.get(123)\n                    .then(function(theFetchedPerson) {\n                        expect(theFetchedPerson).toBeDefined();\n                        expect(theFetchedPerson.name).toEqual('Juri');\n                    });\n\n                $httpBackend.expectGET('/api/people/123');\n                $httpBackend.flush();\n            });\n\n            it('should return the requested resource by its id (as string)', function() {\n                PersonModel.get('123')\n                    .then(function(theFetchedPerson) {\n                        expect(theFetchedPerson).toBeDefined();\n                        expect(theFetchedPerson.name).toEqual('Juri');\n                    });\n\n                $httpBackend.expectGET('/api/people/123');\n                $httpBackend.flush();\n            });\n\n            it('should allow to add additional query params', function(){\n                PersonModel.get('123', { age: 29 });\n\n                $httpBackend.expectGET('/api/people/123?age=29').respond({\n                    id: 1,\n                    age: 29\n                });\n                $httpBackend.flush();\n            });\n\n            xit('should return the requested resource by its id when passing it as object', function() {\n                PersonModel.get({\n                    id: 123\n                });\n\n                $httpBackend.expectGET('/api/people/123');\n                $httpBackend.flush();\n            });\n\n        });\n\n        describe('when calling $update()', function() {\n\n            it('should update the existing model properties with the new ones', function() {\n                var newModel = new PersonModel({\n                    name: null\n                });\n\n                var newModelUpdate = new PersonModel({\n                    name: 'elec29a',\n                    language: {\n                      de: 'hallo'\n                    }\n                });\n\n                //act\n                newModel.$update(newModelUpdate);\n\n                expect(newModel.name).toEqual('elec29a');\n                expect(newModel.language).toBeDefined();\n                expect(newModel.language.de).toEqual('hallo');\n            });\n\n        });\n\n        describe('when calling $save()', function() {\n            var $httpBackend;\n\n            beforeEach(inject(function(_$httpBackend_) {\n                $httpBackend = _$httpBackend_;\n            }));\n\n            afterEach(function() {\n                $httpBackend.verifyNoOutstandingExpectation();\n                $httpBackend.verifyNoOutstandingRequest();\n            });\n\n            it('should execute a POST when we have a new model', function() {\n                var newModel = new PersonModel({\n                    name: 'Juri'\n                });\n\n                $httpBackend.expectPOST('/api/people', JSON.stringify(newModel)).respond(200, '');\n\n                // act\n                newModel.$save();\n                $httpBackend.flush();\n            });\n\n            it('should execute a PUT when we have an existing model', function() {\n                var newModel = new PersonModel({\n                    id: 123,\n                    name: 'Juri'\n                });\n\n                $httpBackend.expectPUT('/api/people/123', JSON.stringify(newModel)).respond(200, '');\n\n                // act\n                newModel.$save();\n                $httpBackend.flush();\n            });\n\n            it('should update the entry with the new results from the server', function() {\n\n                var children = {\n                    count: 1\n                };\n\n                var newModel = new PersonModel({\n                    name: 'Juri',\n                    kids: children\n                });\n\n                $httpBackend.expectPOST('/api/people', JSON.stringify(newModel)).respond(200, JSON.stringify({\n                    id: 12,\n                    name: 'Juri Strumpflohner',\n                    kids: { count: 99 }\n                }));\n\n                //act\n                newModel.$save();\n                $httpBackend.flush();\n\n                expect(newModel.id).toEqual(12);\n                expect(newModel.name).toEqual('Juri Strumpflohner');\n\n                // Make sure object references are not lost\n                expect(newModel.kids).toBe(children);\n                expect(children.count).toEqual(99);\n            });\n\n            it('Should overwrite array properties with the returned server version on update', function() {\n\n                // Set PersonModel object with an array property\n                var people = [];\n                people.push(new PersonModel({\n                        name: 'Ryan'\n                    }\n                ));\n                people.push(new PersonModel({\n                        name: 'Austin'\n                    }\n                ));\n                var newModel = new PersonModel({\n                    friends: people\n                });\n\n                // Create a changed array to return which has an extra element\n                var sender = people.slice().reverse();\n                sender.push(new PersonModel( { name: 'Juri'}));\n\n                $httpBackend.expectPOST('/api/people', JSON.stringify(newModel)).respond(200, JSON.stringify({\n                    friends: sender\n                }));\n\n                //act\n                newModel.$save();\n                $httpBackend.flush();\n\n                // Arrays should be exactly as returned\n                expect(newModel.friends.length).toBe(3);\n                expect(newModel.friends[1].name).toBe('Ryan');\n            });\n\n            it('on a copied model it should sent back the copied model data', function(){\n                var newModel = new PersonModel({\n                    name: 'Juri'\n                });\n\n                var copied = angular.copy(newModel);\n                copied.name = 'Austin'; //change something in the clone\n\n                $httpBackend.expectPOST('/api/people', JSON.stringify(copied)).respond(200, '');\n\n\n                copied.$save();\n                $httpBackend.flush();\n            });\n\n            it('should not loose $$array reference when updating existing model', function (){\n                var list = new PersonModel.List([\n                    {\n                        id: 1,\n                        name: 'Juri'\n                    }\n                ]);\n\n                var aPerson = new PersonModel({\n                    name: 'Jack'\n                });\n\n                aPerson.$save()\n                    .then(function() {\n                        // add to list\n                        list.push(aPerson);\n                    });\n                $httpBackend.expectPOST('/api/people').respond(200, JSON.stringify({ id: 123, name: 'Jack' }));\n                $httpBackend.flush();\n\n                // save again\n                aPerson.$save();\n                $httpBackend.expectPUT('/api/people/123').respond(200, JSON.stringify({ id: 123, name: 'Jack'}));\n                $httpBackend.flush();\n\n                // now delete\n                aPerson.$destroy();\n                $httpBackend.expectDELETE('/api/people/123').respond(200, '');\n                $httpBackend.flush();\n\n                expect(list.length).toBe(1);\n            });\n\n        });\n\n        describe('when calling $rollback', function() {\n\n            it('should revert to the previous values of the object', function() {\n                var newModel = new PersonModel({\n                    name: 'Juri'\n                });\n\n                // act\n                newModel.name = 'Jack';\n                newModel.$rollback();\n\n                expect(newModel.name).toEqual('Juri');\n            });\n\n            xit('should NOT revert to the old values after an entity has been persisted with $save', inject(function($httpBackend) {\n                var newModel = new PersonModel({\n                    name: 'Juri'\n                });\n\n                newModel.name = 'Jack';\n\n                // persist it\n                newModel.$save();\n\n                $httpBackend\n                    .expectPOST('/api/people')\n                    .respond(200, JSON.stringify({\n                        id: 1,\n                        name: 'Jack'\n                    }));\n                $httpBackend.flush();\n\n                // act\n                newModel.$rollback();\n\n                // assert\n                expect(newModel.name).toEqual('Jack'); // there is nothing to revert 'cause the model is fresh from the server'\n            }));\n\n        });\n\n        describe('when deleting an object', function() {\n            var $httpBackend;\n\n            beforeEach(inject(function(_$httpBackend_) {\n                $httpBackend = _$httpBackend_;\n            }));\n\n            afterEach(function() {\n                $httpBackend.verifyNoOutstandingExpectation();\n                $httpBackend.verifyNoOutstandingRequest();\n            });\n\n\n            it('should properly execute a DELETE request', function() {\n                var theModel = new PersonModel({\n                    id: 1234\n                });\n\n                // act\n                theModel.$destroy();\n\n                $httpBackend.expectDELETE('/api/people/1234').respond(200, '');\n                $httpBackend.flush();\n            });\n\n            it('should not include any data in the request body', function(){\n                var theModel = new PersonModel({\n                    id: 1234,\n                    name: 'Juri',\n                    age: 30\n                });\n\n                // act\n                theModel.$destroy();\n\n                $httpBackend.expect('DELETE', '/api/people/1234', null).respond(200, '');\n                $httpBackend.flush();\n            });\n\n            it('should not include any data in the request body for custom endpoints', function(){\n                var theModel = new PersonModel({\n                    id: 1234,\n                    name: 'Juri',\n                    age: 30\n                });\n\n                // act\n                theModel.$customDelete();\n\n                $httpBackend.expect('DELETE', '/api/people/customDelete/1234', null).respond(200, '');\n                $httpBackend.flush();\n            });\n\n            it('should properly execute a correct DELETE request with a different PK name', function(){\n                var theModel = new PersonWithMapModel({\n                    fooId: 1234\n                });\n\n                // act\n                theModel.$destroy();\n\n                $httpBackend.expectDELETE('/api/peoplemodified/1234').respond(200, '');\n                $httpBackend.flush();\n            });\n\n            it('should remove the deleted object from a model list when the deletion succeeds', function() {\n                var modelList = new PersonModel.List([{\n                    id: 1,\n                    name: 'Juri'\n                }, {\n                    id: 2,\n                    name: 'Jack'\n                }, {\n                    id: 3,\n                    name: 'Austin'\n                }]);\n\n                // act\n                var modelToDelete = modelList[1];\n                modelToDelete.$destroy();\n\n                // assert\n                $httpBackend.expectDELETE('/api/people/2').respond(200, '');\n                $httpBackend.flush();\n\n                expect(modelList.length).toEqual(2);\n                expect(modelList[0].id).toEqual(1);\n                expect(modelList[1].id).toEqual(3);\n            });\n\n            it('should remove the deleted object even if the list order changed', function() {\n                var modelList = new PersonModel.List([{\n                    id: 1,\n                    name: 'Juri'\n                }, {\n                    id: 2,\n                    name: 'Otto'\n                }, {\n                    id: 3,\n                    name: 'Austin'\n                }]);\n\n                var modelToDelete = modelList[1];\n\n                // resort the list s.t. the array indices change..\n                modelList.sort(function(a, b){\n                    return a.name.localeCompare(b.name);\n                });\n\n                // act\n                modelToDelete.$destroy();\n\n                // should still delete \"Otto\"\n                $httpBackend.expectDELETE('/api/people/2').respond(200, '');\n                $httpBackend.flush();\n\n                expect(modelList.length).toEqual(2);\n            });\n\n            it('should also properly remove an object that has just been added to the list before', function(){\n\n                var modelList = new PersonModel.List([{\n                    id: 1,\n                    name: 'Juri'\n                }]);\n\n\n                // save a new model through the $save function\n                var newModel = new PersonModel({ name: 'Tom' });\n                newModel.$save()\n                    .then(function(){\n                        // add it to the overall collection\n                        modelList.push(newModel);\n\n                        // act: delete the newly added model again\n                        modelList[1].$destroy();\n                    });\n\n                $httpBackend.expectPOST('/api/people').respond(200, JSON.stringify({ id: 111, name: 'Tom'}));\n                $httpBackend.expectDELETE('/api/people/111').respond(200, '');\n                $httpBackend.flush();\n\n                // I'd expect that it is properly removed from it\n                expect(modelList.length).toBe(1);\n                expect(modelList[0].name).toEqual('Juri');\n            });\n\n            it('should also remove deleted models that have a different PK name', function(){\n                var modelList = new PersonWithMapModel.List([{\n                    fooId: 112,\n                    name: 'Juri'\n                }]);\n\n                //act\n                modelList[0].$destroy();\n\n                $httpBackend.expectDELETE('/api/peoplemodified/112').respond(200, '');\n                $httpBackend.flush();\n\n                expect(modelList.length).toBe(0);\n            });\n\n            it('should NOT remove the deleted object from a model list when the deletion fails', function() {\n                var modelList = new PersonModel.List([{\n                    id: 1,\n                    name: 'Juri'\n                }, {\n                    id: 2,\n                    name: 'Jack'\n                }, {\n                    id: 3,\n                    name: 'Austin'\n                }]);\n\n                // act\n                modelList[1].$destroy();\n\n\n                $httpBackend.expectDELETE('/api/people/2').respond(500, '');\n                $httpBackend.flush();\n\n                expect(modelList.length).toEqual(3);\n            });\n\n        });\n\n    });\n\n    describe('with defaults', function() {\n\n        beforeEach(function() {\n            angular.module('test-module', ['modelFactory'])\n                .factory('PersonModel', function($modelFactory) {\n                    return $modelFactory('/api/people', {\n                        defaults: {\n                            age: 18 //stupid example I know :)\n                        }\n                    });\n                });\n        });\n\n        beforeEach(angular.mock.module('test-module'));\n\n        beforeEach(inject(function(_PersonModel_) {\n            PersonModel = _PersonModel_;\n        }));\n\n        it('should have them properly set when instantiating a new empty object', function() {\n            var personWithDefaults = new PersonModel();\n\n            expect(personWithDefaults.age).toEqual(18);\n        });\n\n        it('should use the defaults when creating an object with some data', function() {\n            var personWithDefaults = new PersonModel({\n                name: 'Juri'\n            });\n\n            expect(personWithDefaults.age).toEqual(18);\n        });\n\n        it('should set the defaults when creating a list', function() {\n            var personWithDefaultsList = new PersonModel.List([{\n                name: 'Juri'\n            }]);\n\n            expect(personWithDefaultsList[0].age).toEqual(18);\n        });\n\n        it('should not overwrite with the default when passing a value for it', function() {\n            var personWithDefaults = new PersonModel({\n                name: 'Juri',\n                age: 29\n            });\n\n            expect(personWithDefaults.age).toEqual(29);\n        });\n\n    });\n\n    describe('backend URL resolution', function() {\n        var $httpBackend;\n\n        beforeEach(function() {\n          angular.module('test-module', ['modelFactory'])\n              .factory('PersonModel', function($modelFactory) {\n                  return $modelFactory('/api/people', {\n                      actions: {\n\n                          // static\n                          queryChildren: {\n                              url: 'children',\n                              isArray: true\n                          },\n\n                          getById: {\n                              url: 'child/{id}/some/subpath'\n                          },\n\n                          getByName: {\n                              url: 'child/{name}/some/subpath'\n                          },\n\n                          deleteLink: {\n                              method: 'DELETE',\n                              url: 'path/{id}/links/{linkId}'\n                          },\n\n                          // instance function\n                          '$serverCopy': {\n                              method: 'POST',\n                              url: 'copy/{name}'\n                          },\n\n                          '$customUpdate': {\n                              method: 'PUT',\n                              url: 'update/{name}'\n                          }\n\n                      }\n                  });\n              });\n        });\n\n        beforeEach(angular.mock.module('test-module'));\n\n        beforeEach(inject(function(_PersonModel_, _$httpBackend_) {\n            PersonModel = _PersonModel_;\n            $httpBackend = _$httpBackend_;\n        }));\n\n        it('should work with GET and id variable', function(){\n            PersonModel.getById({ id: 123 });\n\n            $httpBackend.expectGET('/api/people/child/123/some/subpath').respond(200, []);\n            $httpBackend.flush();\n        });\n\n\n        it('should properly convert a static DELETE request', function(){\n            PersonModel.deleteLink({ id: 123, linkId: '111' });\n\n            $httpBackend.expectDELETE('/api/people/path/123/links/111').respond(200, []);\n            $httpBackend.flush();\n        });\n\n        it('should work with GET and name variable', function(){\n            PersonModel.getByName({ name: 'juri' });\n\n            $httpBackend.expectGET('/api/people/child/juri/some/subpath').respond(200, []);\n            $httpBackend.flush();\n        });\n\n        it('should work with POST and name variable', function(){\n            var person = new PersonModel({\n              name: 'juri'\n            });\n\n            person.$serverCopy();\n\n            $httpBackend.expectPOST('/api/people/copy/juri').respond(200, []);\n            $httpBackend.flush();\n        });\n\n        it('should work with PUT and name variable', function(){\n            var person = new PersonModel({\n              name: 'juri'\n            });\n\n            person.$customUpdate();\n\n            $httpBackend.expectPUT('/api/people/update/juri').respond(200, []);\n            $httpBackend.flush();\n        });\n\n    });\n\n    describe('with custom actions', function() {\n        var $httpBackend;\n\n        beforeEach(function() {\n            angular.module('test-module', ['modelFactory'])\n                .factory('PersonModel', function($modelFactory) {\n                    return $modelFactory('/api/people', {\n                        actions: {\n\n                            // static\n                            queryChildren: {\n                                url: 'children',\n                                isArray: true\n                            },\n\n                            // instance function\n                            '$serverCopy': {\n                                method: 'POST',\n                                url: 'copy'\n                            }\n\n                        }\n                    });\n                });\n        });\n\n        beforeEach(angular.mock.module('test-module'));\n\n        beforeEach(inject(function(_PersonModel_, _$httpBackend_) {\n            PersonModel = _PersonModel_;\n            $httpBackend = _$httpBackend_;\n        }));\n\n        it('should correctly call the defined url', function() {\n            PersonModel.queryChildren();\n            $httpBackend.expectGET('/api/people/children').respond(200, []);\n            $httpBackend.flush();\n        });\n\n        it('should allow to specify query parameters', function() {\n\n            PersonModel.queryChildren({\n                type: 'minor'\n            });\n\n            $httpBackend.expectGET('/api/people/children?type=minor').respond(200, '');\n            $httpBackend.flush();\n        });\n\n        it('should wrap the returned objects', function() {\n\n            PersonModel.queryChildren()\n                .then(function(result) {\n                    expect(result.length).toBe(1);\n                    expect(result[0] instanceof PersonModel).toBeTruthy(); // check whether it's a model\n                });\n\n            $httpBackend.expectGET('/api/people/children').respond(200, [{\n                type: 'minor',\n                name: 'Juri'\n            }]);\n            $httpBackend.flush();\n        });\n\n        it('should correctly invoke the custom model instance function', function() {\n            var model = new PersonModel({\n                name: 'Juri'\n            });\n\n            $httpBackend.expectPOST('/api/people/copy').respond(200, '');\n            // act\n            model.$serverCopy();\n            $httpBackend.flush();\n        });\n\n    });\n\n    describe('using the isArray property', function(){\n        var AddressModel, $httpBackend;\n\n        beforeEach(function() {\n            angular.module('test-module', ['modelFactory'])\n                .factory('AddressModel', function($modelFactory) {\n                    return $modelFactory('/api/addresses', {\n                        actions: {\n                            'query': {\n                                isArray: false\n                            },\n\n                            'myCustomAction': {\n                                url: 'customAction',\n                                isArray: false\n                            }\n                        }\n                    });\n                });\n        });\n\n        beforeEach(angular.mock.module('test-module'));\n\n        beforeEach(inject(function(_AddressModel_, _$httpBackend_) {\n            AddressModel = _AddressModel_;\n            $httpBackend = _$httpBackend_;\n        }));\n\n        afterEach(function() {\n            $httpBackend.verifyNoOutstandingExpectation();\n            $httpBackend.verifyNoOutstandingRequest();\n        });\n\n        it('when setting it to false should accept non-array responses', function(){\n            AddressModel.query()\n                .then(function(result){\n                    expect(result.rows.length).toBe(1);\n                    expect(result.numRecords).toBe(1);\n                });\n\n            $httpBackend.expectGET('/api/addresses').respond(200, { rows: [{ id: 1, street: 'test'}], numRecords: 1 });\n            $httpBackend.flush();\n        });\n\n        it('when setting it to false on a custom action should accept non-array responses', function(){\n            AddressModel.myCustomAction()\n                .then(function(result){\n                    expect(result.rows.length).toBe(1);\n                    expect(result.numRecords).toBe(1);\n                });\n\n            $httpBackend.expectGET('/api/addresses/customAction').respond(200, { rows: [{ id: 1, street: 'test'}], numRecords: 1 });\n            $httpBackend.flush();\n        });\n\n    });\n\n    describe('when backend respond with metadata', function() {\n        var StadiumModel, $httpBackend;\n\n        beforeEach(function() {\n            angular.module('test-module', ['modelFactory'])\n                .factory('StadiumModel', function($modelFactory) {\n                    return $modelFactory('/api/stadiums', {\n                        actions: {\n                            'base': {\n                                afterRequest: function(response) {\n                                    var transfrom = response.data;\n                                    delete response.data;\n                                    transfrom.meta = response;\n                                    return transfrom;\n                                }\n                            },\n                            'query': {\n                                afterRequest: function(response) {\n                                    var transfrom = response.data;\n                                    transfrom.paginator = response.paginator;\n                                    return transfrom;\n                                }\n                            },\n                        }\n                    });\n                });\n        });\n\n        beforeEach(angular.mock.module('test-module'));\n\n        beforeEach(inject(function(_StadiumModel_, _$httpBackend_) {\n            StadiumModel = _StadiumModel_;\n            $httpBackend = _$httpBackend_;\n        }));\n\n        afterEach(function() {\n            $httpBackend.verifyNoOutstandingExpectation();\n            $httpBackend.verifyNoOutstandingRequest();\n        });\n\n        it('when backend respond with pagination ', function() {\n            StadiumModel.query()\n                .then(function(result) {\n                    expect(result.length).toBe(3);\n                    expect(result.paginator.limit).toBe(3);\n                });\n\n            $httpBackend.expectGET('/api/stadiums').respond(200, {\n                'data': [{\n                    'title': 'Accusantium rem magni accusantium placeat.'\n                }, {\n                    'title': 'Maxime ut eum pariatur magni quia iusto.'\n                }, {\n                    'title': 'Sapiente perferendis consectetur ut ipsa consectetur.'\n                }],\n                'paginator': {\n                    'totalCount': 30,\n                    'totalPage': 10,\n                    'currentPage': 1,\n                    'limit': 3\n                }\n            });\n            $httpBackend.flush();\n        });\n        it('when backend respond with metadata ', function() {\n            StadiumModel.get(1)\n                .then(function(result) {\n                    // console.log(result.meta);\n                    expect(result.meta.status.code).toBe(1000);\n                    // expect(result.paginator.limit).toBe(3);\n                });\n\n            $httpBackend.expectGET('/api/stadiums/1').respond(200, {\n                'data': {\n                    'title': 'Accusantium rem magni accusantium placeat.'\n                },\n                'status': {\n                    'code': 1000\n                }\n            });\n            $httpBackend.flush();\n        });\n\n    });\n\n    describe('using the map property',function(){\n        var PersonModel, $httpBackend;\n\n         beforeEach(function() {\n            angular.module('test-module', ['modelFactory'])\n                .factory('PersonModel', function($modelFactory) {\n                    return $modelFactory('/api/people',{\n                        map:{\n                            'id' : 'personId',\n                            'address' :'street'\n                        }\n                    });\n                });\n        });\n\n        beforeEach(angular.mock.module('test-module'));\n\n        beforeEach(inject(function(_PersonModel_,_$httpBackend_) {\n            PersonModel = _PersonModel_;\n            $httpBackend = _$httpBackend_;\n        }));\n\n        afterEach(function() {\n            $httpBackend.verifyNoOutstandingExpectation();\n            $httpBackend.verifyNoOutstandingRequest();\n        });\n\n        it('setting map, transpose `personId` to `id` and `street` to `address` on our instance', function(){\n            PersonModel.query()\n                .then(function(result){\n                    expect(result[0].id).toBe(1);\n                    expect(result[0].address).toBe('test');\n                });\n\n            $httpBackend.expectGET('/api/people').respond(200, [{ personId: 1, street: 'test'}]);\n            $httpBackend.flush();\n        });\n\n    });\n\n    describe('when the server returns an error response',function(){\n        var PersonModel, $httpBackend;\n\n         beforeEach(function() {\n            angular.module('test-module', ['modelFactory'])\n                .factory('PersonModel', function($modelFactory) {\n                    return $modelFactory('/api/people');\n                });\n        });\n\n        beforeEach(angular.mock.module('test-module'));\n\n        beforeEach(inject(function(_PersonModel_,_$httpBackend_) {\n            PersonModel = _PersonModel_;\n            $httpBackend = _$httpBackend_;\n        }));\n\n        afterEach(function() {\n            $httpBackend.verifyNoOutstandingExpectation();\n            $httpBackend.verifyNoOutstandingRequest();\n        });\n\n        it('should not call the success callback', function(){\n            PersonModel.query()\n                .then(function(){\n                    // this should never be executed\n                    expect(false).toBeTruthy();\n                });\n\n            $httpBackend.expectGET('/api/people').respond(500);\n            $httpBackend.flush();\n        });\n\n        it('should be passed to the error promise', function(){\n            PersonModel.query()\n                .then(function(){\n                })\n                .catch(function(response){\n                    expect(response).toBeDefined();\n                    expect(response.status).toEqual(500);\n                });\n\n            $httpBackend.expectGET('/api/people').respond(500);\n            $httpBackend.flush();\n        });\n\n        it('should be possible to use the error callback instead of catch', function(){\n            PersonModel.query()\n                .then(function(){\n                }, function(response){\n                    expect(response).toBeDefined();\n                    expect(response.status).toEqual(500);\n                });\n\n            $httpBackend.expectGET('/api/people').respond(500);\n            $httpBackend.flush();\n        });\n\n    });\n\n    describe('using the model instance features',function(){\n        var Contact, Phone, Address;\n\n         beforeEach(function() {\n            angular.module('test-module', ['modelFactory'])\n                .factory('Contact', function($modelFactory, Address) {\n                    var model = $modelFactory('contacts', {\n                        pk: 'Id',\n                        map: {\n                            Addresses: Address.List\n                        },\n                        defaults: {\n                            GivenName: '',\n                            FamilyName: '',\n                            Addresses: []\n                        },\n                        instance: {\n                            addAddress: function (options) {\n                                this.Addresses.push(new Address(options));\n                            },\n                            removeAddress: function (address) {\n                                this.Addresses.splice(this.Addresses.indexOf(address), 1);\n                            }\n                        }\n                    });\n\n                    return model;\n                })\n                .factory('Address', function ($modelFactory, Phone) {\n                    var model = $modelFactory('Address', {\n                        pk: 'Id',\n                        map: {\n                            Phones: Phone.List\n                        },\n                        defaults: {\n                            Line1: '',\n                            Phones: []\n                        },\n                        instance: {\n                            addPhone: function (options) {\n                                this.Phones.push(new Phone(options));\n                            },\n                            removePhone: function (phone) {\n                                this.Phones.splice(this.Phones.indexOf(phone), 1);\n                            }\n                        }\n                    });\n\n                    return model;\n                })\n                .factory('Phone', function($modelFactory){\n                    return $modelFactory('', {\n                        pk: 'Id',\n                        defaults: {\n                            Type: 'Mobile',\n                            PhoneCode: '1',\n                            Number: '',\n                            Extension: '',\n                            IsPrimary: false\n                        }\n                    });\n                });\n        });\n\n        beforeEach(angular.mock.module('test-module'));\n\n        beforeEach(inject(function(_Contact_, _Phone_, _Address_) {\n            Contact = _Contact_;\n            Phone = _Phone_;\n            Address = _Address_;\n        }));\n\n        it('should work with addresses',function(){\n            var address = new Address();\n            expect(address).toBeDefined();\n\n            address.addPhone();\n\n            var anotherAddress = new Address();\n            anotherAddress.addPhone();\n\n            expect(address.Phones.length).toEqual(1);\n            expect(anotherAddress.Phones.length).toEqual(1);\n        });\n\n        it('multiple nesting of Models and Model collections through map', function(){\n            var contact = new Contact();\n\n            contact.addAddress({ Line1: '123 Main St'});\n\n            expect(contact.Addresses.length).toEqual(1);\n            expect(contact.Addresses[0].Line1).toEqual('123 Main St');\n            expect(contact.Addresses[0].Phones.length).toEqual(0);\n        });\n\n    });\n\n    describe('when afterRequest returns data', function () {\n        var DummyGetModel, $httpBackend;\n        beforeEach(function () {\n            angular.module('test-module', ['modelFactory'])\n                .factory('DummyGetModel', function ($modelFactory) {\n                    return $modelFactory('/test/get/afterRequestTest', {\n                        actions: {\n                            get: {\n                                wrap:false,\n                                afterRequest: function (res) {\n                                    return res.data.newData;\n                                }\n                            }\n                        }\n                    });\n                });\n        });\n\n        beforeEach(angular.mock.module('test-module'));\n\n        beforeEach(inject(function (_DummyGetModel_, _$httpBackend_) {\n            DummyGetModel = _DummyGetModel_;\n            $httpBackend = _$httpBackend_;\n        }));\n\n        it('should reset data if returns null', function(){\n            DummyGetModel.get(1).then(function(data){\n               expect(data).toBeNull();\n            });\n\n            $httpBackend.expectGET('/test/get/afterRequestTest/1').respond(200, {\n                'data': {\n                    newData: null\n                }\n            });\n            $httpBackend.flush();\n        });\n\n        it('should reset data if returns 0', function(){\n            DummyGetModel.get(1).then(function(data){\n                expect(data).toBe(0);\n            });\n\n            $httpBackend.expectGET('/test/get/afterRequestTest/1').respond(200, {\n                'data': {\n                    newData: 0\n                }\n            });\n            $httpBackend.flush();\n        });\n\n        it('should reset data if returns \"\"', function(){\n            DummyGetModel.get(1).then(function(data){\n                expect(data).toBe('');\n            });\n\n            $httpBackend.expectGET('/test/get/afterRequestTest/1').respond(200, {\n                'data': {\n                    newData: ''\n                }\n            });\n            $httpBackend.flush();\n        });\n\n        it('should not reset data if returns undefined', function(){\n            DummyGetModel.get(1).then(function(data){\n                expect(data.newData).toBe();\n            });\n\n            $httpBackend.expectGET('/test/get/afterRequestTest/1').respond(200, {\n                'data': {\n                    newData: undefined\n                }\n            });\n            $httpBackend.flush();\n        });\n    });\n\n\n    describe('when action url (or action template url) contains slash', function () {\n        var DummyGetModel, $httpBackend;\n        beforeEach(function () {\n            angular.module('test-module', ['modelFactory'])\n                .factory('DummyGetModel', function ($modelFactory) {\n                    return $modelFactory('/test/get/actionSlashTest', {\n                        actions: {\n                            get: {\n                                url: '{/templateParam,templateParam2}'\n                            },\n                            query: {\n                                url: '/simpleUrl'\n                            }\n                        }\n                    });\n                });\n        });\n\n        beforeEach(angular.mock.module('test-module'));\n\n        beforeEach(inject(function (_DummyGetModel_, _$httpBackend_) {\n            DummyGetModel = _DummyGetModel_;\n            $httpBackend = _$httpBackend_;\n        }));\n\n        it('should build query with template params and no duplicate slashes', function () {\n            DummyGetModel.get({\n                templateParam: '123',\n                templateParam2: '456'\n            });\n\n            $httpBackend.expectGET('/test/get/actionSlashTest/123/456').respond(200);\n            $httpBackend.flush();\n        });\n\n        it('should set pk as query param, if we have path params as template', function () {\n            DummyGetModel.get({\n                templateParam: '123',\n                templateParam2: '456',\n                id: 0\n            });\n\n            $httpBackend.expectGET('/test/get/actionSlashTest/123/456?id=0').respond(200);\n            $httpBackend.flush();\n        });\n\n        it('should query data without duplicate slash', function () {\n            DummyGetModel.query();\n\n            $httpBackend.expectGET('/test/get/actionSlashTest/simpleUrl').respond(200);\n            $httpBackend.flush();\n        });\n\n        it('should query data without duplicate slash and add query params', function () {\n            DummyGetModel.query({\n                query: 'param'\n            });\n\n            $httpBackend.expectGET('/test/get/actionSlashTest/simpleUrl?query=param').respond(200);\n            $httpBackend.flush();\n        });\n    });\n\n    describe('regression test',function(){\n        var Contact, Phone, Address;\n\n         beforeEach(function() {\n            angular.module('test-module', ['modelFactory'])\n                .factory('Contact', function($modelFactory, Address) {\n                    var model = $modelFactory('contacts', {\n                        pk: 'Id',\n                        map: {\n                            Addresses: Address.List\n                        },\n                        defaults: {\n                            GivenName: '',\n                            FamilyName: '',\n                            Addresses: []\n                        },\n                        instance: {\n                            addAddress: function (options) {\n                                this.Addresses.push(new Address(options));\n                            },\n                            removeAddress: function (address) {\n                                this.Addresses.splice(this.Addresses.indexOf(address), 1);\n                            }\n                        }\n                    });\n\n                    return model;\n                })\n                .factory('Address', function ($modelFactory, Phone) {\n                    var model = $modelFactory('address', {\n                        pk: 'Id',\n                        map: {\n                            Phone: Phone\n                        },\n                        defaults: {\n                            Line1: ''\n                        }\n                    });\n\n                    return model;\n                })\n                .factory('Phone', function($modelFactory){\n                    return $modelFactory('phone', {\n                        pk: 'Id',\n                        defaults: {\n                            Type: 'Mobile',\n                            PhoneCode: '1',\n                            Number: '',\n                            Extension: '',\n                            IsPrimary: false\n                        }\n                    });\n                });\n        });\n\n        beforeEach(angular.mock.module('test-module'));\n\n        beforeEach(inject(function(_Contact_, _Phone_, _Address_) {\n            Contact = _Contact_;\n            Phone = _Phone_;\n            Address = _Address_;\n        }));\n\n        it('should work with addresses',function(){\n            var contact = new Contact();\n\n            contact.addAddress({\n                Line1: '123 Main St',\n                Phone: {\n                    Number: '112233'\n                }\n            });\n\n            expect(contact.Addresses.length).toEqual(1);\n            expect(contact.Addresses[0].Line1).toEqual('123 Main St');\n            expect(contact.Addresses[0].Phone.Number).toEqual('112233');\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/regression.spec.js",
    "content": "'use strict';\n\n/*\n  Regression tests that emerge from GitHub issues. Their main purpose is to identify\n  a bug and make sure it won't be introduced any more\n*/\n\ndescribe('A person model defined using modelFactory', function() {\n    var Department;\n    var $httpBackend;\n\n    beforeEach(angular.mock.module('modelFactory'));\n\n    beforeEach(function() {\n        angular.module('test-module', ['modelFactory'])\n            .factory('Department', function($modelFactory) {\n                var model = $modelFactory('department', {\n                    pk: 'ID',\n\n                    actions: {\n                        // base: {\n                        //     override: true\n                        // },\n                        'lookup': {\n                            url: 'Lookups',\n                            cache: true,\n                            method: 'GET',\n                            params: {\n                                Range: 'All'\n                            }\n                        }\n                    }\n                });\n\n                return model;\n            });\n    });\n\n    beforeEach(angular.mock.module('test-module'));\n\n    beforeEach(inject(function(_$httpBackend_, _Department_) {\n        $httpBackend = _$httpBackend_;\n        Department = _Department_;\n    }));\n\n    afterEach(function() {\n        $httpBackend.verifyNoOutstandingExpectation();\n        $httpBackend.verifyNoOutstandingRequest();\n    });\n\n    it('should correctly override the defaults with the passed data', function(){\n        var customParam = 'test';\n\n        $httpBackend.expectGET('department/Lookups?Range=' + customParam).respond(200, {\n            id: 1,\n            name: 'Human resources'\n        });\n\n        Department.lookup({ Range: customParam });\n\n        $httpBackend.flush();\n    });\n\n});"
  }
]