ballast 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis-gemfile +15 -0
- data/.travis.yml +7 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +21 -0
- data/README.md +40 -0
- data/Rakefile +28 -0
- data/ballast.gemspec +35 -0
- data/doc/Ballast/Concerns/Ajax.html +806 -0
- data/doc/Ballast/Concerns/Common.html +900 -0
- data/doc/Ballast/Concerns/ErrorsHandling.html +283 -0
- data/doc/Ballast/Concerns/View.html +664 -0
- data/doc/Ballast/Concerns.html +127 -0
- data/doc/Ballast/Context.html +417 -0
- data/doc/Ballast/Errors/BaseError.html +326 -0
- data/doc/Ballast/Errors/InvalidDomain.html +157 -0
- data/doc/Ballast/Errors/PerformError.html +157 -0
- data/doc/Ballast/Errors/ValidationError.html +157 -0
- data/doc/Ballast/Errors.html +125 -0
- data/doc/Ballast/Operation.html +1304 -0
- data/doc/Ballast/OperationsChain.html +585 -0
- data/doc/Ballast/Version.html +189 -0
- data/doc/Ballast.html +130 -0
- data/doc/_index.html +267 -0
- data/doc/class_list.html +54 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +57 -0
- data/doc/css/style.css +338 -0
- data/doc/file.README.html +115 -0
- data/doc/file_list.html +56 -0
- data/doc/frames.html +26 -0
- data/doc/index.html +115 -0
- data/doc/js/app.js +219 -0
- data/doc/js/full_list.js +178 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +269 -0
- data/doc/top-level-namespace.html +112 -0
- data/lib/ballast/concerns/ajax.rb +116 -0
- data/lib/ballast/concerns/common.rb +97 -0
- data/lib/ballast/concerns/errors_handling.rb +49 -0
- data/lib/ballast/concerns/view.rb +63 -0
- data/lib/ballast/context.rb +38 -0
- data/lib/ballast/errors.rb +34 -0
- data/lib/ballast/operation.rb +136 -0
- data/lib/ballast/operations_chain.rb +38 -0
- data/lib/ballast/version.rb +24 -0
- data/lib/ballast.rb +24 -0
- data/spec/ballast/concerns/ajax_spec.rb +124 -0
- data/spec/ballast/concerns/common_spec.rb +100 -0
- data/spec/ballast/concerns/errors_handling_spec.rb +63 -0
- data/spec/ballast/concerns/view_spec.rb +107 -0
- data/spec/ballast/context_spec.rb +23 -0
- data/spec/ballast/errors_spec.rb +16 -0
- data/spec/ballast/operation_spec.rb +175 -0
- data/spec/ballast/operations_chain_spec.rb +33 -0
- data/spec/coverage_helper.rb +19 -0
- data/spec/spec_helper.rb +19 -0
- metadata +225 -0
@@ -0,0 +1,124 @@
|
|
1
|
+
#
|
2
|
+
# This file is part of the ballast gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
|
3
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
4
|
+
#
|
5
|
+
|
6
|
+
require "spec_helper"
|
7
|
+
|
8
|
+
describe Ballast::Concerns::Ajax do
|
9
|
+
class AjaxMockClass < OpenStruct
|
10
|
+
include Ballast::Concerns::Ajax
|
11
|
+
|
12
|
+
def initialize(attrs)
|
13
|
+
@operation = OpenStruct.new(attrs.delete(:operation))
|
14
|
+
super(attrs)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
subject{ AjaxMockClass.new(response: OpenStruct.new(headers: {}), headers: {}, params: {}, performed?: false) }
|
19
|
+
|
20
|
+
describe "#is_ajax?" do
|
21
|
+
it "should return false by default" do
|
22
|
+
expect(AjaxMockClass.new(request: {}, params: {}).is_ajax?).to be(false)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should return true when the request is XHR" do
|
26
|
+
expect(AjaxMockClass.new(request: OpenStruct.new(xhr?: true)).is_ajax?).to be(true)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should return true when the parameter is overriden" do
|
30
|
+
expect(AjaxMockClass.new(params: {xhr: true}).is_ajax?).to be(true)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#prepare_ajax" do
|
35
|
+
it "should return a default hash" do
|
36
|
+
expect(subject.prepare_ajax).to be_a(HashWithIndifferentAccess)
|
37
|
+
expect(subject.prepare_ajax).to eq({status: :ok}.with_indifferent_access)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should accept overrides for the status" do
|
41
|
+
expect(subject.prepare_ajax(:forbidden)).to eq({status: :forbidden}.with_indifferent_access)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should accept overrides for the data" do
|
45
|
+
expect(subject.prepare_ajax(:ok, "DATA")).to eq({status: :ok, data: "DATA"}.with_indifferent_access)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should accept overrides for the error" do
|
49
|
+
expect(subject.prepare_ajax(:ok, nil, "ERROR")).to eq({status: :ok, error: "ERROR"}.with_indifferent_access)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#send_ajax" do
|
54
|
+
before(:each) do
|
55
|
+
allow(subject).to receive(:render) {|args| args}
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should prepare the data if the data is not already an Hash" do
|
59
|
+
expect(subject).to receive(:prepare_ajax).with(:ok, "DATA").and_call_original
|
60
|
+
expect(subject).to receive(:render).with(json: "{\"status\":200,\"data\":\"DATA\"}", status: 200, callback: nil, content_type: nil)
|
61
|
+
subject.send_ajax("DATA")
|
62
|
+
end
|
63
|
+
|
64
|
+
it "translate HTTP status" do
|
65
|
+
expect(Rack::Utils).to receive(:status_code).with(:forbidden).and_call_original
|
66
|
+
expect(subject.send_ajax("DATA", status: :forbidden)[:status]).to eq(403)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should setup the right content type for text" do
|
70
|
+
expect(subject).to receive(:render).with(text: "{\"status\":200,\"data\":\"DATA\"}", status: 200, callback: nil, content_type: "text/plain")
|
71
|
+
subject.send_ajax("DATA", format: :text)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should set the right callback for JSONP" do
|
75
|
+
subject.params[:callback] = "callback"
|
76
|
+
expect(subject).to receive(:render).with(jsonp: "{\"status\":200,\"data\":\"DATA\"}", status: 200, callback: "callback", content_type: nil)
|
77
|
+
subject.send_ajax("DATA", format: :jsonp)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "#update_ajax" do
|
82
|
+
it "should merge the data from successful operations" do
|
83
|
+
subject = AjaxMockClass.new(operation: {success?: true, response: {data: "DATA"}})
|
84
|
+
expect(subject.update_ajax({existing: true})).to eq({existing: true, data: "DATA"})
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should merge the data from failed operations" do
|
88
|
+
subject = AjaxMockClass.new(operation: {success?: false, errors: ["ERROR"]})
|
89
|
+
expect(subject.update_ajax({existing: true})).to eq({existing: true, error: "ERROR"})
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "#prevent_caching" do
|
94
|
+
it "should append correct headers" do
|
95
|
+
subject.prevent_caching
|
96
|
+
|
97
|
+
expect(subject.response.headers).to eq({
|
98
|
+
"Cache-Control" => "no-cache, no-store, max-age=0, must-revalidate",
|
99
|
+
"Pragma" => "no-cache",
|
100
|
+
"Expires" => "Fri, 01 Jan 1990 00:00:00 GMT"
|
101
|
+
})
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "#allow_cors" do
|
106
|
+
it "should append correct headers" do
|
107
|
+
subject.allow_cors
|
108
|
+
|
109
|
+
expect(subject.headers).to eq({
|
110
|
+
"Access-Control-Allow-Origin" => "*",
|
111
|
+
"Access-Control-Allow-Methods" => "POST, GET, OPTIONS",
|
112
|
+
"Access-Control-Allow-Headers" => "*",
|
113
|
+
"Access-Control-Max-Age" => "31557600"
|
114
|
+
})
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "#disallow_robots" do
|
119
|
+
it "should disallow robots outputting a text view" do
|
120
|
+
expect(subject).to receive(:render).with(text: "User-agent: *\nDisallow: /", content_type: "text/plain")
|
121
|
+
subject.disallow_robots
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
#
|
2
|
+
# This file is part of the ballast gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
|
3
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
4
|
+
#
|
5
|
+
|
6
|
+
require "spec_helper"
|
7
|
+
|
8
|
+
describe Ballast::Concerns::Common do
|
9
|
+
class CommonMockClass < OpenStruct
|
10
|
+
include Ballast::Concerns::Common
|
11
|
+
end
|
12
|
+
|
13
|
+
class OperationMockClass
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
module Actions
|
18
|
+
module CommonMockClass
|
19
|
+
class Sub
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
subject{ CommonMockClass.new(request: OpenStruct.new(headers: {}), headers: {}, params: {}, performed?: false) }
|
25
|
+
|
26
|
+
describe "#sending_data?" do
|
27
|
+
it "should return the current status" do
|
28
|
+
expect(CommonMockClass.new(request: OpenStruct.new(post?: false, put?: false)).sending_data?).to be(false)
|
29
|
+
expect(CommonMockClass.new(request: OpenStruct.new(post?: true, put?: false)).sending_data?).to be(true)
|
30
|
+
expect(CommonMockClass.new(request: OpenStruct.new(post?: false, put?: true)).sending_data?).to be(true)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#perform_operation" do
|
35
|
+
it "should perform the requested operation and memoize it" do
|
36
|
+
expect(OperationMockClass).to receive(:perform).with("OWNER", a: 1, b: 2).and_return("OPERATION 1")
|
37
|
+
expect(OperationMockClass).to receive(:perform).with(subject, c: 3, d: 4).and_return("OPERATION 2")
|
38
|
+
|
39
|
+
subject.perform_operation(OperationMockClass, "OWNER", a: 1, b: 2)
|
40
|
+
expect(subject.instance_variable_get(:@operation)).to eq("OPERATION 1")
|
41
|
+
subject.perform_operation(OperationMockClass, c: 3, d: 4)
|
42
|
+
expect(subject.instance_variable_get(:@operation)).to eq("OPERATION 2")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "#format_short_duration" do
|
47
|
+
it "should format a date" do
|
48
|
+
now = DateTime.civil(2013, 12, 9, 15, 6, 00)
|
49
|
+
expect(subject.format_short_duration(DateTime.civil(2013, 12, 9, 16, 6, 0), now, "ago")).to eq("now")
|
50
|
+
expect(subject.format_short_duration(DateTime.civil(2013, 12, 9, 15, 5, 58), now, "")).to eq("2s")
|
51
|
+
expect(subject.format_short_duration(DateTime.civil(2013, 12, 9, 15, 3, 0), now, " in the past")).to eq("3m in the past")
|
52
|
+
expect(subject.format_short_duration(DateTime.civil(2013, 12, 9, 8, 6, 0), now, "")).to eq("7h")
|
53
|
+
expect(subject.format_short_duration(DateTime.civil(2013, 5, 3, 15, 6, 0), now, "")).to eq("May 03")
|
54
|
+
expect(subject.format_short_duration(DateTime.civil(2011, 6, 4, 15, 6, 0), now, "")).to eq("Jun 04 2011")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#format_long_date" do
|
59
|
+
before(:each) do
|
60
|
+
expect_any_instance_of(DateTime).to receive(:dst?).and_return(true)
|
61
|
+
expect(Time).to receive(:zone).at_least(1).and_return(ActiveSupport::TimeZone["UTC"], ActiveSupport::TimeZone["Pacific Time (US & Canada)"])
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should format a date" do
|
65
|
+
expect(subject.format_long_date(DateTime.civil(2013, 7, 11, 10, 9, 8))).to eq("10:09AM • Jul 11th, 2013 (UTC)")
|
66
|
+
expect(subject.format_long_date(DateTime.civil(2013, 7, 11, 10, 9, 8), "SEP", "%F %T %o %- %:Z")).to eq("2013-07-11 10:09:08 11th SEP Pacific Time (US & Canada) (DST)")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#authenticate_user" do
|
71
|
+
it "should ask for authentication and yield the authenticator block" do
|
72
|
+
output = nil
|
73
|
+
expect(subject).to receive(:authenticate_with_http_basic) {|&block| block.call("USER", "PASSWORD") }
|
74
|
+
|
75
|
+
subject.authenticate_user { |*args| output = args }
|
76
|
+
expect(output).to eq(["USER", "PASSWORD"])
|
77
|
+
end
|
78
|
+
|
79
|
+
it "in case of failure, it should set error" do
|
80
|
+
expect(subject).to receive(:authenticate_with_http_basic).and_return(false)
|
81
|
+
expect(subject).to receive(:handle_error)
|
82
|
+
subject.authenticate_user
|
83
|
+
|
84
|
+
expect(subject.headers["WWW-Authenticate"]).to eq("Basic realm=\"Private Area\"")
|
85
|
+
expect(subject.instance_variable_get(:@error_title)).to eq("Authentication required.")
|
86
|
+
expect(subject.instance_variable_get(:@error_message)).to eq("To view this resource you have to authenticate.")
|
87
|
+
expect(subject.instance_variable_get(:@error_code)).to eq(401)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "in case of failure, it should show custom messages" do
|
91
|
+
expect(subject).to receive(:authenticate_with_http_basic).and_return(false)
|
92
|
+
expect(subject).to receive(:handle_error)
|
93
|
+
subject.authenticate_user("AREA", "TITLE", "MESSAGE")
|
94
|
+
|
95
|
+
expect(subject.headers["WWW-Authenticate"]).to eq("Basic realm=\"AREA\"")
|
96
|
+
expect(subject.instance_variable_get(:@error_title)).to eq("TITLE")
|
97
|
+
expect(subject.instance_variable_get(:@error_message)).to eq("MESSAGE")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#
|
2
|
+
# This file is part of the ballast gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
|
3
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
4
|
+
#
|
5
|
+
|
6
|
+
require "spec_helper"
|
7
|
+
|
8
|
+
describe Ballast::Concerns::ErrorsHandling do
|
9
|
+
class ErrorsHandlingMockClass < OpenStruct
|
10
|
+
include Ballast::Concerns::Ajax
|
11
|
+
include Ballast::Concerns::ErrorsHandling
|
12
|
+
|
13
|
+
def initialize(attrs)
|
14
|
+
@operation = OpenStruct.new(attrs.delete(:operation))
|
15
|
+
super(attrs)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
subject{ ErrorsHandlingMockClass.new(response: OpenStruct.new(headers: {}), headers: {}, params: {}, performed?: false) }
|
20
|
+
|
21
|
+
describe "#handle_error" do
|
22
|
+
it "should handle a custom error" do
|
23
|
+
error = {status: "STATUS", error: "ERROR"}
|
24
|
+
expect(subject).to receive(:send_or_render_error).with("LAYOUT")
|
25
|
+
subject.handle_error(error, "LAYOUT", "TITLE")
|
26
|
+
|
27
|
+
expect(subject.instance_variable_get(:@error)).to eq(error)
|
28
|
+
expect(subject.instance_variable_get(:@error_code)).to eq("STATUS")
|
29
|
+
expect(subject.instance_variable_get(:@error_title)).to eq("TITLE")
|
30
|
+
expect(subject.instance_variable_get(:@error_message)).to eq("ERROR")
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should handle a debug error" do
|
34
|
+
expect(subject).to receive(:send_or_render_error)
|
35
|
+
subject.handle_error(Lazier::Exceptions::Debug.new("MESSAGE"))
|
36
|
+
|
37
|
+
expect(subject.instance_variable_get(:@error_code)).to eq(503)
|
38
|
+
expect(subject.instance_variable_get(:@error_title)).to eq("Debug")
|
39
|
+
expect(subject.instance_variable_get(:@error_message)).to be_nil
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should handle every other error" do
|
43
|
+
expect(subject).to receive(:send_or_render_error)
|
44
|
+
subject.handle_error(RuntimeError.new("ERROR"))
|
45
|
+
|
46
|
+
expect(subject.instance_variable_get(:@error_code)).to eq(500)
|
47
|
+
expect(subject.instance_variable_get(:@error_title)).to eq("Error - RuntimeError")
|
48
|
+
expect(subject.instance_variable_get(:@error_message)).to be_nil
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should render an AJAX error" do
|
52
|
+
expect_any_instance_of(RuntimeError).to receive(:backtrace).and_return(["A", "B"])
|
53
|
+
expect(subject).to receive(:is_ajax?).and_return(true)
|
54
|
+
expect(subject).to receive(:send_ajax).with({status: 500, error: "ERROR", data: {type: "Error - RuntimeError", backtrace: "A\nB"}}.with_indifferent_access)
|
55
|
+
subject.handle_error(RuntimeError.new("ERROR"), "LAYOUT")
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should render a HTML error" do
|
59
|
+
expect(subject).to receive(:render).with(nothing: true, status: 500, layout: "LAYOUT", formats: [:html])
|
60
|
+
subject.handle_error(RuntimeError.new("ERROR"), "LAYOUT")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
#
|
2
|
+
# This file is part of the ballast gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
|
3
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
4
|
+
#
|
5
|
+
|
6
|
+
require "spec_helper"
|
7
|
+
|
8
|
+
describe Ballast::Concerns::View do
|
9
|
+
class ViewMockClass < OpenStruct
|
10
|
+
include Ballast::Concerns::View
|
11
|
+
|
12
|
+
def initialize(attrs)
|
13
|
+
@operation = OpenStruct.new(attrs.delete(:operation))
|
14
|
+
super(attrs)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
subject{ ViewMockClass.new(response: OpenStruct.new(headers: {}), headers: {}, params: {}, performed?: false) }
|
19
|
+
|
20
|
+
describe "#scope_css" do
|
21
|
+
it "should return the CSS namespace" do
|
22
|
+
expect(subject).to receive(:controller_path).and_return("NAME/CONTROLLER")
|
23
|
+
expect(subject).to receive(:action_name).and_return("ACTION")
|
24
|
+
expect(subject.scope_css).to eq("NAME-CONTROLLER ACTION")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#browser" do
|
29
|
+
it "should return a browser object" do
|
30
|
+
expect(subject).to receive(:request).and_return(OpenStruct.new(user_agent: "AGENT"))
|
31
|
+
expect(Brauser::Browser).to receive(:new).with("AGENT").and_return("BROWSER")
|
32
|
+
expect(subject.browser).to eq("BROWSER")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#browser_supported?" do
|
37
|
+
before(:each) do
|
38
|
+
expect(subject).to receive(:request).and_return(OpenStruct.new(user_agent: "AGENT"))
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should check if a browser is supported" do
|
42
|
+
expect(subject.browser).to receive(:supported?).with("CONF").and_return("SUPPORTED")
|
43
|
+
expect(subject.browser_supported?("CONF")).to eq("SUPPORTED")
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should use a default file" do
|
47
|
+
class Rails
|
48
|
+
def self.root
|
49
|
+
Pathname.new("ROOT")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
expect(subject.browser).to receive(:supported?).with("ROOT/config/supported-browsers.yml").and_return(true)
|
54
|
+
subject.browser_supported?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#javascript_params" do
|
59
|
+
before(:each) do
|
60
|
+
subject.instance_variable_set(:@javascript_params, {a: "1", b: 2})
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should output Javascript as HTML" do
|
64
|
+
expect(subject).to receive(:content_tag).with(:tag, '{"a":"1","b":2}', {"data-jid" => "ID"}).and_return("HTML")
|
65
|
+
expect(subject.javascript_params(true, :tag, "ID")).to eq("HTML")
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should return Javascript as Hash" do
|
69
|
+
expect(subject.javascript_params(false)).to eq({a: "1", b: 2})
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "#add_javascript_params" do
|
74
|
+
before(:each) do
|
75
|
+
subject.add_javascript_params(:a, {b: 1})
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should create an Hash" do
|
79
|
+
expect(subject.instance_variable_get(:@javascript_params)).to be_a(HashWithIndifferentAccess)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should add new keys" do
|
83
|
+
subject.add_javascript_params(:c, {d: 2})
|
84
|
+
expect(subject.instance_variable_get(:@javascript_params)).to eq({a: {b: 1}, c: {d: 2}}.with_indifferent_access)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should merge values for the same key" do
|
88
|
+
subject.add_javascript_params(:a, {d: 2})
|
89
|
+
expect(subject.instance_variable_get(:@javascript_params)).to eq({a: {b: 1, d: 2}}.with_indifferent_access)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should replace values for same key if requested to" do
|
93
|
+
subject.add_javascript_params(:a, {d: 2}, true)
|
94
|
+
expect(subject.instance_variable_get(:@javascript_params)).to eq({a: {d: 2}}.with_indifferent_access)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should merge from the root" do
|
98
|
+
subject.add_javascript_params(nil, {d: 2})
|
99
|
+
expect(subject.instance_variable_get(:@javascript_params)).to eq({a: {b: 1}, d: 2}.with_indifferent_access)
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should replace the entire hash" do
|
103
|
+
subject.add_javascript_params(nil, {d: 2}, true)
|
104
|
+
expect(subject.instance_variable_get(:@javascript_params)).to eq({d: 2}.with_indifferent_access)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
#
|
2
|
+
# This file is part of the ballast gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
|
3
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
4
|
+
#
|
5
|
+
|
6
|
+
require "spec_helper"
|
7
|
+
|
8
|
+
describe Ballast::Context do
|
9
|
+
describe ".build" do
|
10
|
+
it "should prepare data" do
|
11
|
+
expect(Interactor::Context).to receive(:build).with({owner: "OWNER", errors: [], output: nil, response: an_instance_of(HashWithIndifferentAccess), a: 1, b: 2}.with_indifferent_access)
|
12
|
+
Ballast::Context.build("OWNER", {a: 1, b: 2})
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#method_missing" do
|
17
|
+
it "should lookup for keys in the object" do
|
18
|
+
context = Ballast::Context.build(nil, {output: 1})
|
19
|
+
expect(context.output).to eq(1)
|
20
|
+
expect { context.input }.to raise_error(NoMethodError)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#
|
2
|
+
# This file is part of the ballast gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
|
3
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
4
|
+
#
|
5
|
+
|
6
|
+
require "spec_helper"
|
7
|
+
|
8
|
+
describe Ballast::Errors::BaseError do
|
9
|
+
describe ".initialize" do
|
10
|
+
it "should propagate the message also as a response" do
|
11
|
+
reference = Ballast::Errors::BaseError.new("ERROR")
|
12
|
+
expect(reference.message).to eq("ERROR")
|
13
|
+
expect(reference.response).to eq("ERROR")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
#
|
2
|
+
# This file is part of the ballast gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
|
3
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
4
|
+
#
|
5
|
+
|
6
|
+
require "spec_helper"
|
7
|
+
|
8
|
+
describe Ballast::Operation do
|
9
|
+
describe ".perform" do
|
10
|
+
it "should call the superclass implementation with no changes if the first arg is of the right class" do
|
11
|
+
context = Ballast::Context.new
|
12
|
+
|
13
|
+
expect(Ballast::Context).not_to receive(:build)
|
14
|
+
|
15
|
+
Ballast::Operation.perform(context)
|
16
|
+
Ballast::Operation.perform(nil, context: context)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should create a context on the fly if the first arg is NOT of the right class" do
|
20
|
+
expect(Ballast::Context).to receive(:build).with("OWNER", {}).and_return("ONTHEFLY")
|
21
|
+
Ballast::Operation.perform("OWNER")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#initialize" do
|
26
|
+
it "should save the context and then call #setup" do
|
27
|
+
expect_any_instance_of(Ballast::Operation).to receive(:setup)
|
28
|
+
subject = Ballast::Operation.new("CONTEXT")
|
29
|
+
|
30
|
+
expect(subject.context).to eq("CONTEXT")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#in_em_thread" do
|
35
|
+
it "should yield the block in EM::Synchrony thread" do
|
36
|
+
counter = 0
|
37
|
+
allow(EM).to receive(:reactor_running?).and_return(true)
|
38
|
+
expect(EM::Synchrony).to receive(:defer){|&block| block.call }
|
39
|
+
|
40
|
+
Ballast::Operation.new({}).in_em_thread { counter = 1 }
|
41
|
+
expect(counter).to eq(1)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#setup_response" do
|
46
|
+
it "should save instance variables into the context response" do
|
47
|
+
subject = Ballast::Operation.new(Ballast::Context.build(nil, response: {a: 1, b: 2, c: 3, d: 4}))
|
48
|
+
subject.instance_variable_set(:@first, "A")
|
49
|
+
subject.instance_variable_set(:@second, "B")
|
50
|
+
subject.instance_variable_set(:@third, "C")
|
51
|
+
|
52
|
+
subject.setup_response
|
53
|
+
expect(subject.response).to eq({a: 1, b: 2, c: 3, d: 4, first: "A", second: "B", third: "C"}.with_indifferent_access)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should not do anything if interactor failed" do
|
57
|
+
subject = Ballast::Operation.new(Ballast::Context.build(nil, response: {a: 1, b: 2, c: 3, d: 4}))
|
58
|
+
expect(subject).to receive(:success?).and_return(false)
|
59
|
+
|
60
|
+
subject.instance_variable_set(:@first, "A")
|
61
|
+
subject.instance_variable_set(:@second, "B")
|
62
|
+
subject.instance_variable_set(:@third, "C")
|
63
|
+
|
64
|
+
subject.setup_response
|
65
|
+
expect(subject.response).to eq({a: 1, b: 2, c: 3, d: 4}.with_indifferent_access)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "#import_response" do
|
70
|
+
before(:each) do
|
71
|
+
@subject = Ballast::Operation.new(Ballast::Context.build(nil, response: {a: 1, b: 2, c: 3, d: 4}))
|
72
|
+
@target = Object.new
|
73
|
+
@target.instance_variable_set(:@c, 1)
|
74
|
+
@target.instance_variable_set(:@d, 2)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should load instance variables from the context response" do
|
78
|
+
@subject.import_response(@target, :a, :b)
|
79
|
+
expect(@target.instance_variable_get(:@a)).to eq(1)
|
80
|
+
expect(@target.instance_variable_get(:@b)).to eq(2)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should overwrite a variable by default" do
|
84
|
+
expect { @subject.import_response(@target, :c) }.not_to raise_error
|
85
|
+
expect(@target.instance_variable_get(:@c)).to eq(3)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should raise an error if a variable is already defined and overwrite is disabled" do
|
89
|
+
expect { @subject.import_response(@target, :c, overwrite: false) }.to raise_error(ArgumentError)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "#perform_with_handling" do
|
94
|
+
before(:each) do
|
95
|
+
@subject = Ballast::Operation.new({})
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should yield the block" do
|
99
|
+
expect(@subject).to receive(:setup_response)
|
100
|
+
|
101
|
+
counter = 0
|
102
|
+
@subject.perform_with_handling { counter = 1 }
|
103
|
+
expect(counter).to eq(1)
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should propagate debug dumps" do
|
107
|
+
expect { @subject.perform_with_handling { "DEBUG".for_debug } }.to raise_error(Lazier::Exceptions::Debug)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should handle BaseError" do
|
111
|
+
expect(@subject).to receive(:setup_response)
|
112
|
+
expect(@subject).to receive(:fail!).with("RESPONSE")
|
113
|
+
expect { @subject.perform_with_handling { raise Ballast::Errors::BaseError.new("RESPONSE") } }.not_to raise_error
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should propagate the error otherwise" do
|
117
|
+
expect { @subject.perform_with_handling { raise RuntimeError.new("ERROR") } }.to raise_error(RuntimeError)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "#fail!" do
|
122
|
+
it "should append the error and mark the failure" do
|
123
|
+
subject = Ballast::Operation.new(Ballast::Context.build(nil))
|
124
|
+
subject.fail!("NO")
|
125
|
+
|
126
|
+
expect(subject.failure?).to be(true)
|
127
|
+
expect(subject.errors).to eq(["NO"])
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "#import_error" do
|
132
|
+
before(:each) do
|
133
|
+
@subject = Ballast::Operation.new(Ballast::Context.build(nil, errors: [{status: 401, error: "Unauthorized"}, {status: 403, error: "Forbidden"}]))
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should set the flash of the target" do
|
137
|
+
target = OpenStruct.new(flash: {})
|
138
|
+
@subject.import_error(target)
|
139
|
+
expect(target.flash[:error]).to eq("Unauthorized")
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should set instance variable if request to" do
|
143
|
+
target = OpenStruct.new(flash: {})
|
144
|
+
@subject.import_error(target, false)
|
145
|
+
expect(target.instance_variable_get(:@error)).to eq({status: 401, error: "Unauthorized"}.with_indifferent_access)
|
146
|
+
end
|
147
|
+
|
148
|
+
it "should import all errors if requested to" do
|
149
|
+
target = OpenStruct.new(flash: {})
|
150
|
+
@subject.import_error(target, true, false)
|
151
|
+
@subject.import_error(target, false, false)
|
152
|
+
expect(target.flash[:error]).to eq(["Unauthorized", "Forbidden"])
|
153
|
+
expect(target.instance_variable_get(:@error)).to eq([{status: 401, error: "Unauthorized"}.with_indifferent_access, {status: 403, error: "Forbidden"}.with_indifferent_access])
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe "#resolve_error" do
|
158
|
+
it "should format AJAX error" do
|
159
|
+
subject = Ballast::Operation.new({})
|
160
|
+
expect(subject.resolve_error(nil)).to eq({status: 500, error: "Oops! We're having some issue. Please try again later."})
|
161
|
+
expect(subject.resolve_error("A")).to eq({status: 500, error: "Oops! We're having some issue. Please try again later."})
|
162
|
+
expect(subject.resolve_error("A", {500 => "ERROR"})).to eq({status: 500, error: "ERROR"})
|
163
|
+
expect(subject.resolve_error(OpenStruct.new(response: 403))).to eq({status: 403, error: "Oops! We're having some issue. Please try again later."})
|
164
|
+
expect(subject.resolve_error(OpenStruct.new(response: 403), {403 => "ERROR"})).to eq({status: 403, error: "ERROR"})
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe "method_missing" do
|
169
|
+
it "should forward call to the owner" do
|
170
|
+
subject = Ballast::Operation.new(OpenStruct.new(owner: " ABC "))
|
171
|
+
expect(subject.strip).to eq("ABC")
|
172
|
+
expect { subject.not_strip }.to raise_error(NoMethodError)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#
|
2
|
+
# This file is part of the ballast gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
|
3
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
4
|
+
#
|
5
|
+
|
6
|
+
require "spec_helper"
|
7
|
+
|
8
|
+
describe Ballast::OperationsChain do
|
9
|
+
describe ".perform" do
|
10
|
+
before(:each) do
|
11
|
+
expect_any_instance_of(Ballast::OperationsChain).to receive(:perform)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should initialize with the set of operations and the first argument as context" do
|
15
|
+
context = Ballast::Context.new
|
16
|
+
expect(Ballast::Context).not_to receive(:build)
|
17
|
+
expect(Ballast::OperationsChain).to receive(:new).with([:A, :B, :C], context).and_call_original
|
18
|
+
Ballast::OperationsChain.perform(context, [:A, :B, :C])
|
19
|
+
end
|
20
|
+
|
21
|
+
it "shuold use the provided owner and context" do
|
22
|
+
context = Ballast::Context.new
|
23
|
+
expect(Ballast::Context).not_to receive(:build)
|
24
|
+
expect(Ballast::OperationsChain).to receive(:new).with([:A, :B, :C], context).and_call_original
|
25
|
+
Ballast::OperationsChain.perform(nil, [:A, :B, :C], context: context)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should created the context on the fly if needed" do
|
29
|
+
expect(Ballast::Context).to receive(:build).with("A", {a: 1})
|
30
|
+
Ballast::OperationsChain.perform("A", [:A, :B, :C], params: {a: 1})
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#
|
2
|
+
# This file is part of the ballast gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
|
3
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
4
|
+
#
|
5
|
+
|
6
|
+
require "pathname"
|
7
|
+
require "simplecov"
|
8
|
+
require "coveralls"
|
9
|
+
|
10
|
+
Coveralls.wear! if ENV["CI"] || ENV["JENKINS_URL"] # Do not load outside Travis
|
11
|
+
|
12
|
+
SimpleCov.start do
|
13
|
+
root = Pathname.new(File.dirname(__FILE__)) + ".."
|
14
|
+
|
15
|
+
add_filter do |src_file|
|
16
|
+
path = Pathname.new(src_file.filename).relative_path_from(root).to_s
|
17
|
+
path !~ /^lib/
|
18
|
+
end
|
19
|
+
end
|