ballast 1.0.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 +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
|