ballast 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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