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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.travis-gemfile +15 -0
  4. data/.travis.yml +7 -0
  5. data/.yardopts +1 -0
  6. data/CHANGELOG.md +4 -0
  7. data/Gemfile +21 -0
  8. data/README.md +40 -0
  9. data/Rakefile +28 -0
  10. data/ballast.gemspec +35 -0
  11. data/doc/Ballast/Concerns/Ajax.html +806 -0
  12. data/doc/Ballast/Concerns/Common.html +900 -0
  13. data/doc/Ballast/Concerns/ErrorsHandling.html +283 -0
  14. data/doc/Ballast/Concerns/View.html +664 -0
  15. data/doc/Ballast/Concerns.html +127 -0
  16. data/doc/Ballast/Context.html +417 -0
  17. data/doc/Ballast/Errors/BaseError.html +326 -0
  18. data/doc/Ballast/Errors/InvalidDomain.html +157 -0
  19. data/doc/Ballast/Errors/PerformError.html +157 -0
  20. data/doc/Ballast/Errors/ValidationError.html +157 -0
  21. data/doc/Ballast/Errors.html +125 -0
  22. data/doc/Ballast/Operation.html +1304 -0
  23. data/doc/Ballast/OperationsChain.html +585 -0
  24. data/doc/Ballast/Version.html +189 -0
  25. data/doc/Ballast.html +130 -0
  26. data/doc/_index.html +267 -0
  27. data/doc/class_list.html +54 -0
  28. data/doc/css/common.css +1 -0
  29. data/doc/css/full_list.css +57 -0
  30. data/doc/css/style.css +338 -0
  31. data/doc/file.README.html +115 -0
  32. data/doc/file_list.html +56 -0
  33. data/doc/frames.html +26 -0
  34. data/doc/index.html +115 -0
  35. data/doc/js/app.js +219 -0
  36. data/doc/js/full_list.js +178 -0
  37. data/doc/js/jquery.js +4 -0
  38. data/doc/method_list.html +269 -0
  39. data/doc/top-level-namespace.html +112 -0
  40. data/lib/ballast/concerns/ajax.rb +116 -0
  41. data/lib/ballast/concerns/common.rb +97 -0
  42. data/lib/ballast/concerns/errors_handling.rb +49 -0
  43. data/lib/ballast/concerns/view.rb +63 -0
  44. data/lib/ballast/context.rb +38 -0
  45. data/lib/ballast/errors.rb +34 -0
  46. data/lib/ballast/operation.rb +136 -0
  47. data/lib/ballast/operations_chain.rb +38 -0
  48. data/lib/ballast/version.rb +24 -0
  49. data/lib/ballast.rb +24 -0
  50. data/spec/ballast/concerns/ajax_spec.rb +124 -0
  51. data/spec/ballast/concerns/common_spec.rb +100 -0
  52. data/spec/ballast/concerns/errors_handling_spec.rb +63 -0
  53. data/spec/ballast/concerns/view_spec.rb +107 -0
  54. data/spec/ballast/context_spec.rb +23 -0
  55. data/spec/ballast/errors_spec.rb +16 -0
  56. data/spec/ballast/operation_spec.rb +175 -0
  57. data/spec/ballast/operations_chain_spec.rb +33 -0
  58. data/spec/coverage_helper.rb +19 -0
  59. data/spec/spec_helper.rb +19 -0
  60. 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