decision_agent 0.3.0 → 1.0.1
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 +4 -4
- data/README.md +272 -7
- data/lib/decision_agent/agent.rb +72 -1
- data/lib/decision_agent/context.rb +1 -0
- data/lib/decision_agent/data_enrichment/cache/memory_adapter.rb +86 -0
- data/lib/decision_agent/data_enrichment/cache_adapter.rb +49 -0
- data/lib/decision_agent/data_enrichment/circuit_breaker.rb +135 -0
- data/lib/decision_agent/data_enrichment/client.rb +220 -0
- data/lib/decision_agent/data_enrichment/config.rb +78 -0
- data/lib/decision_agent/data_enrichment/errors.rb +36 -0
- data/lib/decision_agent/decision.rb +102 -2
- data/lib/decision_agent/dmn/feel/evaluator.rb +28 -6
- data/lib/decision_agent/dsl/condition_evaluator.rb +982 -839
- data/lib/decision_agent/dsl/schema_validator.rb +51 -13
- data/lib/decision_agent/evaluators/dmn_evaluator.rb +106 -19
- data/lib/decision_agent/evaluators/json_rule_evaluator.rb +69 -9
- data/lib/decision_agent/explainability/condition_trace.rb +83 -0
- data/lib/decision_agent/explainability/explainability_result.rb +52 -0
- data/lib/decision_agent/explainability/rule_trace.rb +39 -0
- data/lib/decision_agent/explainability/trace_collector.rb +24 -0
- data/lib/decision_agent/monitoring/alert_manager.rb +5 -1
- data/lib/decision_agent/simulation/errors.rb +18 -0
- data/lib/decision_agent/simulation/impact_analyzer.rb +498 -0
- data/lib/decision_agent/simulation/monte_carlo_simulator.rb +635 -0
- data/lib/decision_agent/simulation/replay_engine.rb +486 -0
- data/lib/decision_agent/simulation/scenario_engine.rb +318 -0
- data/lib/decision_agent/simulation/scenario_library.rb +163 -0
- data/lib/decision_agent/simulation/shadow_test_engine.rb +287 -0
- data/lib/decision_agent/simulation/what_if_analyzer.rb +1002 -0
- data/lib/decision_agent/simulation.rb +17 -0
- data/lib/decision_agent/version.rb +1 -1
- data/lib/decision_agent/versioning/activerecord_adapter.rb +23 -8
- data/lib/decision_agent/web/public/app.js +119 -0
- data/lib/decision_agent/web/public/index.html +49 -0
- data/lib/decision_agent/web/public/simulation.html +130 -0
- data/lib/decision_agent/web/public/simulation_impact.html +478 -0
- data/lib/decision_agent/web/public/simulation_replay.html +551 -0
- data/lib/decision_agent/web/public/simulation_shadow.html +546 -0
- data/lib/decision_agent/web/public/simulation_whatif.html +532 -0
- data/lib/decision_agent/web/public/styles.css +65 -0
- data/lib/decision_agent/web/server.rb +594 -23
- data/lib/decision_agent.rb +60 -2
- metadata +53 -73
- data/spec/ab_testing/ab_test_assignment_spec.rb +0 -253
- data/spec/ab_testing/ab_test_manager_spec.rb +0 -612
- data/spec/ab_testing/ab_test_spec.rb +0 -270
- data/spec/ab_testing/ab_testing_agent_spec.rb +0 -655
- data/spec/ab_testing/storage/adapter_spec.rb +0 -64
- data/spec/ab_testing/storage/memory_adapter_spec.rb +0 -485
- data/spec/activerecord_thread_safety_spec.rb +0 -553
- data/spec/advanced_operators_spec.rb +0 -3150
- data/spec/agent_spec.rb +0 -289
- data/spec/api_contract_spec.rb +0 -430
- data/spec/audit_adapters_spec.rb +0 -92
- data/spec/auth/access_audit_logger_spec.rb +0 -394
- data/spec/auth/authenticator_spec.rb +0 -112
- data/spec/auth/password_reset_spec.rb +0 -294
- data/spec/auth/permission_checker_spec.rb +0 -207
- data/spec/auth/permission_spec.rb +0 -73
- data/spec/auth/rbac_adapter_spec.rb +0 -778
- data/spec/auth/rbac_config_spec.rb +0 -82
- data/spec/auth/role_spec.rb +0 -51
- data/spec/auth/session_manager_spec.rb +0 -172
- data/spec/auth/session_spec.rb +0 -112
- data/spec/auth/user_spec.rb +0 -130
- data/spec/comprehensive_edge_cases_spec.rb +0 -1777
- data/spec/context_spec.rb +0 -127
- data/spec/decision_agent_spec.rb +0 -96
- data/spec/decision_spec.rb +0 -423
- data/spec/dmn/decision_graph_spec.rb +0 -282
- data/spec/dmn/decision_tree_spec.rb +0 -203
- data/spec/dmn/feel/errors_spec.rb +0 -18
- data/spec/dmn/feel/functions_spec.rb +0 -400
- data/spec/dmn/feel/simple_parser_spec.rb +0 -274
- data/spec/dmn/feel/types_spec.rb +0 -176
- data/spec/dmn/feel_parser_spec.rb +0 -489
- data/spec/dmn/hit_policy_spec.rb +0 -202
- data/spec/dmn/integration_spec.rb +0 -226
- data/spec/dsl/condition_evaluator_spec.rb +0 -774
- data/spec/dsl_validation_spec.rb +0 -648
- data/spec/edge_cases_spec.rb +0 -353
- data/spec/evaluation_spec.rb +0 -364
- data/spec/evaluation_validator_spec.rb +0 -165
- data/spec/examples/feedback_aware_evaluator_spec.rb +0 -460
- data/spec/examples.txt +0 -1909
- data/spec/fixtures/dmn/complex_decision.dmn +0 -81
- data/spec/fixtures/dmn/invalid_structure.dmn +0 -31
- data/spec/fixtures/dmn/simple_decision.dmn +0 -40
- data/spec/issue_verification_spec.rb +0 -759
- data/spec/json_rule_evaluator_spec.rb +0 -587
- data/spec/monitoring/alert_manager_spec.rb +0 -378
- data/spec/monitoring/metrics_collector_spec.rb +0 -501
- data/spec/monitoring/monitored_agent_spec.rb +0 -225
- data/spec/monitoring/prometheus_exporter_spec.rb +0 -242
- data/spec/monitoring/storage/activerecord_adapter_spec.rb +0 -498
- data/spec/monitoring/storage/base_adapter_spec.rb +0 -61
- data/spec/monitoring/storage/memory_adapter_spec.rb +0 -247
- data/spec/performance_optimizations_spec.rb +0 -493
- data/spec/replay_edge_cases_spec.rb +0 -699
- data/spec/replay_spec.rb +0 -210
- data/spec/rfc8785_canonicalization_spec.rb +0 -215
- data/spec/scoring_spec.rb +0 -225
- data/spec/spec_helper.rb +0 -60
- data/spec/testing/batch_test_importer_spec.rb +0 -693
- data/spec/testing/batch_test_runner_spec.rb +0 -307
- data/spec/testing/test_coverage_analyzer_spec.rb +0 -292
- data/spec/testing/test_result_comparator_spec.rb +0 -392
- data/spec/testing/test_scenario_spec.rb +0 -113
- data/spec/thread_safety_spec.rb +0 -490
- data/spec/thread_safety_spec.rb.broken +0 -878
- data/spec/versioning/adapter_spec.rb +0 -156
- data/spec/versioning_spec.rb +0 -1030
- data/spec/web/middleware/auth_middleware_spec.rb +0 -133
- data/spec/web/middleware/permission_middleware_spec.rb +0 -247
- data/spec/web_ui_rack_spec.rb +0 -2134
|
@@ -1,778 +0,0 @@
|
|
|
1
|
-
require "spec_helper"
|
|
2
|
-
require_relative "../../lib/decision_agent/auth/rbac_adapter"
|
|
3
|
-
require_relative "../../lib/decision_agent/auth/user"
|
|
4
|
-
require_relative "../../lib/decision_agent/auth/role"
|
|
5
|
-
|
|
6
|
-
RSpec.describe DecisionAgent::Auth::RbacAdapter do
|
|
7
|
-
describe "base class" do
|
|
8
|
-
let(:adapter) { described_class.new }
|
|
9
|
-
let(:user) { double("User") }
|
|
10
|
-
|
|
11
|
-
describe "#can?" do
|
|
12
|
-
it "raises NotImplementedError" do
|
|
13
|
-
expect { adapter.can?(user, :read) }.to raise_error(NotImplementedError, /must implement #can\?/)
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
describe "#has_role?" do
|
|
18
|
-
it "raises NotImplementedError" do
|
|
19
|
-
expect { adapter.has_role?(user, :admin) }.to raise_error(NotImplementedError, /must implement #has_role\?/)
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
describe "#active?" do
|
|
24
|
-
it "returns false for nil user" do
|
|
25
|
-
expect(adapter.active?(nil)).to be false
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
it "returns true for user with active? method returning true" do
|
|
29
|
-
user = double("User", active?: true)
|
|
30
|
-
expect(adapter.active?(user)).to be true
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
it "returns false for user with active? method returning false" do
|
|
34
|
-
user = double("User", active?: false)
|
|
35
|
-
expect(adapter.active?(user)).to be false
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
it "returns true for user without active? method" do
|
|
39
|
-
user = double("User")
|
|
40
|
-
expect(adapter.active?(user)).to be true
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
describe "#user_id" do
|
|
45
|
-
it "returns nil for nil user" do
|
|
46
|
-
expect(adapter.user_id(nil)).to be_nil
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
it "returns user.id when user responds to id" do
|
|
50
|
-
user = double("User", id: "user123")
|
|
51
|
-
expect(adapter.user_id(user)).to eq("user123")
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
it "returns user.to_s when user doesn't respond to id" do
|
|
55
|
-
user = double("User", to_s: "user_string")
|
|
56
|
-
expect(adapter.user_id(user)).to eq("user_string")
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
describe "#user_email" do
|
|
61
|
-
it "returns nil for nil user" do
|
|
62
|
-
expect(adapter.user_email(nil)).to be_nil
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
it "returns user.email when user responds to email" do
|
|
66
|
-
user = double("User", email: "user@example.com")
|
|
67
|
-
expect(adapter.user_email(user)).to eq("user@example.com")
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
it "returns nil when user doesn't respond to email" do
|
|
71
|
-
user = double("User")
|
|
72
|
-
expect(adapter.user_email(user)).to be_nil
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
describe DecisionAgent::Auth::DefaultAdapter do
|
|
78
|
-
let(:adapter) { described_class.new }
|
|
79
|
-
|
|
80
|
-
describe "#can?" do
|
|
81
|
-
it "returns false for nil user" do
|
|
82
|
-
expect(adapter.can?(nil, :read)).to be false
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
it "returns false for inactive user" do
|
|
86
|
-
user = double("User", active: false, roles: [])
|
|
87
|
-
expect(adapter.can?(user, :read)).to be false
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
it "returns true when user has role with permission" do
|
|
91
|
-
user = DecisionAgent::Auth::User.new(
|
|
92
|
-
id: "user1",
|
|
93
|
-
email: "user@example.com",
|
|
94
|
-
password: "password123"
|
|
95
|
-
)
|
|
96
|
-
user.assign_role(:admin)
|
|
97
|
-
|
|
98
|
-
expect(adapter.can?(user, :read)).to be true
|
|
99
|
-
expect(adapter.can?(user, :write)).to be true
|
|
100
|
-
expect(adapter.can?(user, :manage_users)).to be true
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
it "returns false when user doesn't have role with permission" do
|
|
104
|
-
user = DecisionAgent::Auth::User.new(
|
|
105
|
-
id: "user1",
|
|
106
|
-
email: "user@example.com",
|
|
107
|
-
password: "password123"
|
|
108
|
-
)
|
|
109
|
-
user.assign_role(:guest)
|
|
110
|
-
|
|
111
|
-
expect(adapter.can?(user, :manage_users)).to be false
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
it "returns true when user has multiple roles and one has permission" do
|
|
115
|
-
user = DecisionAgent::Auth::User.new(
|
|
116
|
-
id: "user1",
|
|
117
|
-
email: "user@example.com",
|
|
118
|
-
password: "password123"
|
|
119
|
-
)
|
|
120
|
-
user.assign_role(:guest)
|
|
121
|
-
user.assign_role(:editor)
|
|
122
|
-
|
|
123
|
-
expect(adapter.can?(user, :write)).to be true
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
describe "#has_role?" do
|
|
128
|
-
it "returns false for nil user" do
|
|
129
|
-
expect(adapter.has_role?(nil, :admin)).to be false
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
it "returns true when user has role" do
|
|
133
|
-
user = DecisionAgent::Auth::User.new(
|
|
134
|
-
id: "user1",
|
|
135
|
-
email: "user@example.com",
|
|
136
|
-
password: "password123"
|
|
137
|
-
)
|
|
138
|
-
user.assign_role(:admin)
|
|
139
|
-
|
|
140
|
-
expect(adapter.has_role?(user, :admin)).to be true
|
|
141
|
-
expect(adapter.has_role?(user, :guest)).to be false
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
it "handles string role names" do
|
|
145
|
-
user = DecisionAgent::Auth::User.new(
|
|
146
|
-
id: "user1",
|
|
147
|
-
email: "user@example.com",
|
|
148
|
-
password: "password123"
|
|
149
|
-
)
|
|
150
|
-
user.assign_role(:admin)
|
|
151
|
-
|
|
152
|
-
expect(adapter.has_role?(user, "admin")).to be true
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
describe "#active?" do
|
|
157
|
-
it "returns false for nil user" do
|
|
158
|
-
expect(adapter.active?(nil)).to be false
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
it "returns user.active when user responds to active" do
|
|
162
|
-
user = double("User", active: true)
|
|
163
|
-
expect(adapter.active?(user)).to be true
|
|
164
|
-
|
|
165
|
-
user = double("User", active: false)
|
|
166
|
-
expect(adapter.active?(user)).to be false
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
it "returns true when user doesn't respond to active" do
|
|
170
|
-
user = double("User")
|
|
171
|
-
expect(adapter.active?(user)).to be true
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
describe "#extract_roles" do
|
|
176
|
-
it "extracts roles from user.roles" do
|
|
177
|
-
user = double("User", roles: %i[admin editor])
|
|
178
|
-
roles = adapter.send(:extract_roles, user)
|
|
179
|
-
expect(roles).to eq(%i[admin editor])
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
it "extracts role from user.role (singular)" do
|
|
183
|
-
user = double("User", role: :admin)
|
|
184
|
-
roles = adapter.send(:extract_roles, user)
|
|
185
|
-
expect(roles).to eq([:admin])
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
it "returns empty array when user has no roles" do
|
|
189
|
-
user = double("User")
|
|
190
|
-
roles = adapter.send(:extract_roles, user)
|
|
191
|
-
expect(roles).to eq([])
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
it "handles array of roles" do
|
|
195
|
-
user = double("User", roles: %w[admin editor])
|
|
196
|
-
roles = adapter.send(:extract_roles, user)
|
|
197
|
-
expect(roles).to eq(%i[admin editor])
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
describe DecisionAgent::Auth::DeviseCanCanAdapter do
|
|
203
|
-
let(:adapter) { described_class.new }
|
|
204
|
-
|
|
205
|
-
describe "#initialize" do
|
|
206
|
-
it "initializes without ability_class" do
|
|
207
|
-
adapter = described_class.new
|
|
208
|
-
expect(adapter.instance_variable_get(:@ability_class)).to be_nil
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
it "initializes with ability_class" do
|
|
212
|
-
ability_class = Class.new
|
|
213
|
-
adapter = described_class.new(ability_class: ability_class)
|
|
214
|
-
expect(adapter.instance_variable_get(:@ability_class)).to eq(ability_class)
|
|
215
|
-
end
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
describe "#can?" do
|
|
219
|
-
it "returns false for nil user" do
|
|
220
|
-
expect(adapter.can?(nil, :read)).to be false
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
it "returns false for inactive user" do
|
|
224
|
-
user = double("User", active_for_authentication?: false)
|
|
225
|
-
expect(adapter.can?(user, :read)).to be false
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
it "uses user.can? when available" do
|
|
229
|
-
user = double("User", active_for_authentication?: true)
|
|
230
|
-
allow(user).to receive(:can?).with(:read, Object).and_return(true)
|
|
231
|
-
|
|
232
|
-
expect(adapter.can?(user, :read)).to be true
|
|
233
|
-
expect(user).to have_received(:can?).with(:read, Object)
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
it "uses ability_class when provided" do
|
|
237
|
-
ability_instance = double("Ability", can?: true)
|
|
238
|
-
ability_class = double("AbilityClass", new: ability_instance)
|
|
239
|
-
adapter = described_class.new(ability_class: ability_class)
|
|
240
|
-
user = double("User", active_for_authentication?: true)
|
|
241
|
-
|
|
242
|
-
expect(adapter.can?(user, :read)).to be true
|
|
243
|
-
expect(ability_class).to have_received(:new).with(user)
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
it "returns false when neither user.can? nor ability_class available" do
|
|
247
|
-
user = double("User", active_for_authentication?: true)
|
|
248
|
-
expect(adapter.can?(user, :read)).to be false
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
it "maps permissions to CanCanCan actions" do
|
|
252
|
-
user = double("User", active_for_authentication?: true)
|
|
253
|
-
allow(user).to receive(:can?).and_return(true)
|
|
254
|
-
|
|
255
|
-
adapter.can?(user, :read)
|
|
256
|
-
expect(user).to have_received(:can?).with(:read, Object)
|
|
257
|
-
|
|
258
|
-
adapter.can?(user, :write)
|
|
259
|
-
expect(user).to have_received(:can?).with(:create, Object)
|
|
260
|
-
|
|
261
|
-
adapter.can?(user, :delete)
|
|
262
|
-
expect(user).to have_received(:can?).with(:destroy, Object)
|
|
263
|
-
end
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
describe "#has_role?" do
|
|
267
|
-
it "returns false for nil user" do
|
|
268
|
-
expect(adapter.has_role?(nil, :admin)).to be false
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
it "returns false for inactive user" do
|
|
272
|
-
user = double("User", active_for_authentication?: false)
|
|
273
|
-
expect(adapter.has_role?(user, :admin)).to be false
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
it "uses user.has_role? when available" do
|
|
277
|
-
user = double("User", active_for_authentication?: true, has_role?: true)
|
|
278
|
-
expect(adapter.has_role?(user, :admin)).to be true
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
it "checks user.roles when has_role? not available" do
|
|
282
|
-
role = double("Role", name: :admin)
|
|
283
|
-
user = double("User", active_for_authentication?: true, roles: [role])
|
|
284
|
-
expect(adapter.has_role?(user, :admin)).to be true
|
|
285
|
-
end
|
|
286
|
-
|
|
287
|
-
it "returns false when no role methods available" do
|
|
288
|
-
user = double("User", active_for_authentication?: true)
|
|
289
|
-
expect(adapter.has_role?(user, :admin)).to be false
|
|
290
|
-
end
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
describe "#active?" do
|
|
294
|
-
it "uses active_for_authentication? when available" do
|
|
295
|
-
user = double("User", active_for_authentication?: true)
|
|
296
|
-
expect(adapter.active?(user)).to be true
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
it "falls back to active? when active_for_authentication? not available" do
|
|
300
|
-
user = double("User", active?: true)
|
|
301
|
-
expect(adapter.active?(user)).to be true
|
|
302
|
-
end
|
|
303
|
-
|
|
304
|
-
it "returns true when neither method available" do
|
|
305
|
-
user = double("User")
|
|
306
|
-
expect(adapter.active?(user)).to be true
|
|
307
|
-
end
|
|
308
|
-
end
|
|
309
|
-
|
|
310
|
-
describe "#map_permission_to_action" do
|
|
311
|
-
it "maps known permissions" do
|
|
312
|
-
mapping = adapter.send(:map_permission_to_action, :read)
|
|
313
|
-
expect(mapping).to eq(:read)
|
|
314
|
-
|
|
315
|
-
mapping = adapter.send(:map_permission_to_action, :write)
|
|
316
|
-
expect(mapping).to eq(:create)
|
|
317
|
-
|
|
318
|
-
mapping = adapter.send(:map_permission_to_action, :delete)
|
|
319
|
-
expect(mapping).to eq(:destroy)
|
|
320
|
-
|
|
321
|
-
mapping = adapter.send(:map_permission_to_action, :manage_users)
|
|
322
|
-
expect(mapping).to eq(:manage)
|
|
323
|
-
end
|
|
324
|
-
|
|
325
|
-
it "returns permission as-is for unknown permissions" do
|
|
326
|
-
mapping = adapter.send(:map_permission_to_action, :custom_permission)
|
|
327
|
-
expect(mapping).to eq(:custom_permission)
|
|
328
|
-
end
|
|
329
|
-
end
|
|
330
|
-
end
|
|
331
|
-
|
|
332
|
-
describe DecisionAgent::Auth::PunditAdapter do
|
|
333
|
-
let(:adapter) { described_class.new }
|
|
334
|
-
|
|
335
|
-
describe "#can?" do
|
|
336
|
-
it "returns false for nil user" do
|
|
337
|
-
expect(adapter.can?(nil, :read)).to be false
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
it "returns false for inactive user" do
|
|
341
|
-
user = double("User", active?: false)
|
|
342
|
-
expect(adapter.can?(user, :read)).to be false
|
|
343
|
-
end
|
|
344
|
-
|
|
345
|
-
it "returns false when no resource provided" do
|
|
346
|
-
user = double("User", active?: true)
|
|
347
|
-
expect(adapter.can?(user, :read)).to be false
|
|
348
|
-
end
|
|
349
|
-
|
|
350
|
-
it "uses resource.policy_class when available" do
|
|
351
|
-
policy = double("Policy", show: true)
|
|
352
|
-
policy_class = double("PolicyClass", new: policy)
|
|
353
|
-
resource = double("Resource", policy_class: policy_class)
|
|
354
|
-
user = double("User", active?: true)
|
|
355
|
-
|
|
356
|
-
expect(adapter.can?(user, :read, resource)).to be true
|
|
357
|
-
expect(policy_class).to have_received(:new).with(user, resource)
|
|
358
|
-
end
|
|
359
|
-
|
|
360
|
-
it "infers policy class from resource class name" do
|
|
361
|
-
policy = double("Policy", show: true)
|
|
362
|
-
policy_class = double("PolicyClass")
|
|
363
|
-
allow(policy_class).to receive(:new).with(anything, anything).and_return(policy)
|
|
364
|
-
allow(Object).to receive(:const_defined?).with("TestResourcePolicy").and_return(true)
|
|
365
|
-
allow(Object).to receive(:const_get).with("TestResourcePolicy").and_return(policy_class)
|
|
366
|
-
|
|
367
|
-
resource = double("TestResource", class: double(name: "TestResource"))
|
|
368
|
-
user = double("User", active?: true)
|
|
369
|
-
|
|
370
|
-
expect(adapter.can?(user, :read, resource)).to be true
|
|
371
|
-
end
|
|
372
|
-
|
|
373
|
-
it "returns false when policy class doesn't exist" do
|
|
374
|
-
resource = double("TestResource", class: double(name: "TestResource"))
|
|
375
|
-
user = double("User", active?: true)
|
|
376
|
-
|
|
377
|
-
allow(Object).to receive(:const_defined?).with("TestResourcePolicy").and_return(false)
|
|
378
|
-
|
|
379
|
-
expect(adapter.can?(user, :read, resource)).to be false
|
|
380
|
-
end
|
|
381
|
-
end
|
|
382
|
-
|
|
383
|
-
describe "#has_role?" do
|
|
384
|
-
it "returns false for nil user" do
|
|
385
|
-
expect(adapter.has_role?(nil, :admin)).to be false
|
|
386
|
-
end
|
|
387
|
-
|
|
388
|
-
it "returns false for inactive user" do
|
|
389
|
-
user = double("User", active?: false)
|
|
390
|
-
expect(adapter.has_role?(user, :admin)).to be false
|
|
391
|
-
end
|
|
392
|
-
|
|
393
|
-
it "uses user.has_role? when available" do
|
|
394
|
-
user = double("User", active?: true, has_role?: true)
|
|
395
|
-
expect(adapter.has_role?(user, :admin)).to be true
|
|
396
|
-
end
|
|
397
|
-
|
|
398
|
-
it "checks user.roles when has_role? not available" do
|
|
399
|
-
role = double("Role", name: :admin, to_s: "admin")
|
|
400
|
-
user = double("User", active?: true, roles: [role])
|
|
401
|
-
expect(adapter.has_role?(user, :admin)).to be true
|
|
402
|
-
end
|
|
403
|
-
end
|
|
404
|
-
|
|
405
|
-
describe "#map_permission_to_action" do
|
|
406
|
-
it "maps known permissions" do
|
|
407
|
-
mapping = adapter.send(:map_permission_to_action, :read)
|
|
408
|
-
expect(mapping).to eq(:show)
|
|
409
|
-
|
|
410
|
-
mapping = adapter.send(:map_permission_to_action, :write)
|
|
411
|
-
expect(mapping).to eq(:create)
|
|
412
|
-
|
|
413
|
-
mapping = adapter.send(:map_permission_to_action, :delete)
|
|
414
|
-
expect(mapping).to eq(:destroy)
|
|
415
|
-
end
|
|
416
|
-
end
|
|
417
|
-
end
|
|
418
|
-
|
|
419
|
-
describe DecisionAgent::Auth::CustomAdapter do
|
|
420
|
-
describe "#initialize" do
|
|
421
|
-
it "initializes with procs" do
|
|
422
|
-
can_proc = ->(_u, _p, _r) { true }
|
|
423
|
-
has_role_proc = ->(_u, _r) { true }
|
|
424
|
-
active_proc = ->(_u) { true }
|
|
425
|
-
user_id_proc = ->(_u) { "id" }
|
|
426
|
-
user_email_proc = ->(_u) { "email" }
|
|
427
|
-
|
|
428
|
-
adapter = described_class.new(
|
|
429
|
-
can_proc: can_proc,
|
|
430
|
-
has_role_proc: has_role_proc,
|
|
431
|
-
active_proc: active_proc,
|
|
432
|
-
user_id_proc: user_id_proc,
|
|
433
|
-
user_email_proc: user_email_proc
|
|
434
|
-
)
|
|
435
|
-
|
|
436
|
-
expect(adapter.instance_variable_get(:@can_proc)).to eq(can_proc)
|
|
437
|
-
expect(adapter.instance_variable_get(:@has_role_proc)).to eq(has_role_proc)
|
|
438
|
-
expect(adapter.instance_variable_get(:@active_proc)).to eq(active_proc)
|
|
439
|
-
expect(adapter.instance_variable_get(:@user_id_proc)).to eq(user_id_proc)
|
|
440
|
-
expect(adapter.instance_variable_get(:@user_email_proc)).to eq(user_email_proc)
|
|
441
|
-
end
|
|
442
|
-
end
|
|
443
|
-
|
|
444
|
-
describe "#can?" do
|
|
445
|
-
it "returns false for nil user" do
|
|
446
|
-
adapter = described_class.new(can_proc: ->(_u, _p, _r) { true })
|
|
447
|
-
expect(adapter.can?(nil, :read)).to be false
|
|
448
|
-
end
|
|
449
|
-
|
|
450
|
-
it "returns false for inactive user" do
|
|
451
|
-
adapter = described_class.new(
|
|
452
|
-
can_proc: ->(_u, _p, _r) { true },
|
|
453
|
-
active_proc: ->(_u) { false }
|
|
454
|
-
)
|
|
455
|
-
user = double("User")
|
|
456
|
-
expect(adapter.can?(user, :read)).to be false
|
|
457
|
-
end
|
|
458
|
-
|
|
459
|
-
it "calls can_proc when provided" do
|
|
460
|
-
can_proc = ->(_u, p, _r) { p == :read }
|
|
461
|
-
adapter = described_class.new(can_proc: can_proc, active_proc: ->(_u) { true })
|
|
462
|
-
user = double("User")
|
|
463
|
-
|
|
464
|
-
expect(adapter.can?(user, :read)).to be true
|
|
465
|
-
expect(adapter.can?(user, :write)).to be false
|
|
466
|
-
end
|
|
467
|
-
|
|
468
|
-
it "raises error when can_proc not provided" do
|
|
469
|
-
adapter = described_class.new(active_proc: ->(_u) { true })
|
|
470
|
-
user = double("User")
|
|
471
|
-
|
|
472
|
-
expect { adapter.can?(user, :read) }.to raise_error(NotImplementedError, /requires can_proc/)
|
|
473
|
-
end
|
|
474
|
-
end
|
|
475
|
-
|
|
476
|
-
describe "#has_role?" do
|
|
477
|
-
it "returns false for nil user" do
|
|
478
|
-
adapter = described_class.new(has_role_proc: ->(_u, _r) { true })
|
|
479
|
-
expect(adapter.has_role?(nil, :admin)).to be false
|
|
480
|
-
end
|
|
481
|
-
|
|
482
|
-
it "calls has_role_proc when provided" do
|
|
483
|
-
has_role_proc = ->(_u, r) { r == :admin }
|
|
484
|
-
adapter = described_class.new(has_role_proc: has_role_proc)
|
|
485
|
-
user = double("User")
|
|
486
|
-
|
|
487
|
-
expect(adapter.has_role?(user, :admin)).to be true
|
|
488
|
-
expect(adapter.has_role?(user, :guest)).to be false
|
|
489
|
-
end
|
|
490
|
-
|
|
491
|
-
it "raises error when has_role_proc not provided" do
|
|
492
|
-
adapter = described_class.new
|
|
493
|
-
user = double("User")
|
|
494
|
-
|
|
495
|
-
expect { adapter.has_role?(user, :admin) }.to raise_error(NotImplementedError, /requires has_role_proc/)
|
|
496
|
-
end
|
|
497
|
-
end
|
|
498
|
-
|
|
499
|
-
describe "#active?" do
|
|
500
|
-
it "calls active_proc when provided" do
|
|
501
|
-
active_proc = ->(u) { u == "active_user" }
|
|
502
|
-
adapter = described_class.new(active_proc: active_proc)
|
|
503
|
-
|
|
504
|
-
expect(adapter.active?("active_user")).to be true
|
|
505
|
-
expect(adapter.active?("inactive_user")).to be false
|
|
506
|
-
end
|
|
507
|
-
|
|
508
|
-
it "falls back to super when active_proc not provided" do
|
|
509
|
-
adapter = described_class.new
|
|
510
|
-
user = double("User", active?: true)
|
|
511
|
-
|
|
512
|
-
expect(adapter.active?(user)).to be true
|
|
513
|
-
end
|
|
514
|
-
end
|
|
515
|
-
|
|
516
|
-
describe "#user_id" do
|
|
517
|
-
it "calls user_id_proc when provided" do
|
|
518
|
-
user_id_proc = ->(_u) { "custom_id" }
|
|
519
|
-
adapter = described_class.new(user_id_proc: user_id_proc)
|
|
520
|
-
user = double("User")
|
|
521
|
-
|
|
522
|
-
expect(adapter.user_id(user)).to eq("custom_id")
|
|
523
|
-
end
|
|
524
|
-
|
|
525
|
-
it "falls back to super when user_id_proc not provided" do
|
|
526
|
-
adapter = described_class.new
|
|
527
|
-
user = double("User", id: "user123")
|
|
528
|
-
|
|
529
|
-
expect(adapter.user_id(user)).to eq("user123")
|
|
530
|
-
end
|
|
531
|
-
end
|
|
532
|
-
|
|
533
|
-
describe "#user_email" do
|
|
534
|
-
it "calls user_email_proc when provided" do
|
|
535
|
-
user_email_proc = ->(_u) { "custom@example.com" }
|
|
536
|
-
adapter = described_class.new(user_email_proc: user_email_proc)
|
|
537
|
-
user = double("User")
|
|
538
|
-
|
|
539
|
-
expect(adapter.user_email(user)).to eq("custom@example.com")
|
|
540
|
-
end
|
|
541
|
-
|
|
542
|
-
it "falls back to super when user_email_proc not provided" do
|
|
543
|
-
adapter = described_class.new
|
|
544
|
-
user = double("User", email: "user@example.com")
|
|
545
|
-
|
|
546
|
-
expect(adapter.user_email(user)).to eq("user@example.com")
|
|
547
|
-
end
|
|
548
|
-
end
|
|
549
|
-
end
|
|
550
|
-
|
|
551
|
-
describe "Integration tests with real User and Role objects" do
|
|
552
|
-
let(:adapter) { DecisionAgent::Auth::DefaultAdapter.new }
|
|
553
|
-
|
|
554
|
-
describe "with real User and Role objects" do
|
|
555
|
-
it "checks permissions using real Role class" do
|
|
556
|
-
user = DecisionAgent::Auth::User.new(
|
|
557
|
-
id: "user1",
|
|
558
|
-
email: "admin@example.com",
|
|
559
|
-
password: "password123",
|
|
560
|
-
roles: [:admin]
|
|
561
|
-
)
|
|
562
|
-
|
|
563
|
-
expect(adapter.can?(user, :read)).to be true
|
|
564
|
-
expect(adapter.can?(user, :write)).to be true
|
|
565
|
-
expect(adapter.can?(user, :delete)).to be true
|
|
566
|
-
expect(adapter.can?(user, :manage_users)).to be true
|
|
567
|
-
expect(adapter.can?(user, :audit)).to be true
|
|
568
|
-
end
|
|
569
|
-
|
|
570
|
-
it "checks permissions for editor role" do
|
|
571
|
-
user = DecisionAgent::Auth::User.new(
|
|
572
|
-
id: "user2",
|
|
573
|
-
email: "editor@example.com",
|
|
574
|
-
password: "password123",
|
|
575
|
-
roles: [:editor]
|
|
576
|
-
)
|
|
577
|
-
|
|
578
|
-
expect(adapter.can?(user, :read)).to be true
|
|
579
|
-
expect(adapter.can?(user, :write)).to be true
|
|
580
|
-
expect(adapter.can?(user, :delete)).to be false
|
|
581
|
-
expect(adapter.can?(user, :manage_users)).to be false
|
|
582
|
-
expect(adapter.can?(user, :audit)).to be false
|
|
583
|
-
end
|
|
584
|
-
|
|
585
|
-
it "checks permissions for viewer role" do
|
|
586
|
-
user = DecisionAgent::Auth::User.new(
|
|
587
|
-
id: "user3",
|
|
588
|
-
email: "viewer@example.com",
|
|
589
|
-
password: "password123",
|
|
590
|
-
roles: [:viewer]
|
|
591
|
-
)
|
|
592
|
-
|
|
593
|
-
expect(adapter.can?(user, :read)).to be true
|
|
594
|
-
expect(adapter.can?(user, :write)).to be false
|
|
595
|
-
expect(adapter.can?(user, :delete)).to be false
|
|
596
|
-
expect(adapter.can?(user, :manage_users)).to be false
|
|
597
|
-
end
|
|
598
|
-
|
|
599
|
-
it "checks permissions for approver role" do
|
|
600
|
-
user = DecisionAgent::Auth::User.new(
|
|
601
|
-
id: "user4",
|
|
602
|
-
email: "approver@example.com",
|
|
603
|
-
password: "password123",
|
|
604
|
-
roles: [:approver]
|
|
605
|
-
)
|
|
606
|
-
|
|
607
|
-
expect(adapter.can?(user, :read)).to be true
|
|
608
|
-
expect(adapter.can?(user, :approve)).to be true
|
|
609
|
-
expect(adapter.can?(user, :write)).to be false
|
|
610
|
-
expect(adapter.can?(user, :delete)).to be false
|
|
611
|
-
end
|
|
612
|
-
|
|
613
|
-
it "checks permissions for auditor role" do
|
|
614
|
-
user = DecisionAgent::Auth::User.new(
|
|
615
|
-
id: "user5",
|
|
616
|
-
email: "auditor@example.com",
|
|
617
|
-
password: "password123",
|
|
618
|
-
roles: [:auditor]
|
|
619
|
-
)
|
|
620
|
-
|
|
621
|
-
expect(adapter.can?(user, :read)).to be true
|
|
622
|
-
expect(adapter.can?(user, :audit)).to be true
|
|
623
|
-
expect(adapter.can?(user, :write)).to be false
|
|
624
|
-
expect(adapter.can?(user, :delete)).to be false
|
|
625
|
-
end
|
|
626
|
-
|
|
627
|
-
it "checks permissions for user with multiple roles" do
|
|
628
|
-
user = DecisionAgent::Auth::User.new(
|
|
629
|
-
id: "user6",
|
|
630
|
-
email: "multi@example.com",
|
|
631
|
-
password: "password123",
|
|
632
|
-
roles: %i[viewer editor]
|
|
633
|
-
)
|
|
634
|
-
|
|
635
|
-
# Should have permissions from both roles
|
|
636
|
-
expect(adapter.can?(user, :read)).to be true # from viewer
|
|
637
|
-
expect(adapter.can?(user, :write)).to be true # from editor
|
|
638
|
-
expect(adapter.can?(user, :delete)).to be false
|
|
639
|
-
end
|
|
640
|
-
|
|
641
|
-
it "checks role membership using real Role class" do
|
|
642
|
-
admin_user = DecisionAgent::Auth::User.new(
|
|
643
|
-
id: "admin1",
|
|
644
|
-
email: "admin@example.com",
|
|
645
|
-
password: "password123",
|
|
646
|
-
roles: [:admin]
|
|
647
|
-
)
|
|
648
|
-
|
|
649
|
-
expect(adapter.has_role?(admin_user, :admin)).to be true
|
|
650
|
-
expect(adapter.has_role?(admin_user, :editor)).to be false
|
|
651
|
-
expect(adapter.has_role?(admin_user, :viewer)).to be false
|
|
652
|
-
|
|
653
|
-
editor_user = DecisionAgent::Auth::User.new(
|
|
654
|
-
id: "editor1",
|
|
655
|
-
email: "editor@example.com",
|
|
656
|
-
password: "password123",
|
|
657
|
-
roles: [:editor]
|
|
658
|
-
)
|
|
659
|
-
|
|
660
|
-
expect(adapter.has_role?(editor_user, :editor)).to be true
|
|
661
|
-
expect(adapter.has_role?(editor_user, :admin)).to be false
|
|
662
|
-
end
|
|
663
|
-
|
|
664
|
-
it "checks role membership for users with multiple roles" do
|
|
665
|
-
user = DecisionAgent::Auth::User.new(
|
|
666
|
-
id: "multi1",
|
|
667
|
-
email: "multi@example.com",
|
|
668
|
-
password: "password123",
|
|
669
|
-
roles: %i[admin editor]
|
|
670
|
-
)
|
|
671
|
-
|
|
672
|
-
expect(adapter.has_role?(user, :admin)).to be true
|
|
673
|
-
expect(adapter.has_role?(user, :editor)).to be true
|
|
674
|
-
expect(adapter.has_role?(user, :viewer)).to be false
|
|
675
|
-
end
|
|
676
|
-
|
|
677
|
-
it "handles dynamic role assignment" do
|
|
678
|
-
user = DecisionAgent::Auth::User.new(
|
|
679
|
-
id: "dynamic1",
|
|
680
|
-
email: "dynamic@example.com",
|
|
681
|
-
password: "password123",
|
|
682
|
-
roles: []
|
|
683
|
-
)
|
|
684
|
-
|
|
685
|
-
expect(adapter.has_role?(user, :admin)).to be false
|
|
686
|
-
expect(adapter.can?(user, :read)).to be false
|
|
687
|
-
|
|
688
|
-
# Assign role dynamically
|
|
689
|
-
user.assign_role(:viewer)
|
|
690
|
-
|
|
691
|
-
expect(adapter.has_role?(user, :viewer)).to be true
|
|
692
|
-
expect(adapter.can?(user, :read)).to be true
|
|
693
|
-
expect(adapter.can?(user, :write)).to be false
|
|
694
|
-
end
|
|
695
|
-
|
|
696
|
-
it "handles role removal" do
|
|
697
|
-
user = DecisionAgent::Auth::User.new(
|
|
698
|
-
id: "remove1",
|
|
699
|
-
email: "remove@example.com",
|
|
700
|
-
password: "password123",
|
|
701
|
-
roles: %i[admin editor]
|
|
702
|
-
)
|
|
703
|
-
|
|
704
|
-
expect(adapter.has_role?(user, :admin)).to be true
|
|
705
|
-
expect(adapter.can?(user, :manage_users)).to be true
|
|
706
|
-
|
|
707
|
-
# Remove admin role
|
|
708
|
-
user.remove_role(:admin)
|
|
709
|
-
|
|
710
|
-
expect(adapter.has_role?(user, :admin)).to be false
|
|
711
|
-
expect(adapter.has_role?(user, :editor)).to be true
|
|
712
|
-
expect(adapter.can?(user, :manage_users)).to be false
|
|
713
|
-
expect(adapter.can?(user, :write)).to be true # Still has editor role
|
|
714
|
-
end
|
|
715
|
-
|
|
716
|
-
it "checks active status with real User objects" do
|
|
717
|
-
active_user = DecisionAgent::Auth::User.new(
|
|
718
|
-
id: "active1",
|
|
719
|
-
email: "active@example.com",
|
|
720
|
-
password: "password123",
|
|
721
|
-
roles: [:admin],
|
|
722
|
-
active: true
|
|
723
|
-
)
|
|
724
|
-
|
|
725
|
-
expect(adapter.active?(active_user)).to be true
|
|
726
|
-
expect(adapter.can?(active_user, :read)).to be true
|
|
727
|
-
|
|
728
|
-
inactive_user = DecisionAgent::Auth::User.new(
|
|
729
|
-
id: "inactive1",
|
|
730
|
-
email: "inactive@example.com",
|
|
731
|
-
password: "password123",
|
|
732
|
-
roles: [:admin],
|
|
733
|
-
active: false
|
|
734
|
-
)
|
|
735
|
-
|
|
736
|
-
expect(adapter.active?(inactive_user)).to be false
|
|
737
|
-
expect(adapter.can?(inactive_user, :read)).to be false # Inactive users can't do anything
|
|
738
|
-
end
|
|
739
|
-
|
|
740
|
-
it "gets user ID and email from real User objects" do
|
|
741
|
-
user = DecisionAgent::Auth::User.new(
|
|
742
|
-
id: "userid123",
|
|
743
|
-
email: "real@example.com",
|
|
744
|
-
password: "password123",
|
|
745
|
-
roles: [:admin]
|
|
746
|
-
)
|
|
747
|
-
|
|
748
|
-
expect(adapter.user_id(user)).to eq("userid123")
|
|
749
|
-
expect(adapter.user_email(user)).to eq("real@example.com")
|
|
750
|
-
end
|
|
751
|
-
|
|
752
|
-
it "verifies permission checking integrates with real Role.has_permission? method" do
|
|
753
|
-
# Test that the adapter correctly uses Role.has_permission? for all defined roles
|
|
754
|
-
roles_to_test = DecisionAgent::Auth::Role.all
|
|
755
|
-
|
|
756
|
-
roles_to_test.each do |role|
|
|
757
|
-
user = DecisionAgent::Auth::User.new(
|
|
758
|
-
id: "role_test_#{role}",
|
|
759
|
-
email: "#{role}@example.com",
|
|
760
|
-
password: "password123",
|
|
761
|
-
roles: [role]
|
|
762
|
-
)
|
|
763
|
-
|
|
764
|
-
# Get expected permissions from Role class
|
|
765
|
-
expected_permissions = DecisionAgent::Auth::Role.permissions_for(role)
|
|
766
|
-
|
|
767
|
-
# Verify adapter returns same permissions
|
|
768
|
-
all_permissions = %i[read write delete approve deploy manage_users audit]
|
|
769
|
-
all_permissions.each do |permission|
|
|
770
|
-
expected = expected_permissions.include?(permission)
|
|
771
|
-
actual = adapter.can?(user, permission)
|
|
772
|
-
expect(actual).to eq(expected), "Role #{role} permission #{permission} mismatch: expected #{expected}, got #{actual}"
|
|
773
|
-
end
|
|
774
|
-
end
|
|
775
|
-
end
|
|
776
|
-
end
|
|
777
|
-
end
|
|
778
|
-
end
|