agile-proxy 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -0,0 +1,8 @@
1
+ class AddUsernameAndPasswordToApplications < ActiveRecord::Migration
2
+ def change
3
+ change_table(:applications) do |t|
4
+ t.string :username, default: 'anonymous'
5
+ t.string :password, default: 'password'
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ class AddRecordToApplications < ActiveRecord::Migration
2
+ def change
3
+ change_table(:applications) do |t|
4
+ t.boolean :record_requests, default: false
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,18 @@
1
+ class CreateRecordings < ActiveRecord::Migration
2
+ def change
3
+ create_table :recordings do |t|
4
+ t.integer :application_id
5
+ t.text :request_headers
6
+ t.text :request_body
7
+ t.string :request_url
8
+ t.string :request_method
9
+ t.text :response_headers
10
+ t.text :response_body
11
+ t.text :response_status
12
+ t.integer :request_spec_id
13
+ t.timestamps
14
+ end
15
+ add_index :recordings, :application_id, :unique => false
16
+ add_index :recordings, :request_spec_id, :unique => false
17
+ end
18
+ end
data/db/schema.rb ADDED
@@ -0,0 +1,78 @@
1
+ # encoding: UTF-8
2
+ # This file is auto-generated from the current state of the database. Instead
3
+ # of editing this file, please use the migrations feature of Active Record to
4
+ # incrementally modify your database, and then regenerate this schema definition.
5
+ #
6
+ # Note that this schema.rb definition is the authoritative source for your
7
+ # database schema. If you need to create the application database on another
8
+ # system, you should be using db:schema:load, not running all the migrations
9
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
11
+ #
12
+ # It's strongly recommended that you check this file into your version control system.
13
+
14
+ ActiveRecord::Schema.define(version: 20141119174300) do
15
+
16
+ create_table "applications", force: true do |t|
17
+ t.integer "user_id"
18
+ t.string "name"
19
+ t.datetime "created_at"
20
+ t.datetime "updated_at"
21
+ t.string "username", default: "anonymous"
22
+ t.string "password", default: "password"
23
+ t.boolean "record_requests", default: false
24
+ end
25
+
26
+ add_index "applications", ["user_id"], name: "index_applications_on_user_id"
27
+
28
+ create_table "recordings", force: true do |t|
29
+ t.integer "application_id"
30
+ t.text "request_headers"
31
+ t.text "request_body"
32
+ t.string "request_url"
33
+ t.string "request_method"
34
+ t.text "response_headers"
35
+ t.text "response_body"
36
+ t.text "response_status"
37
+ t.integer "request_spec_id"
38
+ t.datetime "created_at"
39
+ t.datetime "updated_at"
40
+ end
41
+
42
+ add_index "recordings", ["application_id"], name: "index_recordings_on_application_id"
43
+ add_index "recordings", ["request_spec_id"], name: "index_recordings_on_request_spec_id"
44
+
45
+ create_table "request_specs", force: true do |t|
46
+ t.integer "user_id"
47
+ t.integer "application_id"
48
+ t.string "url"
49
+ t.text "note"
50
+ t.integer "response_id"
51
+ t.string "http_method", default: "GET"
52
+ t.string "url_type", default: "url"
53
+ t.text "conditions", default: "{}"
54
+ end
55
+
56
+ add_index "request_specs", ["application_id"], name: "index_request_specs_on_application_id"
57
+ add_index "request_specs", ["user_id"], name: "index_request_specs_on_user_id"
58
+
59
+ create_table "responses", force: true do |t|
60
+ t.string "name"
61
+ t.text "content"
62
+ t.string "content_type"
63
+ t.integer "status_code", default: 200
64
+ t.text "headers", default: "{}"
65
+ t.boolean "is_template"
66
+ t.float "delay", default: 0.0
67
+ t.datetime "created_at"
68
+ t.datetime "updated_at"
69
+ end
70
+
71
+ create_table "users", force: true do |t|
72
+ t.string "name"
73
+ t.string "email"
74
+ t.datetime "created_at"
75
+ t.datetime "updated_at"
76
+ end
77
+
78
+ end
@@ -0,0 +1 @@
1
+ See example specs in `spec/requests/examples`.
@@ -0,0 +1,59 @@
1
+ <div id="fb-root"></div>
2
+ <script>
3
+ // Load the SDK Asynchronously
4
+ (function(d){
5
+ var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
6
+ if (d.getElementById(id)) {return;}
7
+ js = d.createElement('script'); js.id = id; js.async = true;
8
+ js.src = "//connect.facebook.net/en_US/all.js";
9
+ ref.parentNode.insertBefore(js, ref);
10
+ }(document));
11
+
12
+ // Init the SDK upon load
13
+ window.fbAsyncInit = function() {
14
+ FB.init({
15
+ appId : '408416075843608', // App ID
16
+ //channelUrl : '//'+window.location.hostname+'/channel', // Path to your Channel File
17
+ status : true, // check login status
18
+ cookie : true, // enable cookies to allow the server to access the session
19
+ xfbml : true // parse XFBML
20
+ });
21
+
22
+ // listen for and handle auth.statusChange events
23
+ FB.Event.subscribe('auth.statusChange', function(response) {
24
+ if (response.authResponse) {
25
+ // user has auth'd your app and is logged into Facebook
26
+ FB.api('/me', function(me){
27
+ if (me.name) {
28
+ document.getElementById('auth-displayname').innerHTML = me.name;
29
+ }
30
+ });
31
+ document.getElementById('auth-loggedout').style.display = 'none';
32
+ document.getElementById('auth-loggedin').style.display = 'block';
33
+ } else {
34
+ // user has not auth'd your app, or is not logged into Facebook
35
+ document.getElementById('auth-loggedout').style.display = 'block';
36
+ document.getElementById('auth-loggedin').style.display = 'none';
37
+ }
38
+ });
39
+
40
+ // respond to clicks on the login and logout links
41
+ document.getElementById('auth-loginlink').addEventListener('click', function(){
42
+ FB.login();
43
+ });
44
+ document.getElementById('auth-logoutlink').addEventListener('click', function(){
45
+ FB.logout();
46
+ });
47
+ }
48
+ </script>
49
+
50
+ <h1>Facebook Client-side Authentication Example</h1>
51
+ <div id="auth-status">
52
+ <div id="auth-loggedout">
53
+ <a href="#" id="auth-loginlink">Login</a>
54
+ </div>
55
+ <div id="auth-loggedin" style="display:none">
56
+ Hi, <span id="auth-displayname"></span>
57
+ (<a href="#" id="auth-logoutlink">logout</a>)
58
+ </div>
59
+ </div>
@@ -0,0 +1,22 @@
1
+ <!doctype html>
2
+ <body>
3
+ <h1>Latest news</h1>
4
+ <div id="news"></div>
5
+
6
+ <script type='text/javascript' src='http://code.jquery.com/jquery-1.8.2.min.js'></script>
7
+ <script type='text/javascript'>
8
+ $(function () {
9
+ var url = 'http://blog.howmanyleft.co.uk/api/read/json?callback=?&type=text&num=3&filter=text';
10
+ $.getJSON(url, function (data) {
11
+ $.each(data.posts, function (idx, post) {
12
+ var title = post['regular-title'];
13
+ var href = post['url-with-slug'];
14
+ var body = post['regular-body'];
15
+ $('#news').append(
16
+ '<h3><a href="' + href + '">' + title + '</a></h3>' +
17
+ '<p>' + body + '</p>');
18
+ });
19
+ });
20
+ })
21
+ </script>
22
+ </body>
@@ -0,0 +1,8 @@
1
+ require 'agile_proxy/version'
2
+ require 'agile_proxy/config'
3
+ require 'agile_proxy/handlers/handler'
4
+ require 'agile_proxy/handlers/request_handler'
5
+ require 'agile_proxy/handlers/stub_handler'
6
+ require 'agile_proxy/handlers/proxy_handler'
7
+ require 'agile_proxy/server'
8
+ require 'agile_proxy/proxy_connection'
@@ -0,0 +1,77 @@
1
+ module AgileProxy
2
+ module Api
3
+ #
4
+ # = A grape api for applications
5
+ #
6
+ # An application is a central resource for the proxy, it is the 'application under test or development'
7
+ #
8
+ # The proxy server can handle multiple applications by assigning each one a different username and password
9
+ # that is used when connecting to the proxy.
10
+ # Each application can have its own set of stubs, can be set to record or not and much much more.
11
+ class Applications < Grape::API
12
+ include Grape::Kaminari
13
+ helpers do
14
+ # We only allow selected parameters through - spec and note
15
+ def permitted_params
16
+ @permitted_params ||= declared(
17
+ params,
18
+ { include_missing: false },
19
+ [:username, :password, :name, :record_requests]
20
+ )
21
+ end
22
+
23
+ # Convenient access to the record specified in the id parameter
24
+ def record
25
+ current_user.applications.where(id: params[:id]).first
26
+ end
27
+
28
+ # Convenient access to the record parameters from a POST or a PUT, only permitted will be returned
29
+ def record_params
30
+ permitted_params.with_indifferent_access
31
+ end
32
+
33
+ def default_json_spec
34
+ {}
35
+ end
36
+
37
+ end
38
+
39
+ resource :applications do
40
+ desc 'List all applications for the user'
41
+ paginate per_page: 20, max_per_page: 200
42
+ get do
43
+ authenticate!
44
+ scope = current_user.applications
45
+ { applications: paginate(scope).as_json(default_json_spec), total: scope.count }
46
+ end
47
+ desc 'Delete all applications for the user'
48
+ delete do
49
+ authenticate!
50
+ scope = current_user.applications
51
+ scope.destroy_all
52
+ { applications: [], total: 0 }
53
+ end
54
+ desc 'Create a new application for the user'
55
+ post do
56
+ authenticate!
57
+ current_user.applications.create!(record_params.merge user_id: current_user.id).as_json(default_json_spec)
58
+ end
59
+ desc 'Get an application by id'
60
+ get ':id' do
61
+ authenticate!
62
+ record.as_json(default_json_spec)
63
+ end
64
+ delete ':id' do
65
+ authenticate!
66
+ record.tap(&:destroy).as_json(default_json_spec)
67
+ end
68
+ desc 'Update a request application'
69
+ put ':id' do
70
+ authenticate!
71
+ record.tap { |r| r.update_attributes(record_params) }.as_json(default_json_spec)
72
+ end
73
+
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,52 @@
1
+ module AgileProxy
2
+ module Api
3
+ #
4
+ # = A grape API for recordings
5
+ #
6
+ # If the application is set to allow recordings, each HTTP request and response passing
7
+ # through the proxy server will be recorded.
8
+ #
9
+ # This API allows access to those recordings via REST
10
+ #
11
+ class Recordings < Grape::API
12
+ include Grape::Kaminari
13
+ helpers do
14
+ # Convenient access to the record specified in the id parameter
15
+ def record
16
+ current_application.recordings.where(id: params[:id]).first
17
+ end
18
+
19
+ def default_json_spec
20
+ {}
21
+ end
22
+ end
23
+
24
+ resource :recordings do
25
+ desc 'List all recordings made for the application'
26
+ paginate per_page: 20, max_per_page: 200
27
+ get do
28
+ authenticate!
29
+ scope = current_application.recordings
30
+ { recordings: paginate(scope).as_json(default_json_spec), total: scope.count }
31
+ end
32
+ desc 'Delete all rcordings for the application'
33
+ delete do
34
+ authenticate!
35
+ scope = current_application.recordings
36
+ scope.destroy_all
37
+ { recordings: [], total: 0 }
38
+ end
39
+ desc 'Get a recording by id'
40
+ get ':id' do
41
+ authenticate!
42
+ record.as_json(default_json_spec)
43
+ end
44
+ delete ':id' do
45
+ authenticate!
46
+ record.tap(&:destroy).as_json(default_json_spec)
47
+ end
48
+
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,85 @@
1
+ require 'agile_proxy/model/request_spec'
2
+ require 'grape-kaminari'
3
+ module AgileProxy
4
+ module Api
5
+ #
6
+ # = A grape API for request specifications
7
+ #
8
+ # A 'request specification' is what is known as a 'Stub' in the UI.
9
+ #
10
+ # It defines an input and output spec for a HTTP(s) request.
11
+ #
12
+ # For example, we could say
13
+ # 'When http://www.mybing.com/search.html is requested with some specific query parameters, then respond with this'
14
+ #
15
+ # This API allows full CRUD access to these request specifications, but only those belonging to the logged in user.
16
+ #
17
+ class RequestSpecs < Grape::API
18
+ include Grape::Kaminari
19
+ helpers do
20
+ # We only allow selected parameters through - spec and note
21
+ def permitted_params
22
+ @permitted_params ||= declared(
23
+ params,
24
+ { include_missing: false },
25
+ [:spec, :note, :response, :http_method, :url, :url_type, :conditions]
26
+ )
27
+ end
28
+
29
+ # Convenient access to the record specified in the id parameter
30
+ def record
31
+ current_application.request_specs.where(id: params[:id]).first
32
+ end
33
+
34
+ # Convenient access to the record parameters from a POST or a PUT, only permitted will be returned
35
+ # Note that for some reason, to do with rack or grape,
36
+ # when we send a large body, the request_spec does not come through
37
+ # so, to work around this, we inject the request spec in afterwards if it is missing.
38
+ def record_params
39
+ p = permitted_params.with_indifferent_access
40
+ p.merge!(user_id: current_user.id, application_id: current_application.id)
41
+ p[:response_attributes] = p.delete(:response) if p.key?(:response)
42
+ p
43
+ end
44
+
45
+ def default_json_spec
46
+ { include: { response: { except: [:created_at, :updated_at] } } }
47
+ end
48
+
49
+ end
50
+ resource :request_specs do
51
+ desc 'List all request specifications for the application'
52
+ paginate per_page: 20, max_per_page: 200
53
+ get do
54
+ authenticate!
55
+ scope = current_application.request_specs
56
+ { request_specs: paginate(scope).as_json(default_json_spec), total: scope.count }
57
+ end
58
+ delete do
59
+ authenticate!
60
+ scope = current_application.request_specs
61
+ scope.delete_all
62
+ { request_specs: [], total: 0 }
63
+ end
64
+ desc 'Create a new request specification'
65
+ post do
66
+ authenticate!
67
+ current_application.request_specs.create(record_params).as_json(default_json_spec)
68
+ end
69
+ get ':id' do
70
+ authenticate!
71
+ record.as_json(default_json_spec)
72
+ end
73
+ desc 'Update a request specification'
74
+ put ':id' do
75
+ authenticate!
76
+ record.tap { |r| r.update_attributes(record_params) }.as_json(default_json_spec)
77
+ end
78
+ delete ':id' do
79
+ authenticate!
80
+ record.tap(&:destroy).as_json(default_json_spec)
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,41 @@
1
+ require_relative 'request_specs'
2
+ require_relative 'applications'
3
+ require_relative 'recordings'
4
+ module AgileProxy
5
+ module Api
6
+ #
7
+ # = The API Root
8
+ #
9
+ # This is the root of the entire API for use with REST. It will not be accessed directly
10
+ #
11
+ class Root < Grape::API
12
+ version 'v1', vendor: 'agile-proxy'
13
+ format :json
14
+ helpers do
15
+ # Provides easy access to the current user.
16
+ # As we are currently only single user, then we just return the first user
17
+ def current_user
18
+ ::AgileProxy::User.first
19
+ end
20
+ # Secured methods must call this first
21
+ def authenticate!
22
+ # Do nothing yet
23
+ end
24
+ # Provides easy access to the current application whether
25
+ # specified in the URL or not (defaults to the first if not)
26
+ def current_application
27
+ fail 'Application ID is missing' unless params.key?(:application_id)
28
+ applications = current_user.applications
29
+ applications.where(id: params[:application_id]).first
30
+ end
31
+ end
32
+ namespace 'users/:user_id' do
33
+ mount Api::Applications
34
+ namespace '/applications/:application_id' do
35
+ mount Api::Recordings
36
+ mount Api::RequestSpecs
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end