decision_agent 0.1.3 → 0.1.6

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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +84 -233
  3. data/lib/decision_agent/ab_testing/ab_test.rb +197 -0
  4. data/lib/decision_agent/ab_testing/ab_test_assignment.rb +76 -0
  5. data/lib/decision_agent/ab_testing/ab_test_manager.rb +317 -0
  6. data/lib/decision_agent/ab_testing/ab_testing_agent.rb +188 -0
  7. data/lib/decision_agent/ab_testing/storage/activerecord_adapter.rb +155 -0
  8. data/lib/decision_agent/ab_testing/storage/adapter.rb +67 -0
  9. data/lib/decision_agent/ab_testing/storage/memory_adapter.rb +116 -0
  10. data/lib/decision_agent/agent.rb +5 -3
  11. data/lib/decision_agent/auth/access_audit_logger.rb +122 -0
  12. data/lib/decision_agent/auth/authenticator.rb +127 -0
  13. data/lib/decision_agent/auth/password_reset_manager.rb +57 -0
  14. data/lib/decision_agent/auth/password_reset_token.rb +33 -0
  15. data/lib/decision_agent/auth/permission.rb +29 -0
  16. data/lib/decision_agent/auth/permission_checker.rb +43 -0
  17. data/lib/decision_agent/auth/rbac_adapter.rb +278 -0
  18. data/lib/decision_agent/auth/rbac_config.rb +51 -0
  19. data/lib/decision_agent/auth/role.rb +56 -0
  20. data/lib/decision_agent/auth/session.rb +33 -0
  21. data/lib/decision_agent/auth/session_manager.rb +57 -0
  22. data/lib/decision_agent/auth/user.rb +70 -0
  23. data/lib/decision_agent/context.rb +24 -4
  24. data/lib/decision_agent/decision.rb +10 -3
  25. data/lib/decision_agent/dsl/condition_evaluator.rb +378 -1
  26. data/lib/decision_agent/dsl/schema_validator.rb +8 -1
  27. data/lib/decision_agent/errors.rb +38 -0
  28. data/lib/decision_agent/evaluation.rb +10 -3
  29. data/lib/decision_agent/evaluation_validator.rb +8 -13
  30. data/lib/decision_agent/monitoring/dashboard_server.rb +1 -0
  31. data/lib/decision_agent/monitoring/metrics_collector.rb +164 -7
  32. data/lib/decision_agent/monitoring/storage/activerecord_adapter.rb +253 -0
  33. data/lib/decision_agent/monitoring/storage/base_adapter.rb +90 -0
  34. data/lib/decision_agent/monitoring/storage/memory_adapter.rb +222 -0
  35. data/lib/decision_agent/testing/batch_test_importer.rb +373 -0
  36. data/lib/decision_agent/testing/batch_test_runner.rb +244 -0
  37. data/lib/decision_agent/testing/test_coverage_analyzer.rb +191 -0
  38. data/lib/decision_agent/testing/test_result_comparator.rb +235 -0
  39. data/lib/decision_agent/testing/test_scenario.rb +42 -0
  40. data/lib/decision_agent/version.rb +10 -1
  41. data/lib/decision_agent/versioning/activerecord_adapter.rb +1 -1
  42. data/lib/decision_agent/versioning/file_storage_adapter.rb +96 -28
  43. data/lib/decision_agent/web/middleware/auth_middleware.rb +45 -0
  44. data/lib/decision_agent/web/middleware/permission_middleware.rb +94 -0
  45. data/lib/decision_agent/web/public/app.js +184 -29
  46. data/lib/decision_agent/web/public/batch_testing.html +640 -0
  47. data/lib/decision_agent/web/public/index.html +37 -9
  48. data/lib/decision_agent/web/public/login.html +298 -0
  49. data/lib/decision_agent/web/public/users.html +679 -0
  50. data/lib/decision_agent/web/server.rb +873 -7
  51. data/lib/decision_agent.rb +59 -0
  52. data/lib/generators/decision_agent/install/install_generator.rb +37 -0
  53. data/lib/generators/decision_agent/install/templates/ab_test_assignment_model.rb +45 -0
  54. data/lib/generators/decision_agent/install/templates/ab_test_model.rb +54 -0
  55. data/lib/generators/decision_agent/install/templates/ab_testing_migration.rb +43 -0
  56. data/lib/generators/decision_agent/install/templates/ab_testing_tasks.rake +189 -0
  57. data/lib/generators/decision_agent/install/templates/decision_agent_tasks.rake +114 -0
  58. data/lib/generators/decision_agent/install/templates/decision_log.rb +57 -0
  59. data/lib/generators/decision_agent/install/templates/error_metric.rb +53 -0
  60. data/lib/generators/decision_agent/install/templates/evaluation_metric.rb +43 -0
  61. data/lib/generators/decision_agent/install/templates/monitoring_migration.rb +109 -0
  62. data/lib/generators/decision_agent/install/templates/performance_metric.rb +76 -0
  63. data/lib/generators/decision_agent/install/templates/rule_version.rb +1 -1
  64. data/spec/ab_testing/ab_test_assignment_spec.rb +253 -0
  65. data/spec/ab_testing/ab_test_manager_spec.rb +612 -0
  66. data/spec/ab_testing/ab_test_spec.rb +270 -0
  67. data/spec/ab_testing/ab_testing_agent_spec.rb +481 -0
  68. data/spec/ab_testing/storage/adapter_spec.rb +64 -0
  69. data/spec/ab_testing/storage/memory_adapter_spec.rb +485 -0
  70. data/spec/advanced_operators_spec.rb +1003 -0
  71. data/spec/agent_spec.rb +40 -0
  72. data/spec/audit_adapters_spec.rb +18 -0
  73. data/spec/auth/access_audit_logger_spec.rb +394 -0
  74. data/spec/auth/authenticator_spec.rb +112 -0
  75. data/spec/auth/password_reset_spec.rb +294 -0
  76. data/spec/auth/permission_checker_spec.rb +207 -0
  77. data/spec/auth/permission_spec.rb +73 -0
  78. data/spec/auth/rbac_adapter_spec.rb +550 -0
  79. data/spec/auth/rbac_config_spec.rb +82 -0
  80. data/spec/auth/role_spec.rb +51 -0
  81. data/spec/auth/session_manager_spec.rb +172 -0
  82. data/spec/auth/session_spec.rb +112 -0
  83. data/spec/auth/user_spec.rb +130 -0
  84. data/spec/context_spec.rb +43 -0
  85. data/spec/decision_agent_spec.rb +96 -0
  86. data/spec/decision_spec.rb +423 -0
  87. data/spec/dsl/condition_evaluator_spec.rb +774 -0
  88. data/spec/evaluation_spec.rb +364 -0
  89. data/spec/evaluation_validator_spec.rb +165 -0
  90. data/spec/examples.txt +1542 -548
  91. data/spec/issue_verification_spec.rb +95 -21
  92. data/spec/monitoring/metrics_collector_spec.rb +221 -3
  93. data/spec/monitoring/monitored_agent_spec.rb +1 -1
  94. data/spec/monitoring/prometheus_exporter_spec.rb +1 -1
  95. data/spec/monitoring/storage/activerecord_adapter_spec.rb +498 -0
  96. data/spec/monitoring/storage/base_adapter_spec.rb +61 -0
  97. data/spec/monitoring/storage/memory_adapter_spec.rb +247 -0
  98. data/spec/performance_optimizations_spec.rb +486 -0
  99. data/spec/spec_helper.rb +23 -0
  100. data/spec/testing/batch_test_importer_spec.rb +693 -0
  101. data/spec/testing/batch_test_runner_spec.rb +307 -0
  102. data/spec/testing/test_coverage_analyzer_spec.rb +292 -0
  103. data/spec/testing/test_result_comparator_spec.rb +392 -0
  104. data/spec/testing/test_scenario_spec.rb +113 -0
  105. data/spec/versioning/adapter_spec.rb +156 -0
  106. data/spec/versioning_spec.rb +253 -0
  107. data/spec/web/middleware/auth_middleware_spec.rb +133 -0
  108. data/spec/web/middleware/permission_middleware_spec.rb +247 -0
  109. data/spec/web_ui_rack_spec.rb +1705 -0
  110. metadata +123 -6
@@ -0,0 +1,550 @@
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
+ end
@@ -0,0 +1,82 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe DecisionAgent::Auth::RbacConfig do
4
+ let(:config) { described_class.new }
5
+
6
+ describe "#initialize" do
7
+ it "initializes with nil adapter, authenticator, and user_store" do
8
+ expect(config.instance_variable_get(:@adapter)).to be_nil
9
+ expect(config.authenticator).to be_nil
10
+ expect(config.user_store).to be_nil
11
+ end
12
+ end
13
+
14
+ describe "#use" do
15
+ it "configures default adapter" do
16
+ config.use(:default)
17
+ expect(config.adapter).to be_a(DecisionAgent::Auth::DefaultAdapter)
18
+ end
19
+
20
+ it "configures devise_cancan adapter" do
21
+ config.use(:devise_cancan)
22
+ expect(config.adapter).to be_a(DecisionAgent::Auth::DeviseCanCanAdapter)
23
+ end
24
+
25
+ it "configures pundit adapter" do
26
+ config.use(:pundit)
27
+ expect(config.adapter).to be_a(DecisionAgent::Auth::PunditAdapter)
28
+ end
29
+
30
+ it "configures custom adapter" do
31
+ config.use(:custom)
32
+ expect(config.adapter).to be_a(DecisionAgent::Auth::CustomAdapter)
33
+ end
34
+
35
+ it "raises error for unknown adapter type" do
36
+ expect do
37
+ config.use(:unknown)
38
+ end.to raise_error(ArgumentError, /Unknown adapter type/)
39
+ end
40
+
41
+ it "returns self for chaining" do
42
+ result = config.use(:default)
43
+ expect(result).to eq(config)
44
+ end
45
+ end
46
+
47
+ describe "#adapter=" do
48
+ it "sets custom adapter instance" do
49
+ custom_adapter = DecisionAgent::Auth::DefaultAdapter.new
50
+ config.adapter = custom_adapter
51
+ expect(config.adapter).to eq(custom_adapter)
52
+ end
53
+
54
+ it "raises error for non-RbacAdapter instance" do
55
+ expect do
56
+ config.adapter = "not an adapter"
57
+ end.to raise_error(ArgumentError, /must be an instance of DecisionAgent::Auth::RbacAdapter/)
58
+ end
59
+ end
60
+
61
+ describe "#adapter" do
62
+ it "returns configured adapter" do
63
+ config.use(:default)
64
+ expect(config.adapter).to be_a(DecisionAgent::Auth::DefaultAdapter)
65
+ end
66
+
67
+ it "returns default adapter if none configured" do
68
+ expect(config.adapter).to be_a(DecisionAgent::Auth::DefaultAdapter)
69
+ end
70
+ end
71
+
72
+ describe "#configured?" do
73
+ it "returns false when no adapter configured" do
74
+ expect(config.configured?).to be false
75
+ end
76
+
77
+ it "returns true when adapter configured" do
78
+ config.use(:default)
79
+ expect(config.configured?).to be true
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,51 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe DecisionAgent::Auth::Role do
4
+ describe ".all" do
5
+ it "returns all role symbols" do
6
+ roles = DecisionAgent::Auth::Role.all
7
+ expect(roles).to include(:admin, :editor, :viewer, :auditor, :approver)
8
+ end
9
+ end
10
+
11
+ describe ".exists?" do
12
+ it "returns true for valid roles" do
13
+ expect(DecisionAgent::Auth::Role.exists?(:admin)).to be true
14
+ expect(DecisionAgent::Auth::Role.exists?(:editor)).to be true
15
+ end
16
+
17
+ it "returns false for invalid roles" do
18
+ expect(DecisionAgent::Auth::Role.exists?(:invalid)).to be false
19
+ end
20
+ end
21
+
22
+ describe ".permissions_for" do
23
+ it "returns permissions for admin role" do
24
+ permissions = DecisionAgent::Auth::Role.permissions_for(:admin)
25
+ expect(permissions).to include(:read, :write, :delete, :approve, :deploy, :manage_users, :audit)
26
+ end
27
+
28
+ it "returns permissions for editor role" do
29
+ permissions = DecisionAgent::Auth::Role.permissions_for(:editor)
30
+ expect(permissions).to include(:read, :write)
31
+ end
32
+
33
+ it "returns permissions for viewer role" do
34
+ permissions = DecisionAgent::Auth::Role.permissions_for(:viewer)
35
+ expect(permissions).to include(:read)
36
+ end
37
+
38
+ it "returns empty array for invalid role" do
39
+ permissions = DecisionAgent::Auth::Role.permissions_for(:invalid)
40
+ expect(permissions).to eq([])
41
+ end
42
+ end
43
+
44
+ describe ".has_permission?" do
45
+ it "returns true if role has permission" do
46
+ expect(DecisionAgent::Auth::Role.has_permission?(:admin, :write)).to be true
47
+ expect(DecisionAgent::Auth::Role.has_permission?(:editor, :write)).to be true
48
+ expect(DecisionAgent::Auth::Role.has_permission?(:viewer, :write)).to be false
49
+ end
50
+ end
51
+ end