the_wizard_of_api 0.0.2 → 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.
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