action_logic 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cb88bf14abd86df63ae10d679391a032ac66245f
4
- data.tar.gz: 9a98e3cf999ed6390c9f239960945c2f2efb9767
3
+ metadata.gz: b6d36552e9a95bd397f6841f5ea609b870885349
4
+ data.tar.gz: f2e121ac6007fcd73d09a88988053adc7892fe4a
5
5
  SHA512:
6
- metadata.gz: 3a6102dceee026431347f921772b4919584a8253bbb54a1fdd68b64adbc84f3c0aa970b686735c20871dfb14a56913d1e0a3c18ca0f7a06535402c9b30e111ce
7
- data.tar.gz: 50db9e8264312f9cd1a7dd7f38e1bf0ee711bcdfd895bce653a316e4d5e09a5d1b4346946033c59d186799b825ed6dad62a39b2f8c4dc0522a373f6f4206df4c
6
+ metadata.gz: 8ae1e86d81b1d5799d95089eb6319402610046880d7c1856ce420fba6865a6029191e606d5093c595e839179773b16e91f10fb53bc4e1489300fd9202474afb0
7
+ data.tar.gz: 869e66643e26a901d707f4887539649ebe13e160199fc361190099be4b46dfe0761eb3e75d40805e12f8db72faf2c92f9ae865d375ce2ca66238f3686a008488
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ coverage/
2
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,72 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ action_logic (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coderay (1.1.0)
10
+ coveralls (0.8.3)
11
+ json (~> 1.8)
12
+ rest-client (>= 1.6.8, < 2)
13
+ simplecov (~> 0.10.0)
14
+ term-ansicolor (~> 1.3)
15
+ thor (~> 0.19.1)
16
+ diff-lcs (1.2.5)
17
+ docile (1.1.5)
18
+ domain_name (0.5.25)
19
+ unf (>= 0.0.5, < 1.0.0)
20
+ http-cookie (1.0.2)
21
+ domain_name (~> 0.5)
22
+ json (1.8.3)
23
+ method_source (0.8.2)
24
+ mime-types (2.6.2)
25
+ netrc (0.11.0)
26
+ pry (0.10.3)
27
+ coderay (~> 1.1.0)
28
+ method_source (~> 0.8.1)
29
+ slop (~> 3.4)
30
+ rest-client (1.8.0)
31
+ http-cookie (>= 1.0.2, < 2.0)
32
+ mime-types (>= 1.16, < 3.0)
33
+ netrc (~> 0.7)
34
+ rspec (3.3.0)
35
+ rspec-core (~> 3.3.0)
36
+ rspec-expectations (~> 3.3.0)
37
+ rspec-mocks (~> 3.3.0)
38
+ rspec-core (3.3.2)
39
+ rspec-support (~> 3.3.0)
40
+ rspec-expectations (3.3.1)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.3.0)
43
+ rspec-mocks (3.3.2)
44
+ diff-lcs (>= 1.2.0, < 2.0)
45
+ rspec-support (~> 3.3.0)
46
+ rspec-support (3.3.0)
47
+ simplecov (0.10.0)
48
+ docile (~> 1.1.0)
49
+ json (~> 1.8)
50
+ simplecov-html (~> 0.10.0)
51
+ simplecov-html (0.10.0)
52
+ slop (3.6.0)
53
+ term-ansicolor (1.3.2)
54
+ tins (~> 1.0)
55
+ thor (0.19.1)
56
+ tins (1.6.0)
57
+ unf (0.1.4)
58
+ unf_ext
59
+ unf_ext (0.0.7.1)
60
+
61
+ PLATFORMS
62
+ ruby
63
+
64
+ DEPENDENCIES
65
+ action_logic!
66
+ coveralls (~> 0.8.3)
67
+ pry (~> 0.10)
68
+ rspec (~> 3.3)
69
+ simplecov (~> 0.10.0)
70
+
71
+ BUNDLED WITH
72
+ 1.10.6
data/README.md ADDED
@@ -0,0 +1,6 @@
1
+ # ActionLogic
2
+ [![Gem Version](https://badge.fury.io/rb/action_logic.svg)](https://badge.fury.io/rb/action_logic)
3
+ [![Code Climate](https://codeclimate.com/github/rewinfrey/action_logic/badges/gpa.svg)](https://codeclimate.com/github/rewinfrey/action_logic)
4
+ [![Coverage Status](https://coveralls.io/repos/rewinfrey/action_logic/badge.svg?branch=master&service=github)](https://coveralls.io/github/rewinfrey/action_logic?branch=master)
5
+
6
+ [![Codeship Status for rewinfrey/action_logic](https://codeship.com/projects/7737cf40-6808-0133-84a7-460d97cd31f0/status?branch=master)](https://codeship.com/projects/114179)
@@ -0,0 +1,24 @@
1
+ require File.expand_path('../lib/action_logic/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'action_logic'
5
+ s.summary = 'Business logic abstraction'
6
+ s.homepage = 'https://github.com/rewinfrey/action_logic'
7
+ s.license = 'MIT'
8
+
9
+ s.files = `git ls-files`.split($\)
10
+ s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
11
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
12
+ s.require_paths = ["lib"]
13
+ s.version = ActionLogic::VERSION
14
+
15
+ s.authors = ["Rick Winfrey"]
16
+ s.email = 'rick.winfrey@gmail.com'
17
+ s.date = '2015-11-03'
18
+ s.description = 'Provides common interfaces for validating and abstracting business logic'
19
+
20
+ s.add_development_dependency("rspec", "~> 3.3")
21
+ s.add_development_dependency("pry", "~> 0.10")
22
+ s.add_development_dependency("simplecov", "~> 0.10.0")
23
+ s.add_development_dependency("coveralls", "~> 0.8.3")
24
+ end
@@ -0,0 +1,39 @@
1
+ require 'ostruct'
2
+
3
+ module ActionLogic
4
+ class ActionContext < OpenStruct
5
+ SUCCESS = :success
6
+ FAILURE = :failure
7
+ HALTED = :halted
8
+
9
+ def initialize(params = {})
10
+ params[:status] ||= SUCCESS
11
+ super(params)
12
+ end
13
+
14
+ def update!(status, message)
15
+ self.status = status
16
+ self.message = message
17
+ end
18
+
19
+ def fail!(message = "")
20
+ update!(FAILURE, message)
21
+ end
22
+
23
+ def halt!(message = "")
24
+ update!(HALTED, message)
25
+ end
26
+
27
+ def success?
28
+ self.status == SUCCESS
29
+ end
30
+
31
+ def failure?
32
+ self.status == FAILURE
33
+ end
34
+
35
+ def halted?
36
+ self.status == HALTED
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,42 @@
1
+ require 'action_logic/action_core'
2
+ require 'action_logic/action_validation'
3
+
4
+ module ActionLogic
5
+ module ActionCoordinator
6
+ include ActionLogic::ActionValidation
7
+ include ActionLogic::ActionCore
8
+
9
+ def self.included(klass)
10
+ klass.extend ClassMethods
11
+ klass.extend ActionLogic::ActionCore::ClassMethods
12
+ klass.extend ActionLogic::ActionValidation::ClassMethods
13
+ end
14
+
15
+ module ClassMethods
16
+ def execute(params = {})
17
+ around(params) do |execution_context|
18
+ execution_context.call
19
+
20
+ next_execution_context = execution_context.plan.keys.first
21
+
22
+ while (next_execution_context) do
23
+ execution_context.context = next_execution_context.execute(execution_context.context)
24
+ next_execution_context = execution_context.plan[next_execution_context][execution_context.context.status]
25
+
26
+ # From the perspective of the coordinator, the status of the context should be
27
+ # :success as long as the state transition plan defines the next execution context
28
+ # for a given current exection context and its resulting context state.
29
+ # However, because normally a context in a state of :halted or :failure would
30
+ # be considered a "breaking" state, the status of a context that is :halted or :failure
31
+ # has to be reset to the default :success status only within the execution context of
32
+ # the coordinator and only when the next execution context is defined within the
33
+ # state transition plan. Otherwise, the context is return as is, without mutating its :status.
34
+ execution_context.context.status = :success if next_execution_context
35
+ end
36
+
37
+ execution_context.context
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,45 @@
1
+ module ActionLogic
2
+ module ActionCore
3
+ attr_accessor :context
4
+
5
+ def initialize(params)
6
+ self.context = make_context(params)
7
+ end
8
+
9
+ def make_context(params = {})
10
+ ActionContext.new(params)
11
+ end
12
+
13
+ def break?
14
+ context.status == :failure ||
15
+ context.status == :halted
16
+ end
17
+
18
+ module ClassMethods
19
+ def around(params, &block)
20
+ execution_context = self.new(params)
21
+
22
+ return execution_context.context if execution_context.break?
23
+
24
+ execution_context.set_validation_rules
25
+ execution_context.validations!(:before)
26
+ execution_context.validations!(:around)
27
+
28
+ begin
29
+ block.call(execution_context)
30
+ rescue => e
31
+ if execution_context.respond_to?(:error)
32
+ execution_context.error(e)
33
+ else
34
+ raise e
35
+ end
36
+ end
37
+
38
+ execution_context.validations!(:after)
39
+ execution_context.validations!(:around)
40
+
41
+ execution_context.context
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,24 @@
1
+ require 'action_logic/action_core'
2
+ require 'action_logic/action_validation'
3
+
4
+ module ActionLogic
5
+ module ActionTask
6
+ include ActionLogic::ActionValidation
7
+ include ActionLogic::ActionCore
8
+
9
+ def self.included(klass)
10
+ klass.extend ClassMethods
11
+ klass.extend ActionLogic::ActionCore::ClassMethods
12
+ klass.extend ActionLogic::ActionValidation::ClassMethods
13
+ end
14
+
15
+ module ClassMethods
16
+ def execute(params = {})
17
+ around(params) do |execution_context|
18
+ execution_context.call
19
+ execution_context.context
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ require 'action_logic/action_core'
2
+ require 'action_logic/action_validation'
3
+
4
+ module ActionLogic
5
+ module ActionUseCase
6
+ include ActionLogic::ActionValidation
7
+ include ActionLogic::ActionCore
8
+
9
+ def self.included(klass)
10
+ klass.extend ClassMethods
11
+ klass.extend ActionLogic::ActionCore::ClassMethods
12
+ klass.extend ActionLogic::ActionValidation::ClassMethods
13
+ end
14
+
15
+ module ClassMethods
16
+ def execute(params = {})
17
+ around(params) do |execution_context|
18
+ execution_context.call
19
+
20
+ execution_context.tasks.reduce(execution_context.context) do |context, task|
21
+ execution_context.context = task.execute(context)
22
+ execution_context.context
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,111 @@
1
+ require 'action_logic/errors'
2
+
3
+ module ActionLogic
4
+ module ActionValidation
5
+
6
+ module ClassMethods
7
+ def validates_before(args)
8
+ @validates_before = args
9
+ end
10
+
11
+ def validates_after(args)
12
+ @validates_after = args
13
+ end
14
+
15
+ def validates_around(args)
16
+ @validates_around = args
17
+ end
18
+
19
+ def get_validates_before
20
+ @validates_before
21
+ end
22
+
23
+ def get_validates_after
24
+ @validates_after
25
+ end
26
+
27
+ def get_validates_around
28
+ @validates_around
29
+ end
30
+ end
31
+
32
+ def validations
33
+ [:validate_attributes!,
34
+ :validate_types!,
35
+ :validate_presence!]
36
+ end
37
+
38
+ def validate!(validation, validation_rules)
39
+ return if validation_rules.empty?
40
+ send(validation, validation_rules)
41
+ end
42
+
43
+ def validations!(validation_order)
44
+ case validation_order
45
+ when :before then validations.each { |validation| validate!(validation, @before_validation_rules) }
46
+ when :after then validations.each { |validation| validate!(validation, @after_validation_rules) }
47
+ when :around then validations.each { |validation| validate!(validation, @around_validation_rules) }
48
+ end
49
+ end
50
+
51
+ def set_validation_rules
52
+ @before_validation_rules ||= self.class.get_validates_before || {}
53
+ @after_validation_rules ||= self.class.get_validates_after || {}
54
+ @around_validation_rules ||= self.class.get_validates_around || {}
55
+ end
56
+
57
+ def validate_attributes!(validations)
58
+ existing_attributes = context.to_h.keys
59
+ expected_attributes = validations.keys || []
60
+ missing_attributes = expected_attributes - existing_attributes
61
+
62
+ raise ActionLogic::MissingAttributeError.new(missing_attributes) if missing_attributes.any?
63
+ end
64
+
65
+ def validate_types!(validations)
66
+ return unless validations.values.find { |expected_validation| expected_validation[:type] }
67
+
68
+ type_errors = validations.reduce([]) do |collection, (expected_attribute, expected_validation)|
69
+ next unless expected_validation[:type]
70
+
71
+ if type_to_sym(context.to_h[expected_attribute]) != expected_validation[:type]
72
+ collection << "Attribute: #{expected_attribute} with value: #{context.to_h[expected_attribute]} was expected to be of type #{expected_validation[:type]} but is #{type_to_sym(context.to_h[expected_attribute])}"
73
+ end
74
+ collection
75
+ end
76
+
77
+ raise ActionLogic::AttributeTypeError.new(type_errors) if type_errors.any?
78
+ end
79
+
80
+ def validate_presence!(validations)
81
+ return unless validations.values.find { |expected_validation| expected_validation[:presence] }
82
+
83
+ presence_errors = validations.reduce([]) do |collection, (expected_attribute, expected_validation)|
84
+ next unless expected_validation[:presence]
85
+
86
+ if expected_validation[:presence] == true
87
+ collection << "Attribute: #{expected_attribute} is missing value in context but presence validation was specified" unless context[expected_attribute]
88
+ elsif expected_validation[:presence].class == Proc
89
+ collection << "Attribute: #{expected_attribute} is missing value in context but custom presence validation was specified" unless expected_validation[:presence].call(context[expected_attribute])
90
+ else
91
+ raise ActionLogic::UnrecognizablePresenceValidatorError.new("Presence validator: #{expected_validation[:presence]} is not a supported format")
92
+ end
93
+
94
+ collection
95
+ end || []
96
+
97
+ raise ActionLogic::PresenceError.new(presence_errors) if presence_errors.any?
98
+ end
99
+
100
+ def type_to_sym(value)
101
+ case value.class.name.downcase.to_sym
102
+ when :fixnum then :integer
103
+ when :falseclass then :boolean
104
+ when :trueclass then :boolean
105
+ when :nilclass then :nil
106
+ else
107
+ value.class.name.downcase.to_sym
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,6 @@
1
+ module ActionLogic
2
+ class MissingAttributeError < StandardError; end
3
+ class AttributeTypeError < StandardError; end
4
+ class PresenceError < StandardError; end
5
+ class UnrecognizablePresenceValidatorError < StandardError; end
6
+ end
@@ -0,0 +1,3 @@
1
+ module ActionLogic
2
+ VERSION = '0.0.4'
3
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+ require 'action_logic'
3
+ require 'fixtures/constants'
4
+
5
+ module ActionLogic
6
+ describe ActionContext do
7
+ subject { ActionContext.new }
8
+
9
+ describe "initialization" do
10
+ it "sets a default success attribute on the context" do
11
+ expect(subject.status).to eq(described_class::SUCCESS)
12
+ end
13
+ end
14
+
15
+ describe "success?" do
16
+ it "returns true if the context is successful" do
17
+ expect(subject.success?).to be_truthy
18
+ end
19
+ end
20
+
21
+ describe "failing a context" do
22
+ it "sets the context status as failed" do
23
+ subject.fail!
24
+
25
+ expect(subject.status).to eq(:failure)
26
+ end
27
+
28
+ it "does not require a message" do
29
+ subject.fail!
30
+
31
+ expect(subject.message).to be_empty
32
+ end
33
+
34
+ it "allows a custom failure message to be defined" do
35
+ failure_message = Constants::FAILURE_MESSAGE
36
+ subject.fail!(failure_message)
37
+
38
+ expect(subject.message).to eq(failure_message)
39
+ end
40
+
41
+ it "responds to directly query" do
42
+ subject.fail!
43
+
44
+ expect(subject.failure?).to be_truthy
45
+ end
46
+ end
47
+
48
+ describe "halting a context" do
49
+ it "sets the context status as halted" do
50
+ subject.halt!
51
+
52
+ expect(subject.status).to eq(:halted)
53
+ end
54
+
55
+ it "does not require a message" do
56
+ subject.halt!
57
+
58
+ expect(subject.message).to be_empty
59
+ end
60
+
61
+ it "allows a custom halted message to be defined" do
62
+ halt_message = Constants::HALT_MESSAGE
63
+ subject.halt!(halt_message)
64
+
65
+ expect(subject.message).to eq(halt_message)
66
+ end
67
+
68
+ it "responds to direct query" do
69
+ subject.halt!
70
+
71
+ expect(subject.halted?).to be_truthy
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,162 @@
1
+ require 'spec_helper'
2
+ require 'action_logic'
3
+ require 'fixtures/coordinators'
4
+ require 'fixtures/custom_types'
5
+
6
+ module ActionLogic
7
+ describe ActionCoordinator do
8
+ context "no failures and no halts" do
9
+ it "evaluates all use cases defined by the state transition plan" do
10
+ result = TestCoordinator1.execute()
11
+
12
+ expect(result.test_coordinator1).to be_truthy
13
+ expect(result.test_use_case1).to be_truthy
14
+ expect(result.test_task1).to be_truthy
15
+ expect(result.test_use_case2).to be_truthy
16
+ expect(result.test_task2).to be_truthy
17
+ expect(result.test_use_case3).to be_truthy
18
+ expect(result.test_task3).to be_truthy
19
+ end
20
+ end
21
+
22
+ context "with halts" do
23
+ it "evaluates all use cases defined by the state transition plan" do
24
+ result = HaltedTestCoordinator1.execute()
25
+
26
+ expect(result.halted_test_coordinator1).to be_truthy
27
+ expect(result.halted_test_use_case1).to be_truthy
28
+ expect(result.halted_test_task1).to be_truthy
29
+ expect(result.test_use_case2).to be_truthy
30
+ expect(result.test_task2).to be_truthy
31
+ expect(result.test_use_case3).to be_truthy
32
+ expect(result.test_task3).to be_truthy
33
+ end
34
+ end
35
+
36
+ context "with failures" do
37
+ it "evaluates all use cases defined by the state transition plan" do
38
+ result = FailureTestCoordinator1.execute()
39
+
40
+ expect(result.failure_test_coordinator1).to be_truthy
41
+ expect(result.failure_test_use_case1).to be_truthy
42
+ expect(result.failure_test_task1).to be_truthy
43
+ expect(result.test_use_case2).to be_truthy
44
+ expect(result.test_task2).to be_truthy
45
+ expect(result.test_use_case3).to be_truthy
46
+ expect(result.test_task3).to be_truthy
47
+ end
48
+ end
49
+
50
+ describe "before validations" do
51
+ describe "required attributes and type validation" do
52
+ it "does not raise error if context has required keys and values are of the correct type" do
53
+ expect { ValidateBeforeTestCoordinator.execute(Constants::VALID_ATTRIBUTES) }.to_not raise_error
54
+ end
55
+
56
+ it "raises error if context is missing required keys" do
57
+ expect { ValidateBeforeTestCoordinator.execute() }.to\
58
+ raise_error(ActionLogic::MissingAttributeError)
59
+ end
60
+
61
+ it "raises error if context has required key but is not of correct type" do
62
+ expect { ValidateBeforeTestCoordinator.execute(Constants::INVALID_ATTRIBUTES) }.to\
63
+ raise_error(ActionLogic::AttributeTypeError)
64
+ end
65
+ end
66
+
67
+ describe "custom types" do
68
+ it "allows validation against custom defined types" do
69
+ expect { ValidateBeforeCustomTypeTestCoordinator.execute(Constants::CUSTOM_TYPE_ATTRIBUTES1) }.to_not raise_error
70
+ end
71
+
72
+ it "raises error if context has custom type attribute but value is not correct custom type" do
73
+ expect { ValidateBeforeCustomTypeTestCoordinator.execute(Constants::CUSTOM_TYPE_ATTRIBUTES2) }.to\
74
+ raise_error(ActionLogic::AttributeTypeError)
75
+ end
76
+ end
77
+
78
+ describe "presence" do
79
+ it "validates presence if presence is specified" do
80
+ expect { ValidateBeforePresenceTestCoordinator.execute(:integer_test => 1) }.to_not raise_error
81
+ end
82
+
83
+ it "raises error if context has required key but value is not defined when validation requires presence" do
84
+ expect { ValidateBeforePresenceTestCoordinator.execute(:integer_test => nil) }.to\
85
+ raise_error(ActionLogic::PresenceError)
86
+ end
87
+ end
88
+
89
+ describe "custom presence" do
90
+ it "allows custom presence validation if custom presence is defined" do
91
+ expect { ValidateBeforeCustomPresenceTestCoordinator.execute(:array_test => [1]) }.to_not raise_error
92
+ end
93
+
94
+ it "raises error if custom presence validation is not satisfied" do
95
+ expect { ValidateBeforeCustomPresenceTestCoordinator.execute(:array_test => []) }.to\
96
+ raise_error(ActionLogic::PresenceError)
97
+ end
98
+
99
+ it "raises error if custom presence validation is not supported" do
100
+ expect { ValidateBeforeUnrecognizablePresenceTestCoordinator.execute(:integer_test => 1) }.to\
101
+ raise_error(ActionLogic::UnrecognizablePresenceValidatorError)
102
+ end
103
+ end
104
+ end
105
+
106
+ describe "after validations" do
107
+ describe "required attributes and type validation" do
108
+ it "does not raise error if the task sets all required keys and values are of the correct type" do
109
+ expect { ValidateAfterTestCoordinator.execute() }.to_not raise_error
110
+ end
111
+
112
+ it "raises error if task does not provide the necessary keys" do
113
+ expect { ValidateAfterMissingAttributesTestCoordinator.execute() }.to\
114
+ raise_error(ActionLogic::MissingAttributeError)
115
+ end
116
+
117
+ it "raises error if task has required key but is not of correct type" do
118
+ expect { ValidateAfterInvalidTypeTestCoordinator.execute() }.to\
119
+ raise_error(ActionLogic::AttributeTypeError)
120
+ end
121
+ end
122
+
123
+ describe "custom types" do
124
+ it "allows validation against custom defined types" do
125
+ expect { ValidateAfterCustomTypeTestCoordinator.execute() }.to_not raise_error
126
+ end
127
+
128
+ it "raises error if context has custom type attribute but value is not correct custom type" do
129
+ expect { ValidateAfterInvalidCustomTypeTestCoordinator.execute() }.to\
130
+ raise_error(ActionLogic::AttributeTypeError)
131
+ end
132
+ end
133
+
134
+ describe "presence" do
135
+ it "validates presence if presence is specified" do
136
+ expect { ValidateAfterPresenceTestCoordinator.execute() }.to_not raise_error
137
+ end
138
+
139
+ it "raises error if context has required key but value is not defined when validation requires presence" do
140
+ expect { ValidateAfterInvalidPresenceTestCoordinator.execute() }.to\
141
+ raise_error(ActionLogic::PresenceError)
142
+ end
143
+ end
144
+
145
+ describe "custom presence" do
146
+ it "allows custom presence validation if custom presence is defined" do
147
+ expect { ValidateAfterCustomPresenceTestCoordinator.execute() }.to_not raise_error
148
+ end
149
+
150
+ it "raises error if custom presence validation is not satisfied" do
151
+ expect { ValidateAfterInvalidCustomPresenceTestCoordinator.execute() }.to\
152
+ raise_error(ActionLogic::PresenceError)
153
+ end
154
+
155
+ it "raises error if custom presence validation is not supported" do
156
+ expect { ValidateAfterUnrecognizablePresenceTestCoordinator.execute() }.to\
157
+ raise_error(ActionLogic::UnrecognizablePresenceValidatorError)
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end