tobias-rack-webconsole 0.1.4
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.
- data/.gitignore +9 -0
- data/.rvmrc +1 -0
- data/.travis.yml +9 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +40 -0
- data/History +27 -0
- data/Rakefile +23 -0
- data/Readme.md +125 -0
- data/lib/rack-webconsole.rb +3 -0
- data/lib/rack/webconsole.rb +83 -0
- data/lib/rack/webconsole/asset_helpers.rb +73 -0
- data/lib/rack/webconsole/assets.rb +75 -0
- data/lib/rack/webconsole/railtie.rb +14 -0
- data/lib/rack/webconsole/repl.rb +86 -0
- data/lib/rack/webconsole/sandbox.rb +33 -0
- data/lib/rack/webconsole/shell.rb +74 -0
- data/lib/rack/webconsole/version.rb +7 -0
- data/public/jquery.html +1 -0
- data/public/webconsole.css +86 -0
- data/public/webconsole.html +15 -0
- data/public/webconsole.js +92 -0
- data/rack-webconsole.gemspec +36 -0
- data/spec/rack/webconsole/asset_helpers_spec.rb +56 -0
- data/spec/rack/webconsole/assets_spec.rb +101 -0
- data/spec/rack/webconsole/repl_spec.rb +106 -0
- data/spec/rack/webconsole/sandbox_spec.rb +50 -0
- data/spec/rack/webconsole/shell_spec.rb +73 -0
- data/spec/rack/webconsole_spec.rb +56 -0
- data/spec/spec_helper.rb +10 -0
- metadata +190 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
class AssetClass
|
5
|
+
include Rack::Webconsole::AssetHelpers
|
6
|
+
end
|
7
|
+
|
8
|
+
module Rack
|
9
|
+
describe Webconsole::AssetHelpers do
|
10
|
+
|
11
|
+
describe '#html_code' do
|
12
|
+
it 'loads the html code' do
|
13
|
+
asset_class = AssetClass.new
|
14
|
+
html = asset_class.html_code
|
15
|
+
|
16
|
+
html.must_match /console/
|
17
|
+
html.must_match /results/
|
18
|
+
html.must_match /form/
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#css_code' do
|
23
|
+
it 'loads the css code' do
|
24
|
+
asset_class = AssetClass.new
|
25
|
+
css = asset_class.css_code
|
26
|
+
|
27
|
+
css.must_match /<style/
|
28
|
+
css.must_match /text\/css/
|
29
|
+
css.must_match /#console/
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#js_code' do
|
34
|
+
it 'loads the js code' do
|
35
|
+
asset_class = AssetClass.new
|
36
|
+
js = asset_class.js_code
|
37
|
+
|
38
|
+
js.must_match /\$\("#rack-webconsole"\)/
|
39
|
+
js.must_match /escapeHTML/
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#render' do
|
44
|
+
it 'knows how to replace $ vars' do
|
45
|
+
asset_class = AssetClass.new
|
46
|
+
|
47
|
+
text = "test $test test $test"
|
48
|
+
asset_class.render(text, :test => "123").must_equal("test 123 test 123")
|
49
|
+
|
50
|
+
text = "test $var1 test $var2"
|
51
|
+
asset_class.render(text, :var1 => "123", :var2 => "321").must_equal("test 123 test 321")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
describe Webconsole::Assets do
|
7
|
+
|
8
|
+
it 'initializes with an app' do
|
9
|
+
@app = stub
|
10
|
+
@assets = Webconsole::Assets.new(@app)
|
11
|
+
@assets.instance_variable_get(:@app).must_equal @app
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#code" do
|
15
|
+
it 'injects the token, key_code, and context path' do
|
16
|
+
Webconsole::Repl.stubs(:token).returns('fake_generated_token')
|
17
|
+
Webconsole.key_code = "96"
|
18
|
+
|
19
|
+
@assets = Webconsole::Assets.new(nil)
|
20
|
+
assets_code = @assets.code('SCRIPT_NAME' => '/hambiscuit')
|
21
|
+
|
22
|
+
assets_code.must_match /fake_generated_token/
|
23
|
+
assets_code.must_match /event\.which == 96/
|
24
|
+
assets_code.must_match %r{/hambiscuit/webconsole}
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#call" do
|
30
|
+
|
31
|
+
describe 'when the call is not appropriate to inject the view code' do
|
32
|
+
# Different invalid cases
|
33
|
+
[
|
34
|
+
[200, {'Content-Type' => 'text/html'}, ['Whatever']],
|
35
|
+
[200, {'Content-Type' => 'text/plain'}, ['Hello World']],
|
36
|
+
[404, {'Content-Type' => 'text/html'}, ['Hello World']],
|
37
|
+
[404, {'Content-Type' => 'text/html'}, ['Hello, World']],
|
38
|
+
|
39
|
+
].each do |invalid_response|
|
40
|
+
it 'passes the call untouched' do
|
41
|
+
@app = lambda { |env| invalid_response }
|
42
|
+
|
43
|
+
assets = Webconsole::Assets.new(@app)
|
44
|
+
assets.expects(:inject_code).never
|
45
|
+
|
46
|
+
assets.call({}).last.first.must_equal invalid_response.last.first
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'otherwise' do
|
52
|
+
|
53
|
+
it 'injects the view code before the body ending' do
|
54
|
+
|
55
|
+
valid_html = "<!DOCTYPE html>\n<html>\n<head>\n <title>Testapp</title>\n <link href=\"/assets/application.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />\n <script src=\"/assets/application.js\" type=\"text/javascript\"></script>\n <meta content=\"authenticity_token\" name=\"csrf-param\" />\n<meta content=\"26Ls63zdKBiCXoqU5CuG6KqVbeMYydRqOuovP+DXx8g=\" name=\"csrf-token\" />\n</head>\n<body>\n\n<h1> Hello bitches </h1>\n\n<p> Lorem ipsum dolor sit amet. </p>\n\n\n</body>\n</html>\n"
|
56
|
+
|
57
|
+
html = [valid_html]
|
58
|
+
|
59
|
+
@app = lambda { |env| [200, {'Content-Type' => 'text/html'}, html] }
|
60
|
+
|
61
|
+
assets = Webconsole::Assets.new(@app)
|
62
|
+
response = assets.call({}).last.first
|
63
|
+
|
64
|
+
response.must_match /input name/m # html
|
65
|
+
response.must_match /text\/css/m # css
|
66
|
+
response.must_match /escapeHTML/m # js
|
67
|
+
end
|
68
|
+
|
69
|
+
it "works with Rails' particular conception of what a response is" do
|
70
|
+
|
71
|
+
valid_html = "<!DOCTYPE html>\n<html>\n<head>\n <title>Testapp</title>\n <link href=\"/assets/application.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />\n <script src=\"/assets/application.js\" type=\"text/javascript\"></script>\n <meta content=\"authenticity_token\" name=\"csrf-param\" />\n<meta content=\"26Ls63zdKBiCXoqU5CuG6KqVbeMYydRqOuovP+DXx8g=\" name=\"csrf-token\" />\n</head>\n<body>\n\n<h1> Hello bitches </h1>\n\n<p> Lorem ipsum dolor sit amet. </p>\n\n\n</body>\n</html>\n"
|
72
|
+
|
73
|
+
@app = lambda { |env| [200, {'Content-Type' => 'text/html'}, OpenStruct.new({:body => valid_html})] }
|
74
|
+
|
75
|
+
assets = Webconsole::Assets.new(@app)
|
76
|
+
|
77
|
+
response = assets.call({}).last.first
|
78
|
+
|
79
|
+
response.must_match /input name/m # html
|
80
|
+
response.must_match /text\/css/m # css
|
81
|
+
response.must_match /escapeHTML/m # js
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'exposes the request object to the console' do
|
85
|
+
valid_html = "<!DOCTYPE html>\n<html>\n<head>\n <title>Testapp</title>\n <link href=\"/assets/application.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />\n <script src=\"/assets/application.js\" type=\"text/javascript\"></script>\n <meta content=\"authenticity_token\" name=\"csrf-param\" />\n<meta content=\"26Ls63zdKBiCXoqU5CuG6KqVbeMYydRqOuovP+DXx8g=\" name=\"csrf-token\" />\n</head>\n<body>\n\n<h1> Hello bitches </h1>\n\n<p> Lorem ipsum dolor sit amet. </p>\n\n\n</body>\n</html>\n"
|
86
|
+
|
87
|
+
@app = lambda { |env| [200, {'Content-Type' => 'text/html'}, OpenStruct.new({:body => valid_html})] }
|
88
|
+
|
89
|
+
env = {'PATH_INFO' => '/some_path'}
|
90
|
+
assets = Webconsole::Assets.new(@app)
|
91
|
+
|
92
|
+
assets.call(env)
|
93
|
+
|
94
|
+
Webconsole::Repl.request.env['PATH_INFO'].must_equal '/some_path'
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module Rack
|
5
|
+
describe Webconsole::Repl do
|
6
|
+
|
7
|
+
it 'initializes with an app' do
|
8
|
+
@app = stub
|
9
|
+
@repl = Webconsole::Repl.new(@app)
|
10
|
+
|
11
|
+
@repl.instance_variable_get(:@app).must_equal @app
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#call" do
|
15
|
+
it 'evaluates the :query param in a sandbox and returns the result' do
|
16
|
+
@app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['hello world']] }
|
17
|
+
env = {}
|
18
|
+
Webconsole::Repl.stubs(:token).returns('abc')
|
19
|
+
request = OpenStruct.new(:params => {'query' => 'a = 4; a * 2', 'token' => 'abc'}, :post? => true)
|
20
|
+
Rack::Request.stubs(:new).returns request
|
21
|
+
|
22
|
+
@repl = Webconsole::Repl.new(@app)
|
23
|
+
|
24
|
+
response = @repl.call(env).last.first
|
25
|
+
|
26
|
+
MultiJson.decode(response)['result'].must_equal "8"
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'maintains local state in subsequent calls thanks to an evil global variable' do
|
30
|
+
@app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['hello world']] }
|
31
|
+
env = {}
|
32
|
+
Webconsole::Repl.stubs(:token).returns('abc')
|
33
|
+
request = OpenStruct.new(:params => {'query' => 'a = 4', 'token' => 'abc'}, :post? => true)
|
34
|
+
Rack::Request.stubs(:new).returns request
|
35
|
+
@repl = Webconsole::Repl.new(@app)
|
36
|
+
|
37
|
+
@repl.call(env) # call 1 sets a to 4
|
38
|
+
|
39
|
+
request = OpenStruct.new(:params => {'query' => 'a * 8', 'token' => 'abc'}, :post? => true)
|
40
|
+
Rack::Request.stubs(:new).returns request
|
41
|
+
|
42
|
+
response = @repl.call(env).last.first # call 2 retrieves a and multiplies it by 8
|
43
|
+
|
44
|
+
MultiJson.decode(response)['result'].must_equal "32"
|
45
|
+
$sandbox.instance_variable_get(:@locals)[:a].must_equal 4
|
46
|
+
$sandbox.instance_variable_get(:@locals).size.must_equal 1
|
47
|
+
end
|
48
|
+
|
49
|
+
it "returns any found errors prepended with 'Error:'" do
|
50
|
+
@app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['hello world']] }
|
51
|
+
env = {}
|
52
|
+
Webconsole::Repl.stubs(:token).returns('abc')
|
53
|
+
request = OpenStruct.new(:params => {'query' => 'unknown_method', 'token' => 'abc'}, :post? => true)
|
54
|
+
Rack::Request.stubs(:new).returns request
|
55
|
+
@repl = Webconsole::Repl.new(@app)
|
56
|
+
|
57
|
+
response = @repl.call(env).last.first
|
58
|
+
|
59
|
+
MultiJson.decode(response)['result'].must_match /Error:/
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'rejects non-post requests' do
|
63
|
+
@app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['hello world']] }
|
64
|
+
env = {}
|
65
|
+
Webconsole::Repl.stubs(:token).returns('abc')
|
66
|
+
request = OpenStruct.new(:params => {'query' => 'unknown_method', 'token' => 'abc'}, :post? => false)
|
67
|
+
Rack::Request.stubs(:new).returns request
|
68
|
+
@repl = Webconsole::Repl.new(@app)
|
69
|
+
|
70
|
+
$sandbox.expects(:instance_eval).never
|
71
|
+
|
72
|
+
@repl.call(env).must_equal @app.call(env)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'rejects requests with invalid token' do
|
76
|
+
@app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['hello world']] }
|
77
|
+
env = {}
|
78
|
+
Webconsole::Repl.stubs(:token).returns('abc')
|
79
|
+
request = OpenStruct.new(:params => {'query' => 'unknown_method', 'token' => 'cba'}, :post? => true)
|
80
|
+
Rack::Request.stubs(:new).returns request
|
81
|
+
@repl = Webconsole::Repl.new(@app)
|
82
|
+
|
83
|
+
$sandbox.expects(:instance_eval).never
|
84
|
+
|
85
|
+
@repl.call(env).must_equal @app.call(env)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe 'class methods' do
|
90
|
+
describe '#reset_token and #token' do
|
91
|
+
it 'returns the security token' do
|
92
|
+
Webconsole::Repl.reset_token
|
93
|
+
Webconsole::Repl.token.must_be_kind_of String
|
94
|
+
end
|
95
|
+
end
|
96
|
+
describe '#request= and #request' do
|
97
|
+
it 'returns the request object' do
|
98
|
+
request = stub
|
99
|
+
Webconsole::Repl.request = request
|
100
|
+
Webconsole::Repl.request.must_equal request
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
describe Webconsole::Sandbox do
|
5
|
+
|
6
|
+
describe "#method_missing" do
|
7
|
+
describe 'when the method exists in @locals' do
|
8
|
+
it 'retrieves it' do
|
9
|
+
@sandbox = Webconsole::Sandbox.new
|
10
|
+
@sandbox.instance_variable_set(:@locals, {:a => 123})
|
11
|
+
|
12
|
+
@sandbox.a.must_equal 123
|
13
|
+
end
|
14
|
+
end
|
15
|
+
describe 'otherwise' do
|
16
|
+
it 'raises a NoMethodError' do
|
17
|
+
@sandbox = Webconsole::Sandbox.new
|
18
|
+
|
19
|
+
lambda {
|
20
|
+
@sandbox.a
|
21
|
+
}.must_raise NoMethodError
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#reload!" do
|
27
|
+
it 'assigns a new, fresh Sandbox to the global variable' do
|
28
|
+
old_sandbox = $sandbox = Webconsole::Sandbox.new
|
29
|
+
|
30
|
+
$sandbox.reload!
|
31
|
+
|
32
|
+
$sandbox.wont_equal old_sandbox
|
33
|
+
end
|
34
|
+
it 'returns a feedback string' do
|
35
|
+
Webconsole::Sandbox.new.reload!.must_equal 'ok'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "request" do
|
40
|
+
it 'returns the request object' do
|
41
|
+
@sandbox = Webconsole::Sandbox.new
|
42
|
+
request = Rack::Request.new({'PATH_INFO' => '/some_path'})
|
43
|
+
Webconsole::Repl.request = request
|
44
|
+
|
45
|
+
@sandbox.request.must_equal request
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
describe Webconsole::Shell do
|
5
|
+
before do
|
6
|
+
Ripl.instance_variable_set(:@shell, nil)
|
7
|
+
end
|
8
|
+
|
9
|
+
describe ".eval_query" do
|
10
|
+
describe "sets Ripl::Shell" do
|
11
|
+
before { Webconsole::Shell.eval_query('2 + 2') }
|
12
|
+
|
13
|
+
it "#input" do
|
14
|
+
Ripl.shell.input.must_equal '2 + 2'
|
15
|
+
end
|
16
|
+
|
17
|
+
it "#result" do
|
18
|
+
Ripl.shell.result.must_equal 4
|
19
|
+
end
|
20
|
+
|
21
|
+
it "#history" do
|
22
|
+
Ripl.shell.history.must_equal ['2 + 2']
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "for one line input" do
|
27
|
+
def response
|
28
|
+
Webconsole::Shell.eval_query("'input'")
|
29
|
+
end
|
30
|
+
|
31
|
+
it ":result returns inspected result" do
|
32
|
+
response[:result].must_equal '"input"'
|
33
|
+
end
|
34
|
+
|
35
|
+
it ":prompt returns normal prompt" do
|
36
|
+
response[:prompt] = '>> '
|
37
|
+
end
|
38
|
+
|
39
|
+
it "returns false for multi_line keys" do
|
40
|
+
response[:multi_line].must_equal false
|
41
|
+
response[:previous_multi_line].must_equal false
|
42
|
+
end
|
43
|
+
|
44
|
+
it "with an error :result returns error raised" do
|
45
|
+
Webconsole::Shell.eval_query("blah")[:result].must_match /^Error:/
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "for multi line input" do
|
50
|
+
def response
|
51
|
+
[
|
52
|
+
Webconsole::Shell.eval_query("[1,2,3].map do |num|"),
|
53
|
+
Webconsole::Shell.eval_query("num ** 2"),
|
54
|
+
Webconsole::Shell.eval_query("end")
|
55
|
+
]
|
56
|
+
end
|
57
|
+
|
58
|
+
it "returns correct results" do
|
59
|
+
response.map {|e| e[:result] }.must_equal [nil, nil, "[1, 4, 9]"]
|
60
|
+
end
|
61
|
+
|
62
|
+
it "returns correct prompts" do
|
63
|
+
response.map {|e| e[:prompt] }.must_equal ['>> ', '|| ', '|| ']
|
64
|
+
end
|
65
|
+
|
66
|
+
it "returns correct multi_line keys" do
|
67
|
+
response.map {|e| e[:multi_line] }.must_equal [true, true, false]
|
68
|
+
response.map {|e| e[:previous_multi_line] }.must_equal [false, true, true]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
describe Webconsole do
|
5
|
+
|
6
|
+
it 'initializes with an app' do
|
7
|
+
@app = stub
|
8
|
+
@webconsole = Webconsole.new(@app)
|
9
|
+
|
10
|
+
@webconsole.instance_variable_get(:@app).must_equal @app
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#call" do
|
14
|
+
it 'delegates the call to the Repl middleware when the path is /webconsole' do
|
15
|
+
@app = stub
|
16
|
+
@webconsole = Webconsole.new(@app)
|
17
|
+
@env = {'PATH_INFO' => '/webconsole'}
|
18
|
+
|
19
|
+
repl = stub
|
20
|
+
Webconsole::Repl.expects(:new).with(@app).returns repl
|
21
|
+
repl.expects(:call).with @env
|
22
|
+
|
23
|
+
@webconsole.call(@env)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'passes the call to the Assets middleware otherwise' do
|
27
|
+
@app = stub
|
28
|
+
@webconsole = Webconsole.new(@app)
|
29
|
+
@env = {'PATH_INFO' => '/whatever'}
|
30
|
+
|
31
|
+
assets = stub
|
32
|
+
Webconsole::Assets.expects(:new).with(@app).returns assets
|
33
|
+
assets.expects(:call).with @env
|
34
|
+
|
35
|
+
@webconsole.call(@env)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'class methods' do
|
40
|
+
it '#inject_jquery accessors' do
|
41
|
+
Webconsole.inject_jquery.must_equal false
|
42
|
+
Webconsole.inject_jquery = true
|
43
|
+
Webconsole.inject_jquery.must_equal true
|
44
|
+
end
|
45
|
+
it '#key_code accessors' do
|
46
|
+
Webconsole.key_code.must_equal "96"
|
47
|
+
Webconsole.key_code = "97"
|
48
|
+
Webconsole.key_code.must_equal "97"
|
49
|
+
end
|
50
|
+
it '#key_code setter cast parameter type' do
|
51
|
+
Webconsole.key_code = 96
|
52
|
+
Webconsole.key_code.must_equal "96"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|