agile-proxy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/.bowerrc +3 -0
  3. data/.gitignore +8 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +36 -0
  6. data/.travis.yml +8 -0
  7. data/Gemfile +4 -0
  8. data/Gemfile.lock +267 -0
  9. data/Guardfile +20 -0
  10. data/LICENSE +22 -0
  11. data/README.md +93 -0
  12. data/Rakefile +13 -0
  13. data/agile-proxy.gemspec +50 -0
  14. data/assets/index.html +39 -0
  15. data/assets/ui/app/HttpFlexibleProxyApi.js +31 -0
  16. data/assets/ui/app/app.js +1 -0
  17. data/assets/ui/app/controller/Stubs.js +64 -0
  18. data/assets/ui/app/controller/main.js +12 -0
  19. data/assets/ui/app/directive/AppEnhancedFormElement.js +21 -0
  20. data/assets/ui/app/directive/AppFor.js +16 -0
  21. data/assets/ui/app/directive/AppResponseEditor.js +54 -0
  22. data/assets/ui/app/model/RequestSpec.js +6 -0
  23. data/assets/ui/app/routes.js +10 -0
  24. data/assets/ui/app/service/Dialog.js +49 -0
  25. data/assets/ui/app/service/DomId.js +10 -0
  26. data/assets/ui/app/service/Error.js +7 -0
  27. data/assets/ui/app/service/Stub.js +36 -0
  28. data/assets/ui/app/view/404.html +2 -0
  29. data/assets/ui/app/view/dialog/error.html +10 -0
  30. data/assets/ui/app/view/dialog/yesNo.html +8 -0
  31. data/assets/ui/app/view/responses/editForm.html +78 -0
  32. data/assets/ui/app/view/status.html +1 -0
  33. data/assets/ui/app/view/stubs.html +19 -0
  34. data/assets/ui/app/view/stubs/edit.html +58 -0
  35. data/assets/ui/css/main.css +3 -0
  36. data/bin/agile_proxy +113 -0
  37. data/bower.json +27 -0
  38. data/config.yml +6 -0
  39. data/db.yml +10 -0
  40. data/db/migrations/20140818110800_create_users.rb +9 -0
  41. data/db/migrations/20140818134700_create_applications.rb +10 -0
  42. data/db/migrations/20140818135200_create_request_specs.rb +13 -0
  43. data/db/migrations/20140821115300_create_responses.rb +14 -0
  44. data/db/migrations/20140823082900_add_method_to_request_specs.rb +7 -0
  45. data/db/migrations/20140823083900_rename_request_spec_columns.rb +8 -0
  46. data/db/migrations/20141031072100_add_url_type_to_request_specs.rb +8 -0
  47. data/db/migrations/20141105125600_add_conditions_to_request_specs.rb +7 -0
  48. data/db/migrations/20141106083100_add_username_and_password_to_applications.rb +8 -0
  49. data/db/migrations/20141119143800_add_record_to_applications.rb +7 -0
  50. data/db/migrations/20141119174300_create_recordings.rb +18 -0
  51. data/db/schema.rb +78 -0
  52. data/examples/README.md +1 -0
  53. data/examples/facebook_api.html +59 -0
  54. data/examples/tumblr_api.html +22 -0
  55. data/lib/agile_proxy.rb +8 -0
  56. data/lib/agile_proxy/api/applications.rb +77 -0
  57. data/lib/agile_proxy/api/recordings.rb +52 -0
  58. data/lib/agile_proxy/api/request_specs.rb +85 -0
  59. data/lib/agile_proxy/api/root.rb +41 -0
  60. data/lib/agile_proxy/config.rb +63 -0
  61. data/lib/agile_proxy/handlers/handler.rb +43 -0
  62. data/lib/agile_proxy/handlers/proxy_handler.rb +110 -0
  63. data/lib/agile_proxy/handlers/request_handler.rb +57 -0
  64. data/lib/agile_proxy/handlers/stub_handler.rb +113 -0
  65. data/lib/agile_proxy/mitm.crt +22 -0
  66. data/lib/agile_proxy/mitm.key +27 -0
  67. data/lib/agile_proxy/model/application.rb +20 -0
  68. data/lib/agile_proxy/model/recording.rb +16 -0
  69. data/lib/agile_proxy/model/request_spec.rb +47 -0
  70. data/lib/agile_proxy/model/response.rb +56 -0
  71. data/lib/agile_proxy/model/user.rb +17 -0
  72. data/lib/agile_proxy/proxy_connection.rb +113 -0
  73. data/lib/agile_proxy/route.rb +106 -0
  74. data/lib/agile_proxy/router.rb +99 -0
  75. data/lib/agile_proxy/server.rb +85 -0
  76. data/lib/agile_proxy/servers/api.rb +41 -0
  77. data/lib/agile_proxy/servers/request_spec.rb +30 -0
  78. data/lib/agile_proxy/version.rb +6 -0
  79. data/load_proxy.js +39 -0
  80. data/log/.gitkeep +0 -0
  81. data/spec/common_helper.rb +32 -0
  82. data/spec/fixtures/test-server.crt +15 -0
  83. data/spec/fixtures/test-server.key +15 -0
  84. data/spec/integration/helpers/request_spec_helper.rb +60 -0
  85. data/spec/integration/specs/lib/server_spec.rb +407 -0
  86. data/spec/integration_spec_helper.rb +18 -0
  87. data/spec/spec_helper.rb +39 -0
  88. data/spec/support/test_server.rb +75 -0
  89. data/spec/unit/agile_proxy/api/applications_spec.rb +102 -0
  90. data/spec/unit/agile_proxy/api/common_helper.rb +31 -0
  91. data/spec/unit/agile_proxy/api/recordings_spec.rb +115 -0
  92. data/spec/unit/agile_proxy/api/request_specs_spec.rb +159 -0
  93. data/spec/unit/agile_proxy/handlers/handler_spec.rb +8 -0
  94. data/spec/unit/agile_proxy/handlers/proxy_handler_spec.rb +138 -0
  95. data/spec/unit/agile_proxy/handlers/request_handler_spec.rb +55 -0
  96. data/spec/unit/agile_proxy/handlers/stub_handler_spec.rb +154 -0
  97. data/spec/unit/agile_proxy/model/recording_spec.rb +0 -0
  98. data/spec/unit/agile_proxy/model/request_spec_spec.rb +45 -0
  99. data/spec/unit/agile_proxy/model/response_spec.rb +38 -0
  100. data/spec/unit/agile_proxy/server_spec.rb +88 -0
  101. data/spec/unit/agile_proxy/servers/api_spec.rb +31 -0
  102. data/spec/unit/agile_proxy/servers/request_spec_spec.rb +32 -0
  103. metadata +618 -0
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+ require 'active_record'
4
+ include ActiveRecord::Tasks
5
+ DatabaseTasks.database_configuration = YAML.load_file('config.yml')
6
+ DatabaseTasks.db_dir = 'db'
7
+ DatabaseTasks.migrations_paths = 'db/migrations'
8
+ DatabaseTasks.env = ENV['ENV'] || 'development'
9
+ ActiveRecord::Base.establish_connection(DatabaseTasks.database_configuration[DatabaseTasks.env])
10
+ DatabaseTasks.root = File.dirname(__FILE__)
11
+ Rake::Task.define_task(:environment)
12
+ # other settings...
13
+ load 'active_record/railties/databases.rake'
@@ -0,0 +1,50 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/agile_proxy/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ['Gary Taylor']
6
+ gem.email = ['gary.taylor@hismessages.com']
7
+ gem.description = 'An agile, programmable, controllable proxy server for use standalone or as part of an integration test suite with clients for many languages'
8
+ gem.summary = 'An agile, programmable, controllable flexible proxy server for development or test use'
9
+ gem.homepage = 'https://github.com/garytaylor/agileproxy'
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = 'agile-proxy'
15
+ gem.require_paths = ['lib']
16
+ gem.version = AgileProxy::VERSION
17
+
18
+ gem.add_development_dependency 'rake'
19
+ gem.add_development_dependency 'rspec', '~> 3.1.0'
20
+ gem.add_development_dependency 'rspec-mocks', '~> 3.1.3'
21
+ gem.add_development_dependency 'faraday', '~> 0.9.0'
22
+ gem.add_development_dependency 'poltergeist'
23
+ gem.add_development_dependency 'selenium-webdriver', '~> 2.43.0'
24
+ gem.add_development_dependency 'rack', '~> 1.5.2'
25
+ gem.add_development_dependency 'guard'
26
+ gem.add_development_dependency 'rb-inotify'
27
+ gem.add_development_dependency 'pry'
28
+ gem.add_development_dependency 'cucumber'
29
+ gem.add_development_dependency 'airborne'
30
+ gem.add_development_dependency 'rest-client'
31
+ gem.add_development_dependency 'require_all'
32
+ gem.add_development_dependency 'faker'
33
+ gem.add_development_dependency 'yard'
34
+ gem.add_development_dependency 'simplecov'
35
+ gem.add_runtime_dependency 'eventmachine', '~> 1.0.3'
36
+ gem.add_runtime_dependency 'em-synchrony', '~> 1.0.3'
37
+ gem.add_runtime_dependency 'em-http-request', '~> 1.1.2'
38
+ gem.add_runtime_dependency 'eventmachine_httpserver', '~> 0.2.1'
39
+ gem.add_runtime_dependency 'http_parser.rb', '~> 0.6.0'
40
+ gem.add_runtime_dependency 'multi_json'
41
+ gem.add_runtime_dependency 'thin', '~> 1.6.2'
42
+ gem.add_runtime_dependency 'grape', '~> 0.9.0'
43
+ gem.add_runtime_dependency 'activerecord', '~> 4.1.6'
44
+ gem.add_runtime_dependency 'sqlite3', '~> 1.3.10'
45
+ gem.add_runtime_dependency 'grape-kaminari', '~> 0.1.6'
46
+ gem.add_runtime_dependency 'shoulda-matchers'
47
+ gem.add_runtime_dependency 'flavour_saver'
48
+ gem.add_runtime_dependency 'thor'
49
+ gem.add_runtime_dependency 'rack-parser', '~> 0.6.1'
50
+ end
data/assets/index.html ADDED
@@ -0,0 +1,39 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <script src="ui/bower_components/jquery/dist/jquery.js"></script>
5
+ <script src="ui/bower_components/ace-builds/src-min-noconflict/ace.js"></script>
6
+ <script src="ui/bower_components/angular/angular.js"></script>
7
+ <script src="ui/bower_components/angular-bootstrap/ui-bootstrap.js"></script>
8
+ <script src="ui/bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>
9
+ <script src="ui/bower_components/angular-ui-ace/ui-ace.js"></script>
10
+ <script src="ui/app/app.js"></script>
11
+ <script src="ui/app/model/RequestSpec.js"></script>
12
+ <script src="ui/app/controller/main.js"></script>
13
+ <script src="ui/app/controller/Stubs.js"></script>
14
+ <script src="ui/app/service/Dialog.js"></script>
15
+ <script src="ui/app/service/Stub.js"></script>
16
+ <script src="ui/app/service/DomId.js"></script>
17
+ <script src="ui/app/service/Error.js"></script>
18
+ <script src="ui/app/directive/AppEnhancedFormElement.js"></script>
19
+ <script src="ui/app/directive/AppResponseEditor.js"></script>
20
+ <script src="ui/app/directive/AppFor.js"></script>
21
+ <script src="ui/app/routes.js"></script>
22
+ <script src="ui/bower_components/angular-route/angular-route.js"></script>
23
+ <script src="ui/bower_components/angular-resource/angular-resource.js"></script>
24
+ <script src="ui/bower_components/angular-restmod/dist/angular-restmod-bundle.js"></script>
25
+ <script src="ui/app/AgileProxyApi.js"></script>
26
+ <link rel="stylesheet" href="ui/bower_components/bootstrap/dist/css/bootstrap.css" />
27
+ <link rel="stylesheet" href="ui/bower_components/bootstrap/dist/css/bootstrap-theme.css" />
28
+ <link rel="stylesheet" href="ui/css/main.css" />
29
+ </head>
30
+ <body ng-app="AgileProxy" class="container-fluid">
31
+ <div class="navbar navbar-inverse">
32
+ <a href="#status" class="navbar-brand">Status</a>
33
+ <a href="#stubs" class="navbar-brand">Stubs</a>
34
+ </div>
35
+ <div>
36
+ <ng-view></ng-view>
37
+ </div>
38
+ </body>
39
+ </html>
@@ -0,0 +1,31 @@
1
+ /**
2
+ * API Bound Models for AngularJS
3
+ * @version v1.1.3 - 2014-09-25
4
+ * @link https://github.com/angular-platanus/restmod
5
+ * @author Ignacio Baixas <ignacio@platan.us>
6
+ * @license MIT License, http://www.opensource.org/licenses/MIT
7
+ */
8
+
9
+ (function(angular, undefined) {
10
+ 'use strict';
11
+
12
+ angular.module('restmod').factory('AgileProxyApi', ['restmod', 'inflector', function(restmod, inflector) {
13
+
14
+ return restmod.mixin('DefaultPacker', { // include default packer extension
15
+ $config: {
16
+ style: 'AMS',
17
+ primaryKey: 'id',
18
+ jsonMeta: 'meta',
19
+ jsonLinks: 'links'
20
+ },
21
+
22
+ $extend: {
23
+ // special snakecase to camelcase renaming
24
+ Model: {
25
+ decodeName: inflector.camelize,
26
+ encodeName: function(_v) { return inflector.parameterize(_v, '_'); }
27
+ }
28
+ }
29
+ });
30
+
31
+ }]);})(angular);
@@ -0,0 +1 @@
1
+ angular.module('AgileProxy', ['ui.bootstrap', 'ngRoute', 'ngResource', 'restmod', 'ui.ace']);
@@ -0,0 +1,64 @@
1
+ angular.module('AgileProxy').controller('StubsCtrl', function ($resource, $scope, DialogService, StubService, RequestSpecModel, ErrorService) {
2
+ var selection;
3
+ $scope.selection = {};
4
+ $scope.selectionCount = 0;
5
+ angular.extend($scope, {
6
+ editStub: function (stub) {
7
+ StubService.editStub(stub, $scope).then(function (obj) {
8
+ obj.stub.$save().$then(function (stub) {
9
+ obj.close();
10
+ },
11
+ function (response) {
12
+ ErrorService.serverError(response);
13
+ }
14
+ )
15
+ });
16
+ },
17
+ deleteStub: function (stub) {
18
+ DialogService.yesNo('Delete this stub ?').then(function (response) {
19
+ stub.$delete({id: stub.id});
20
+ });
21
+ },
22
+ addStub: function () {
23
+ StubService.addStub($scope).then(function(obj) {
24
+ obj.stub.$save().$then(function (stub) {
25
+ obj.close();
26
+ },
27
+ function (response) {
28
+ ErrorService.serverError(response);
29
+ }
30
+ );
31
+ });
32
+ },
33
+ updateSelection: function (stub) {
34
+ if (stub.$isSelected) {
35
+ $scope.addToSelection(stub);
36
+ } else {
37
+ $scope.removeFromSelection(stub);
38
+ }
39
+ },
40
+ addToSelection: function (stub) {
41
+ $scope.selection[stub.id] = true;
42
+ $scope.onSelectionChange();
43
+ },
44
+ removeFromSelection: function (stub) {
45
+ delete $scope.selection[stub.id];
46
+ $scope.onSelectionChange();
47
+ },
48
+ deleteSelection: function () {
49
+ var s;
50
+ s = $scope.selection;
51
+ debugger;
52
+
53
+ },
54
+ onSelectionChange: function () {
55
+ $scope.selectionCount = Object.keys($scope.selection).length;
56
+ },
57
+ emptySelection: function () {
58
+
59
+ }
60
+ });
61
+ $scope.requestSpecs = RequestSpecModel.$collection({});
62
+ $scope.requestSpecs.$refresh()
63
+
64
+ });
@@ -0,0 +1,12 @@
1
+ angular.module('AgileProxy').controller('MainCtrl', function ($scope) {
2
+ $scope.tabs = [
3
+ { title:'Dynamic Title 1', content:'Dynamic content 1' },
4
+ { title:'Dynamic Title 2', content:'Dynamic content 2', disabled: true }
5
+ ];
6
+ $scope.alertMe = function() {
7
+ setTimeout(function() {
8
+ alert('You\'ve selected the alert tab!');
9
+ });
10
+ };
11
+
12
+ });
@@ -0,0 +1,21 @@
1
+ angular.module('AgileProxy').directive('appEnhancedFormElement', function ($rootScope, $compile, DomIdService) {
2
+ return {
3
+ restrict: 'A',
4
+ scope: true,
5
+ link: function (scope, element, attrs) {
6
+ var label, localScope;
7
+ if (element.attr('id') === undefined) {
8
+ element.attr('id', DomIdService.nextId());
9
+ }
10
+ localScope = $rootScope.$new(true);
11
+ angular.extend(localScope, {
12
+ elementId: element.attr('id'),
13
+ labelText: attrs.label
14
+ });
15
+ label = $compile('<label class="control-label" for="elementId">{{labelText}}</label>')(localScope);
16
+ element.wrap('<div class="form-group">');
17
+ element.parent().prepend(label);
18
+
19
+ }
20
+ };
21
+ });
@@ -0,0 +1,16 @@
1
+ angular.module('AgileProxy').directive('appFor', function (DomIdService) {
2
+ return {
3
+ restrict: 'A',
4
+ link: function (scope, element, attrs) {
5
+ var inputElement;
6
+ inputElement = element.parent().find('[name="' + attrs.appFor + '"]').first();
7
+ if (!inputElement) {
8
+ return;
9
+ }
10
+ if (!inputElement.attr('id')) {
11
+ inputElement.attr('id', DomIdService.nextId());
12
+ }
13
+ element.attr('for', inputElement.attr('id'));
14
+ }
15
+ };
16
+ });
@@ -0,0 +1,54 @@
1
+ angular.module('AgileProxy').directive('appResponseEditor', function () {
2
+ return {
3
+ restrict: 'EA',
4
+ replace: true,
5
+ templateUrl: '/ui/app/view/responses/editForm.html',
6
+ scope: {
7
+ response: '=ngModel'
8
+ }, controller: function ($scope) {
9
+ var aceInstance, beautifiers;
10
+ beautifiers = {
11
+ 'json': function () {
12
+ $scope.response.content = JSON.stringify(JSON.parse($scope.response.content), null, 4);
13
+ }
14
+ };
15
+ angular.extend($scope, {
16
+ hasBeautifier: hasBeautifier(),
17
+ aceMode: contentTypeToAceMode($scope.response.contentType),
18
+ onAceLoaded: function (instance) {
19
+ aceInstance = instance;
20
+ },
21
+ reformat: function () {
22
+ var type;
23
+ type = contentTypeToAceMode($scope.response.contentType);
24
+ if (beautifiers.hasOwnProperty(type)) {
25
+ beautifiers[type].apply($scope, []);
26
+ }
27
+ },
28
+ onContentTypeChange: function (contentType) {
29
+
30
+ var type = contentTypeToAceMode(contentType);
31
+ aceInstance.getSession().setMode('ace/mode/' + type);
32
+ $scope.hasBeautifier = (beautifiers.hasOwnProperty(type));
33
+ }
34
+ });
35
+ function contentTypeToAceMode(contentType) {
36
+ switch (contentType) {
37
+ case "application/json":
38
+ return 'json';
39
+ case "application/javascript":
40
+ return 'javascript';
41
+ case "text/html":
42
+ return 'html';
43
+ case "text/plain":
44
+ return "plain_text";
45
+ default:
46
+ return "plain_text";
47
+ }
48
+ }
49
+ function hasBeautifier() {
50
+ return beautifiers.hasOwnProperty(contentTypeToAceMode($scope.response.contentType));
51
+ }
52
+ }
53
+ };
54
+ });
@@ -0,0 +1,6 @@
1
+ angular.module('AgileProxy').config(function(restmodProvider) {
2
+ restmodProvider.rebase('DefaultPacker');
3
+ });
4
+ angular.module('AgileProxy').factory('RequestSpecModel', function ($resource, restmod) {
5
+ return restmod.model('/api/v1/users/1/applications/1/request_specs', 'AgileProxyApi');
6
+ });
@@ -0,0 +1,10 @@
1
+ angular.module('AgileProxy').config(function ($routeProvider) {
2
+ $routeProvider.when('/status', {
3
+ templateUrl: '/ui/app/view/status.html'
4
+ }).when('/stubs', {
5
+ templateUrl: '/ui/app/view/stubs.html',
6
+ controller: 'StubsCtrl'
7
+ }).otherwise({
8
+ templateUrl: '/ui/app/view/404.html'
9
+ })
10
+ });
@@ -0,0 +1,49 @@
1
+ angular.module('AgileProxy').factory('DialogService', function ($modal, $q, $rootScope) {
2
+ return {
3
+ yesNo: function (message) {
4
+ var modalInstance, localScope, deferred;
5
+ localScope = $rootScope.$new(true);
6
+ deferred = $q.defer();
7
+ angular.extend(localScope, {
8
+ message: message,
9
+ yes: function () {
10
+ modalInstance.close();
11
+ deferred.resolve();
12
+
13
+ },
14
+ no: function () {
15
+ modalInstance.close();
16
+ deferred.reject('User Cancelled');
17
+ }
18
+ });
19
+ modalInstance = $modal.open({
20
+ templateUrl: '/ui/app/view/dialog/yesNo.html',
21
+ size: 'lg',
22
+ scope: localScope
23
+ });
24
+ return deferred.promise;
25
+
26
+
27
+ },
28
+ error: function (title, message) {
29
+ var modalInstance, localScope, deferred;
30
+ localScope = $rootScope.$new(true);
31
+ deferred = $q.defer();
32
+ angular.extend(localScope, {
33
+ title: title,
34
+ message: message,
35
+ close: function () {
36
+ modalInstance.close();
37
+ deferred.resolve({});
38
+ }
39
+ });
40
+ modalInstance = $modal.open({
41
+ templateUrl: '/ui/app/view/dialog/error.html',
42
+ size: 'lg',
43
+ scope: localScope
44
+ });
45
+ return deferred.promise;
46
+
47
+ }
48
+ };
49
+ });
@@ -0,0 +1,10 @@
1
+ angular.module('AgileProxy').factory('DomIdService', function () {
2
+ var nextIdNumber;
3
+ return {
4
+ nextId: function () {
5
+ nextIdNumber = nextIdNumber || 0;
6
+ nextIdNumber = nextIdNumber + 1;
7
+ return 'element-' + nextIdNumber;
8
+ }
9
+ };
10
+ });
@@ -0,0 +1,7 @@
1
+ angular.module('AgileProxy').factory('ErrorService', function (DialogService) {
2
+ return {
3
+ serverError: function (response) {
4
+ DialogService.error('Server Error', response.statusText);
5
+ }
6
+ };
7
+ });
@@ -0,0 +1,36 @@
1
+ angular.module('AgileProxy').factory('StubService', function ($modal, $q, $rootScope, RequestSpecModel) {
2
+ return {
3
+ addStub: function (scope) {
4
+ var stub;
5
+ stub = scope.requestSpecs.$build({httpMethod: 'GET', urlType: "url", response: {contentType: 'text/html', statusCode: 200}});
6
+ return this.openEditor(stub, scope);
7
+ },
8
+ editStub: function (stub, scope) {
9
+ return this.openEditor(stub, scope);
10
+ },
11
+ openEditor: function (stub, scope) {
12
+ var modalInstance, localScope, deferred;
13
+ function closeEditor() {
14
+ modalInstance.close();
15
+ }
16
+ localScope = scope ? scope.$new() : $rootScope.$new();
17
+ deferred = $q.defer();
18
+ angular.extend(localScope, {
19
+ stub: stub,
20
+ onOk: function (stub) {
21
+ deferred.resolve({stub: stub, close: closeEditor});
22
+ },
23
+ onCancel: function (stub) {
24
+ closeEditor();
25
+ deferred.reject('User Cancelled', stub);
26
+ }
27
+ });
28
+ modalInstance = $modal.open({
29
+ templateUrl: '/ui/app/view/stubs/edit.html',
30
+ size: 'lg',
31
+ scope: localScope
32
+ });
33
+ return deferred.promise;
34
+ }
35
+ };
36
+ });