the_wizard_of_api 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: aef0bec6a9854b4f1162cd1ecdda53a09a4aa855
4
- data.tar.gz: d1944f330aaad723f15356154f3f3d18374cedb2
3
+ metadata.gz: cf9b2e9d6271e57b48f843c4acfbd9d093b36106
4
+ data.tar.gz: 590cb0698da72963df534396fea4d9624a14cc12
5
5
  SHA512:
6
- metadata.gz: f1bca7f40dbacdce9a46875cc4d0289e62c2ea2074e65c1703b3276de88d67cd4f4071d7ff9ac75dfe043bf1930b61393041437330ba191f6363b2fd82d2b98d
7
- data.tar.gz: 8d0d442ad538a7a601045e10d162c6e6f425ab9fd98e47943a1d143f9ec125ac78c4448e7d08abbbd46aadd23bf441ae6fa41e27e610afbefe2d1369dc2953a1
6
+ metadata.gz: cc3fbaa708376e3bdd32ef5188ee634c2036085d49d5c77019a47039eb2faa79a26f41d422409daf79c1d1395beb6f864d7ae0de2da8c88a6d0291e66131c9da
7
+ data.tar.gz: dfd5d921116c7a34601c1d83129418516dcb3cbab6bca118e1fb587a098e769497aba6304c1c28ba02e609c9c31bc5f11d1847a61ec60dbabd049eaf794ee3f2
@@ -0,0 +1,24 @@
1
+ Feature: Javascript form for submitting responses
2
+ In order to create responses faster
3
+ As The Wizard
4
+ I want to compose the responses from a page in my browser
5
+
6
+ Scenario: A simple request and response
7
+ Given TheWizardOfApi is running in the browser
8
+ And someone else makes a GET request to "/api"
9
+ When I submit a "response" with:
10
+ """
11
+ Always believe in who you are Dorothy and let no one stand in your way.
12
+ """
13
+ Then they should see:
14
+ """
15
+ Always believe in who you are Dorothy and let no one stand in your way.
16
+ """
17
+ And I should see:
18
+ """
19
+ HTTP/1.1 200 OK
20
+ Content-Type: application/json
21
+
22
+ Always believe in who you are Dorothy and let no one stand in your way.
23
+ """
24
+
@@ -0,0 +1,18 @@
1
+ Given(/^TheWizardOfApi is running in the browser$/) do
2
+ unset_bundler_env_vars
3
+
4
+ install_the_wizard_of_api
5
+
6
+ start_the_wizard_of_api
7
+
8
+ visit throne_url
9
+
10
+ avoid_timing_errors do
11
+ expect(page).to have_text("Throne Room")
12
+ end
13
+ end
14
+
15
+ When(/^I submit a "(.*?)" with:$/) do |field, value|
16
+ fill_in(field, with: value)
17
+ click_on("Submit")
18
+ end
@@ -1,37 +1,31 @@
1
- require 'restclient'
2
-
3
1
  Given(/^TheWizardOfApi is running with defaults$/) do
4
2
  unset_bundler_env_vars
5
3
 
6
- write_file("Gemfile", <<-GEMFILE)
7
- gem 'the_wizard_of_api', path: '../../'
8
- GEMFILE
9
-
10
- write_file("config.ru",<<-RACKUP)
11
- require 'bundler/setup'
12
- require 'the_wizard_of_api'
13
- run TheWizardOfApi.new
14
- RACKUP
15
-
16
- run_simple("bundle")
4
+ install_the_wizard_of_api
17
5
 
18
- run_process(start: thin("-R config.ru start"),
19
- stop: thin("-R config.ru stop"))
6
+ start_the_wizard_of_api
20
7
 
21
- until thin_pid_path.exist?
22
- $stdout.write('.') if ENV['DEBUG']
23
- end
24
-
25
- visit throne_url
8
+ the_wizard_takes_their_seat
26
9
  end
27
10
 
28
11
  When(/^someone else makes a GET request to "(.*?)"$/) do |path|
29
12
  last_response = avoid_timing_errors do
30
- run_process(start: "curl -s localhost:3000#{path} & echo $! > curl.pid",
31
- stop: "kill -9 `cat curl.pid`")
13
+ dorothy_request(path)
32
14
  end
33
15
  end
34
16
 
35
17
  Then(/^I should see:$/) do |string|
36
- expect(page).to have_text(string)
18
+ avoid_timing_errors(2) do
19
+ expect(page).to have_text(string)
20
+ end
21
+ end
22
+
23
+ Then(/^they should see:$/) do |string|
24
+ avoid_timing_errors(2) do
25
+ check_file_content(log_path("dorothy").basename,string,true)
26
+ end
27
+ end
28
+
29
+ When(/^I respond with:$/) do |response|
30
+ wizard_response(response)
37
31
  end
@@ -1,17 +1,28 @@
1
1
  Feature: The Wizard sees all requests
2
2
  In order to create the illusion of a service
3
3
  As The Wizard
4
- I want to see all the requests coming to me
4
+ I want to see all the requests coming to me so that I may respond
5
5
 
6
6
  Scenario: A GET request to the default mount point
7
7
  Given TheWizardOfApi is running with defaults
8
8
  When someone else makes a GET request to "/api"
9
9
  Then I should see:
10
10
  """
11
- GET / HTTP/1.1
12
- Accept: */*; q=0.5, application/xml
13
- Accept-Encoding: gzip, deflate
14
- User-Agent: Ruby
15
- Host: 0.0.0.0:9292
11
+ GET /api HTTP/1.1
12
+ Accept: */*
13
+ User-Agent: curl
14
+ Host: localhost:3000
15
+ """
16
+
17
+ Scenario: A POST request to the Wizard's response mount point
18
+ Given TheWizardOfApi is running with defaults
19
+ And someone else makes a GET request to "/api"
20
+ When I respond with:
21
+ """
22
+ {"dorothy": "You may have your wish"}
23
+ """
24
+ Then they should see:
25
+ """
26
+ {"dorothy": "You may have your wish"}
16
27
  """
17
28
 
@@ -0,0 +1,20 @@
1
+ module CurlHelper
2
+ def curl(flag, request, options = {})
3
+ pid_path = options.fetch(:pid, "#{flag}.pid")
4
+ log_path = options.fetch(:log, "#{flag}.log")
5
+
6
+ run = ["curl -sN -A 'curl'"]
7
+ run << "-o #{log_path}" if log_path
8
+ run << request
9
+ run << " & echo $! > #{pid_path}" if pid_path
10
+
11
+ debug(run.join(" "))
12
+ run.join(" ")
13
+ end
14
+
15
+ def cleanup_curl(flag, options = {})
16
+ pid_path = options.fetch(:pid, "#{flag}.pid")
17
+
18
+ "kill -9 `cat #{pid_path}`"
19
+ end
20
+ end
@@ -21,7 +21,10 @@ After(&ProcessHelper::After)
21
21
 
22
22
  require 'the_wizard_of_api_helper'
23
23
 
24
- World(DebuggingPryHelper,
24
+ require 'curl_helper'
25
+
26
+ World(CurlHelper,
27
+ DebuggingPryHelper,
25
28
  ProcessHelper,
26
29
  ThinHelper,
27
30
  TimingErrorHelper,
@@ -19,6 +19,18 @@ module ProcessHelper
19
19
  end
20
20
  end
21
21
 
22
+ def wait_for_log_to_contain(path,string=false)
23
+ until path.exist? && !path.read.empty?
24
+ debug(".")
25
+ end
26
+
27
+ if string
28
+ until path.read.include?(string)
29
+ debug(".")
30
+ end
31
+ end
32
+ end
33
+
22
34
  After = lambda do |scenario|
23
35
  stop_services
24
36
  end
@@ -2,4 +2,46 @@ module TheWizardOfApiHelper
2
2
  def throne_url
3
3
  "http://localhost:3000/throne"
4
4
  end
5
+
6
+ def install_the_wizard_of_api
7
+ write_file("Gemfile", <<-GEMFILE)
8
+ gem 'the_wizard_of_api', path: '../../'
9
+ GEMFILE
10
+
11
+ write_file("config.ru",<<-RACKUP)
12
+ require 'bundler/setup'
13
+ require 'the_wizard_of_api'
14
+ run TheWizardOfApi.new
15
+ RACKUP
16
+
17
+ run_simple("bundle")
18
+ end
19
+
20
+ def start_the_wizard_of_api
21
+ run_process(
22
+ start: thin(:start, pid: thin_pid_path.basename, log: thin_log_path.basename, rackup: "config.ru"),
23
+ stop: thin(:stop, pid: thin_pid_path.basename))
24
+
25
+ wait_for_log_to_contain(thin_log_path,"Listening on")
26
+ end
27
+
28
+ def dorothy_request(path)
29
+ # Curl or new webkit process, or rest client
30
+ run_process(start: curl("dorothy", "http://localhost:3000#{path}"),
31
+ stop: cleanup_curl("dorothy"))
32
+
33
+ wait_for_log_to_contain(log_path("dorothy"))
34
+ end
35
+
36
+ def wizard_response(data)
37
+ run_simple("curl -sN --form 'response=#{data}' http://localhost:3000/throne/response")
38
+ end
39
+
40
+ def the_wizard_takes_their_seat
41
+ visit throne_url
42
+ end
43
+
44
+ def log_path(name)
45
+ Pathname.new(current_dir) + "#{name}.log"
46
+ end
5
47
  end
@@ -15,8 +15,20 @@ module ThinHelper
15
15
  thin_pid_path.read.to_i
16
16
  end
17
17
 
18
- def thin(command)
19
- "thin -d -P #{thin_pid_path.basename} -l #{thin_log_path.basename} #{command}"
18
+ def thin(command, options = {})
19
+ pid_path = options.fetch(:pid,false)
20
+ log_path = options.fetch(:log,false)
21
+ rackup = options.fetch(:rackup,false)
22
+ run = ["thin -DV"]
23
+ run << "-d" if pid_path
24
+ run << "-P #{pid_path}" if pid_path
25
+ run << "-l #{log_path}" if log_path
26
+ run << "-R #{rackup}" if rackup
27
+
28
+ debug(run.join(" "))
29
+
30
+ run << command
31
+ run.join(" ")
20
32
  end
21
33
 
22
34
  Before = lambda do |scenario|
@@ -1,6 +1,6 @@
1
1
  module TimingErrorHelper
2
2
  def debug(string)
3
- if ENV['DEBUG'] == true
3
+ if ENV['DEBUG']
4
4
  if string.length == 1
5
5
  $stdout.write(string)
6
6
  else
@@ -9,23 +9,42 @@ module TimingErrorHelper
9
9
  end
10
10
  end
11
11
 
12
- def avoid_timing_errors
13
- begin
14
- yield
15
- rescue Timeout::Error => e
16
- debug('.')
17
- retry
18
- rescue Errno::ECONNREFUSED => e
19
- debug(',')
20
- retry
21
- rescue Capybara::Webkit::InvalidResponseError => e
22
- if e.message.include?("Unable to load URL")
23
- debug('!')
12
+ def avoid_timing_errors(time=false,&block)
13
+ carefully = lambda do |*args|
14
+ begin
15
+ block.call
16
+ rescue Timeout::Error => e
17
+ debug('.')
24
18
  retry
19
+ rescue Errno::ECONNREFUSED => e
20
+ debug(',')
21
+ retry
22
+ rescue Capybara::Webkit::InvalidResponseError => e
23
+ if e.message.include?("Unable to load URL")
24
+ debug('!')
25
+ retry
26
+ end
27
+ rescue RSpec::Expectations::ExpectationNotMetError => e
28
+ if e.message.include?("Diff:")
29
+ debug('`')
30
+ retry
31
+ end
32
+ rescue => e
33
+ pry(binding)
34
+ raise e
25
35
  end
26
- rescue => e
27
- pry(binding)
28
- raise e
36
+ end
37
+
38
+ if time
39
+ begin
40
+ Timeout::timeout(time,&carefully)
41
+ rescue Timeout::Error => e
42
+ debug('.')
43
+ end
44
+
45
+ block.call
46
+ else
47
+ carefully.call
29
48
  end
30
49
  end
31
50
  end
@@ -1,3 +1,3 @@
1
1
  module TheWizardOfApi
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -1,5 +1,6 @@
1
1
  require "the_wizard_of_api/version"
2
2
  require 'faye'
3
+ require 'rack/try_static'
3
4
 
4
5
  class DeferrableBody
5
6
  include EventMachine::Deferrable
@@ -21,25 +22,27 @@ module TheWizardOfApi
21
22
  def self.new(app = nil)
22
23
  wizard = Rack::Builder.new do
23
24
 
24
- self.class.class_eval do
25
- def stream_line(body, shown_key, env, http_key)
26
- value = env[http_key]
27
-
28
- if value
29
- line = ["#{shown_key}:",value].join(" ")
30
- puts line
31
- body.call [line+"\n"]
32
- else
33
- puts
34
- puts env.inspect
35
- puts
36
- end
25
+ map "/throne" do
26
+ map "/response" do
27
+ run Proc.new { |env|
28
+ client = env.fetch('faye.client')
29
+
30
+ req = Rack::Request.new(env)
31
+
32
+ client.publish('/wizard_response',req.params['response'])
33
+
34
+ [200,{},[]]
35
+ }
37
36
  end
37
+
38
+ run Rack::TryStatic.new(app,{
39
+ root: Pathname.new(__FILE__).dirname.dirname.join("public").to_s,
40
+ urls: %w[/],
41
+ try: ['.html', 'index.html', '/index.html']})
38
42
  end
39
43
 
40
- map "/throne" do
44
+ map "/api" do
41
45
  run Proc.new { |env|
42
-
43
46
  body = DeferrableBody.new
44
47
 
45
48
  client = env.fetch('faye.client')
@@ -49,35 +52,13 @@ module TheWizardOfApi
49
52
  body.call [" " * 1024]
50
53
  }
51
54
 
52
- EventMachine::add_timer(0.5) {
53
- body.call ["Throne Room\n"]
54
- }
55
-
56
- client.subscribe('/api') do |message|
57
- first_line = message.values_at("REQUEST_METHOD","REQUEST_PATH","HTTP_VERSION").join(" ")+"\n"
58
-
59
- puts first_line
60
- body.call [first_line]
61
-
62
- stream_line(body, "Accept", message, "HTTP_ACCEPT")
63
- stream_line(body, "Accept-Encoding", message, "HTTP_ACCEPT_ENCODING")
64
- stream_line(body, "User-Agent", message, "HTTP_USER_AGENT")
65
- stream_line(body, "Host", message, "HTTP_HOST")
66
- end
67
- ASYNC
68
- }
69
- end
70
-
71
- map "/api" do
72
- run Proc.new { |env|
73
- client = env.fetch('faye.client')
74
-
75
55
  client.publish('/api', env)
76
56
 
77
57
  callback = env['async.callback']
78
58
 
79
59
  client.subscribe('/wizard_response') do |message|
80
- callback.call(message)
60
+ body.call [message]
61
+ body.succeed
81
62
  end
82
63
 
83
64
  ASYNC
data/public/index.html ADDED
@@ -0,0 +1,60 @@
1
+ <!DOCTYPE html>
2
+
3
+ <html>
4
+ <head>
5
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
6
+ <title>Throne Room</title>
7
+ <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
8
+ <script src="/faye/client.js" type="text/javascript"></script>
9
+ </head>
10
+ <body>
11
+ <h1>Throne Room</h1>
12
+ <form action="/throne/response" method="POST">
13
+ <label for="response_field">Response</label>
14
+ <input id="response_field" name="response" type="text" />
15
+
16
+ <input type="submit" value="Submit" />
17
+ </form>
18
+
19
+ <script type="text/javascript">
20
+ var client = new Faye.Client('/faye');
21
+ Faye.logger = console;
22
+
23
+ $("form").on("submit",function(e){
24
+ var $form = $(e.target);
25
+ var $response = $("#response_field");
26
+
27
+ $("<pre>HTTP/1.1 200 OK\nContent-Type: application/json\n\n"+$response.val()+"</pre>").insertBefore($form);
28
+
29
+ $.ajax($form.attr("action"), {"data": $form.serializeArray()});
30
+ return false;
31
+ });
32
+
33
+ client.subscribe("/api", function(message){
34
+ request = [
35
+ formatFirstLine(message),
36
+ formatLine("Accept", message, "HTTP_ACCEPT"),
37
+ formatLine("User-Agent", message, "HTTP_USER_AGENT"),
38
+ formatLine("Host", message, "HTTP_HOST")
39
+ ]
40
+
41
+ document.body.querySelector("form").insertAdjacentHTML("beforeBegin","<pre>"+request.join("\n")+"</pre>")
42
+ });
43
+
44
+ function formatFirstLine(request) {
45
+ return [request.REQUEST_METHOD,request.REQUEST_PATH,request.HTTP_VERSION].join(" ")
46
+ }
47
+
48
+ function formatLine(label, request, key) {
49
+ var value = request[key];
50
+
51
+ if (value) {
52
+ return [label+":", value].join(" ");
53
+ } else {
54
+ console.log(label,request,key);
55
+ }
56
+ }
57
+ </script>
58
+ </body>
59
+ </html>
60
+
@@ -15,17 +15,19 @@ describe TheWizardOfApi do
15
15
  context "Throne Room" do
16
16
  subject { get "/throne" }
17
17
 
18
- it "should stream the response" do
19
- expect(subject.status).to eql(-1)
20
- end
18
+ it { expect(subject).to be_ok }
21
19
 
22
- pending "this isn't the actual feature-value i'm looking for" do
23
- it "should stream Throne Room first" do
24
- expect(subject.body).to include("Throne Room")
25
- end
20
+ it "should stream Throne Room first" do
21
+ expect(subject.body).to include("Throne Room")
26
22
  end
27
23
  end
28
24
 
25
+ context "Throne Room response" do
26
+ subject { post "/throne/response", "response"=>"something" }
27
+
28
+ it { should be_ok }
29
+ end
30
+
29
31
  context "api mount point" do
30
32
  # default
31
33
  let(:api_mount_point) { "/api" }
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_dependency "faye", "~> 0.8"
22
22
  spec.add_dependency "rack", "~> 1.5"
23
+ spec.add_dependency "rack-try_static2", "~> 1.1"
23
24
  spec.add_dependency "thin", "~> 1.5"
24
25
  spec.add_development_dependency "bcat", "~> 0.6"
25
26
  spec.add_development_dependency "capybara", "~> 2.1"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: the_wizard_of_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Caleb Buxton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-09-03 00:00:00.000000000 Z
11
+ date: 2013-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faye
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ~>
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rack-try_static2
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.1'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: thin
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -220,10 +234,13 @@ files:
220
234
  - LICENSE.txt
221
235
  - README.md
222
236
  - Rakefile
237
+ - features/javascript_frontend.feature
223
238
  - features/readme.feature
239
+ - features/step_definitions/javascript_frontend_steps.rb
224
240
  - features/step_definitions/readme_steps.rb
225
241
  - features/step_definitions/streaming_requests_to_the_throne_room_steps.rb
226
242
  - features/streaming_requests_to_the_throne_room.feature
243
+ - features/support/curl_helper.rb
227
244
  - features/support/debugging_pry_helper.rb
228
245
  - features/support/env.rb
229
246
  - features/support/process_helper.rb
@@ -232,6 +249,7 @@ files:
232
249
  - features/support/timing_error_helper.rb
233
250
  - lib/the_wizard_of_api.rb
234
251
  - lib/the_wizard_of_api/version.rb
252
+ - public/index.html
235
253
  - spec/lib/the_wizard_of_api_spec.rb
236
254
  - the_wizard_of_api.gemspec
237
255
  homepage: ''
@@ -259,10 +277,13 @@ signing_key:
259
277
  specification_version: 4
260
278
  summary: Enables a human-wizard to craft each response for a yet unimplemented API.
261
279
  test_files:
280
+ - features/javascript_frontend.feature
262
281
  - features/readme.feature
282
+ - features/step_definitions/javascript_frontend_steps.rb
263
283
  - features/step_definitions/readme_steps.rb
264
284
  - features/step_definitions/streaming_requests_to_the_throne_room_steps.rb
265
285
  - features/streaming_requests_to_the_throne_room.feature
286
+ - features/support/curl_helper.rb
266
287
  - features/support/debugging_pry_helper.rb
267
288
  - features/support/env.rb
268
289
  - features/support/process_helper.rb