decision_agent 0.2.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.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +313 -8
  3. data/bin/decision_agent +104 -0
  4. data/lib/decision_agent/agent.rb +72 -1
  5. data/lib/decision_agent/context.rb +1 -0
  6. data/lib/decision_agent/data_enrichment/cache/memory_adapter.rb +86 -0
  7. data/lib/decision_agent/data_enrichment/cache_adapter.rb +49 -0
  8. data/lib/decision_agent/data_enrichment/circuit_breaker.rb +135 -0
  9. data/lib/decision_agent/data_enrichment/client.rb +220 -0
  10. data/lib/decision_agent/data_enrichment/config.rb +78 -0
  11. data/lib/decision_agent/data_enrichment/errors.rb +36 -0
  12. data/lib/decision_agent/decision.rb +102 -2
  13. data/lib/decision_agent/dmn/adapter.rb +135 -0
  14. data/lib/decision_agent/dmn/cache.rb +306 -0
  15. data/lib/decision_agent/dmn/decision_graph.rb +327 -0
  16. data/lib/decision_agent/dmn/decision_tree.rb +192 -0
  17. data/lib/decision_agent/dmn/errors.rb +30 -0
  18. data/lib/decision_agent/dmn/exporter.rb +217 -0
  19. data/lib/decision_agent/dmn/feel/evaluator.rb +819 -0
  20. data/lib/decision_agent/dmn/feel/functions.rb +420 -0
  21. data/lib/decision_agent/dmn/feel/parser.rb +349 -0
  22. data/lib/decision_agent/dmn/feel/simple_parser.rb +276 -0
  23. data/lib/decision_agent/dmn/feel/transformer.rb +372 -0
  24. data/lib/decision_agent/dmn/feel/types.rb +276 -0
  25. data/lib/decision_agent/dmn/importer.rb +77 -0
  26. data/lib/decision_agent/dmn/model.rb +197 -0
  27. data/lib/decision_agent/dmn/parser.rb +191 -0
  28. data/lib/decision_agent/dmn/testing.rb +333 -0
  29. data/lib/decision_agent/dmn/validator.rb +315 -0
  30. data/lib/decision_agent/dmn/versioning.rb +229 -0
  31. data/lib/decision_agent/dmn/visualizer.rb +513 -0
  32. data/lib/decision_agent/dsl/condition_evaluator.rb +984 -838
  33. data/lib/decision_agent/dsl/schema_validator.rb +53 -14
  34. data/lib/decision_agent/evaluators/dmn_evaluator.rb +308 -0
  35. data/lib/decision_agent/evaluators/json_rule_evaluator.rb +69 -9
  36. data/lib/decision_agent/explainability/condition_trace.rb +83 -0
  37. data/lib/decision_agent/explainability/explainability_result.rb +52 -0
  38. data/lib/decision_agent/explainability/rule_trace.rb +39 -0
  39. data/lib/decision_agent/explainability/trace_collector.rb +24 -0
  40. data/lib/decision_agent/monitoring/alert_manager.rb +5 -1
  41. data/lib/decision_agent/simulation/errors.rb +18 -0
  42. data/lib/decision_agent/simulation/impact_analyzer.rb +498 -0
  43. data/lib/decision_agent/simulation/monte_carlo_simulator.rb +635 -0
  44. data/lib/decision_agent/simulation/replay_engine.rb +486 -0
  45. data/lib/decision_agent/simulation/scenario_engine.rb +318 -0
  46. data/lib/decision_agent/simulation/scenario_library.rb +163 -0
  47. data/lib/decision_agent/simulation/shadow_test_engine.rb +287 -0
  48. data/lib/decision_agent/simulation/what_if_analyzer.rb +1002 -0
  49. data/lib/decision_agent/simulation.rb +17 -0
  50. data/lib/decision_agent/version.rb +1 -1
  51. data/lib/decision_agent/versioning/activerecord_adapter.rb +23 -8
  52. data/lib/decision_agent/web/dmn_editor.rb +426 -0
  53. data/lib/decision_agent/web/public/app.js +119 -0
  54. data/lib/decision_agent/web/public/dmn-editor.css +596 -0
  55. data/lib/decision_agent/web/public/dmn-editor.html +250 -0
  56. data/lib/decision_agent/web/public/dmn-editor.js +553 -0
  57. data/lib/decision_agent/web/public/index.html +52 -0
  58. data/lib/decision_agent/web/public/simulation.html +130 -0
  59. data/lib/decision_agent/web/public/simulation_impact.html +478 -0
  60. data/lib/decision_agent/web/public/simulation_replay.html +551 -0
  61. data/lib/decision_agent/web/public/simulation_shadow.html +546 -0
  62. data/lib/decision_agent/web/public/simulation_whatif.html +532 -0
  63. data/lib/decision_agent/web/public/styles.css +86 -0
  64. data/lib/decision_agent/web/server.rb +1059 -23
  65. data/lib/decision_agent.rb +60 -2
  66. metadata +105 -61
  67. data/spec/ab_testing/ab_test_assignment_spec.rb +0 -253
  68. data/spec/ab_testing/ab_test_manager_spec.rb +0 -612
  69. data/spec/ab_testing/ab_test_spec.rb +0 -270
  70. data/spec/ab_testing/ab_testing_agent_spec.rb +0 -481
  71. data/spec/ab_testing/storage/adapter_spec.rb +0 -64
  72. data/spec/ab_testing/storage/memory_adapter_spec.rb +0 -485
  73. data/spec/activerecord_thread_safety_spec.rb +0 -553
  74. data/spec/advanced_operators_spec.rb +0 -3150
  75. data/spec/agent_spec.rb +0 -289
  76. data/spec/api_contract_spec.rb +0 -430
  77. data/spec/audit_adapters_spec.rb +0 -92
  78. data/spec/auth/access_audit_logger_spec.rb +0 -394
  79. data/spec/auth/authenticator_spec.rb +0 -112
  80. data/spec/auth/password_reset_spec.rb +0 -294
  81. data/spec/auth/permission_checker_spec.rb +0 -207
  82. data/spec/auth/permission_spec.rb +0 -73
  83. data/spec/auth/rbac_adapter_spec.rb +0 -550
  84. data/spec/auth/rbac_config_spec.rb +0 -82
  85. data/spec/auth/role_spec.rb +0 -51
  86. data/spec/auth/session_manager_spec.rb +0 -172
  87. data/spec/auth/session_spec.rb +0 -112
  88. data/spec/auth/user_spec.rb +0 -130
  89. data/spec/comprehensive_edge_cases_spec.rb +0 -1777
  90. data/spec/context_spec.rb +0 -127
  91. data/spec/decision_agent_spec.rb +0 -96
  92. data/spec/decision_spec.rb +0 -423
  93. data/spec/dsl/condition_evaluator_spec.rb +0 -774
  94. data/spec/dsl_validation_spec.rb +0 -648
  95. data/spec/edge_cases_spec.rb +0 -353
  96. data/spec/evaluation_spec.rb +0 -364
  97. data/spec/evaluation_validator_spec.rb +0 -165
  98. data/spec/examples/feedback_aware_evaluator_spec.rb +0 -460
  99. data/spec/examples.txt +0 -1633
  100. data/spec/issue_verification_spec.rb +0 -759
  101. data/spec/json_rule_evaluator_spec.rb +0 -587
  102. data/spec/monitoring/alert_manager_spec.rb +0 -378
  103. data/spec/monitoring/metrics_collector_spec.rb +0 -499
  104. data/spec/monitoring/monitored_agent_spec.rb +0 -222
  105. data/spec/monitoring/prometheus_exporter_spec.rb +0 -242
  106. data/spec/monitoring/storage/activerecord_adapter_spec.rb +0 -498
  107. data/spec/monitoring/storage/base_adapter_spec.rb +0 -61
  108. data/spec/monitoring/storage/memory_adapter_spec.rb +0 -247
  109. data/spec/performance_optimizations_spec.rb +0 -486
  110. data/spec/replay_edge_cases_spec.rb +0 -699
  111. data/spec/replay_spec.rb +0 -210
  112. data/spec/rfc8785_canonicalization_spec.rb +0 -215
  113. data/spec/scoring_spec.rb +0 -225
  114. data/spec/spec_helper.rb +0 -60
  115. data/spec/testing/batch_test_importer_spec.rb +0 -693
  116. data/spec/testing/batch_test_runner_spec.rb +0 -307
  117. data/spec/testing/test_coverage_analyzer_spec.rb +0 -292
  118. data/spec/testing/test_result_comparator_spec.rb +0 -392
  119. data/spec/testing/test_scenario_spec.rb +0 -113
  120. data/spec/thread_safety_spec.rb +0 -482
  121. data/spec/thread_safety_spec.rb.broken +0 -878
  122. data/spec/versioning/adapter_spec.rb +0 -156
  123. data/spec/versioning_spec.rb +0 -1030
  124. data/spec/web/middleware/auth_middleware_spec.rb +0 -133
  125. data/spec/web/middleware/permission_middleware_spec.rb +0 -247
  126. data/spec/web_ui_rack_spec.rb +0 -1840
@@ -1,394 +0,0 @@
1
- require "spec_helper"
2
-
3
- RSpec.describe DecisionAgent::Auth::AccessAuditLogger do
4
- let(:adapter) { DecisionAgent::Audit::InMemoryAccessAdapter.new }
5
- let(:logger) { DecisionAgent::Auth::AccessAuditLogger.new(adapter: adapter) }
6
-
7
- describe "#log_authentication" do
8
- it "logs successful login" do
9
- logger.log_authentication(
10
- "login",
11
- user_id: "user123",
12
- email: "test@example.com",
13
- success: true
14
- )
15
-
16
- logs = adapter.all_logs
17
- expect(logs.size).to eq(1)
18
- expect(logs.first[:event_type]).to eq("login")
19
- expect(logs.first[:user_id]).to eq("user123")
20
- expect(logs.first[:success]).to be true
21
- end
22
-
23
- it "logs failed login" do
24
- logger.log_authentication(
25
- "login",
26
- user_id: nil,
27
- email: "test@example.com",
28
- success: false,
29
- reason: "Invalid password"
30
- )
31
-
32
- logs = adapter.all_logs
33
- expect(logs.size).to eq(1)
34
- expect(logs.first[:success]).to be false
35
- expect(logs.first[:reason]).to eq("Invalid password")
36
- end
37
- end
38
-
39
- describe "#log_permission_check" do
40
- it "logs permission check" do
41
- logger.log_permission_check(
42
- user_id: "user123",
43
- permission: :write,
44
- resource_type: "rule",
45
- resource_id: "rule456",
46
- granted: true
47
- )
48
-
49
- logs = adapter.all_logs
50
- expect(logs.size).to eq(1)
51
- expect(logs.first[:event_type]).to eq("permission_check")
52
- expect(logs.first[:permission]).to eq("write")
53
- expect(logs.first[:granted]).to be true
54
- end
55
- end
56
-
57
- describe "#log_access" do
58
- it "logs access event" do
59
- logger.log_access(
60
- user_id: "user123",
61
- action: "create",
62
- resource_type: "rule",
63
- resource_id: "rule456",
64
- success: true
65
- )
66
-
67
- logs = adapter.all_logs
68
- expect(logs.size).to eq(1)
69
- expect(logs.first[:event_type]).to eq("access")
70
- expect(logs.first[:action]).to eq("create")
71
- end
72
- end
73
-
74
- describe "#query" do
75
- before do
76
- logger.log_authentication("login", user_id: "user1", email: "user1@example.com", success: true)
77
- logger.log_authentication("login", user_id: "user2", email: "user2@example.com", success: true)
78
- logger.log_permission_check(user_id: "user1", permission: :write, granted: true)
79
- end
80
-
81
- it "filters by user_id" do
82
- logs = logger.query(user_id: "user1")
83
- expect(logs.size).to eq(2)
84
- expect(logs.all? { |log| log[:user_id] == "user1" }).to be true
85
- end
86
-
87
- it "filters by event_type" do
88
- logs = logger.query(event_type: "login")
89
- expect(logs.size).to eq(2)
90
- expect(logs.all? { |log| log[:event_type] == "login" }).to be true
91
- end
92
-
93
- it "filters by start_time" do
94
- start_time = Time.now.utc - 3600
95
- logger.log_authentication("login", user_id: "user3", email: "user3@example.com", success: true)
96
-
97
- logs = logger.query(start_time: start_time)
98
- expect(logs.size).to be >= 1
99
- end
100
-
101
- it "limits results" do
102
- logs = logger.query(limit: 2)
103
- expect(logs.size).to eq(2)
104
- end
105
-
106
- it "filters by end_time" do
107
- end_time = Time.now.utc + 3600
108
- logs = logger.query(end_time: end_time)
109
- expect(logs.size).to eq(3) # All logs are before end_time
110
- end
111
-
112
- it "filters by start_time and end_time together" do
113
- start_time = Time.now.utc - 1800
114
- end_time = Time.now.utc + 1800
115
- logs = logger.query(start_time: start_time, end_time: end_time)
116
- expect(logs.size).to be >= 0
117
- end
118
-
119
- it "handles string timestamps" do
120
- start_time = (Time.now.utc - 3600).iso8601
121
- logs = logger.query(start_time: start_time)
122
- expect(logs).to be_an(Array)
123
- end
124
-
125
- it "returns logs in reverse order (most recent first)" do
126
- # Clear any existing logs first
127
- adapter.clear
128
-
129
- logger.log_authentication("test1", user_id: "user1", email: "user1@example.com", success: true)
130
- sleep(0.01) # Ensure different timestamps
131
- logger.log_authentication("test2", user_id: "user1", email: "user1@example.com", success: true)
132
-
133
- logs = logger.query(user_id: "user1")
134
- expect(logs.size).to eq(2)
135
- expect(logs.first[:event_type]).to eq("test2")
136
- expect(logs.last[:event_type]).to eq("test1")
137
- end
138
- end
139
-
140
- describe "#log_authentication" do
141
- it "includes timestamp in log entry" do
142
- logger.log_authentication("login", user_id: "user1", email: "user1@example.com", success: true)
143
- logs = adapter.all_logs
144
- expect(logs.first[:timestamp]).to be_a(String)
145
- expect { Time.parse(logs.first[:timestamp]) }.not_to raise_error
146
- end
147
-
148
- it "includes ip_address field (nil by default)" do
149
- logger.log_authentication("login", user_id: "user1", email: "user1@example.com", success: true)
150
- logs = adapter.all_logs
151
- expect(logs.first[:ip_address]).to be_nil
152
- end
153
-
154
- it "converts event_type to string" do
155
- logger.log_authentication(:login, user_id: "user1", email: "user1@example.com", success: true)
156
- logs = adapter.all_logs
157
- expect(logs.first[:event_type]).to eq("login")
158
- end
159
- end
160
-
161
- describe "#log_permission_check" do
162
- it "includes all fields in log entry" do
163
- logger.log_permission_check(
164
- user_id: "user123",
165
- permission: :write,
166
- resource_type: "rule",
167
- resource_id: "rule456",
168
- granted: false
169
- )
170
-
171
- logs = adapter.all_logs
172
- log = logs.first
173
- expect(log[:event_type]).to eq("permission_check")
174
- expect(log[:user_id]).to eq("user123")
175
- expect(log[:permission]).to eq("write")
176
- expect(log[:resource_type]).to eq("rule")
177
- expect(log[:resource_id]).to eq("rule456")
178
- expect(log[:granted]).to be false
179
- expect(log[:timestamp]).to be_a(String)
180
- end
181
-
182
- it "handles nil resource_type and resource_id" do
183
- logger.log_permission_check(
184
- user_id: "user123",
185
- permission: :read,
186
- granted: true
187
- )
188
-
189
- logs = adapter.all_logs
190
- log = logs.first
191
- expect(log[:resource_type]).to be_nil
192
- expect(log[:resource_id]).to be_nil
193
- end
194
- end
195
-
196
- describe "#log_access" do
197
- it "includes all fields in log entry" do
198
- logger.log_access(
199
- user_id: "user123",
200
- action: "delete",
201
- resource_type: "version",
202
- resource_id: "version789",
203
- success: false
204
- )
205
-
206
- logs = adapter.all_logs
207
- log = logs.first
208
- expect(log[:event_type]).to eq("access")
209
- expect(log[:user_id]).to eq("user123")
210
- expect(log[:action]).to eq("delete")
211
- expect(log[:resource_type]).to eq("version")
212
- expect(log[:resource_id]).to eq("version789")
213
- expect(log[:success]).to be false
214
- end
215
-
216
- it "converts action to string" do
217
- logger.log_access(user_id: "user1", action: :create, success: true)
218
- logs = adapter.all_logs
219
- expect(logs.first[:action]).to eq("create")
220
- end
221
- end
222
-
223
- describe "adapter attribute" do
224
- it "returns the configured adapter" do
225
- custom_adapter = double("CustomAdapter")
226
- logger = DecisionAgent::Auth::AccessAuditLogger.new(adapter: custom_adapter)
227
- expect(logger.adapter).to eq(custom_adapter)
228
- end
229
- end
230
- end
231
-
232
- # Test for InMemoryAccessAdapter - class is nested inside AccessAuditLogger
233
- # but may not be directly accessible. Testing through AccessAuditLogger instead.
234
- RSpec.describe DecisionAgent::Auth::AccessAuditLogger do
235
- describe "InMemoryAccessAdapter integration" do
236
- let(:logger) { DecisionAgent::Auth::AccessAuditLogger.new }
237
-
238
- it "uses InMemoryAccessAdapter by default" do
239
- expect(logger.adapter).to be_a(DecisionAgent::Audit::InMemoryAccessAdapter)
240
- rescue NameError
241
- # If class is not directly accessible, test through logger interface
242
- logger.log_authentication("test", user_id: "user1")
243
- logs = logger.adapter.all_logs
244
- expect(logs.size).to eq(1)
245
- end
246
- end
247
- end
248
-
249
- RSpec.describe DecisionAgent::Audit::InMemoryAccessAdapter do
250
- let(:adapter) { described_class.new }
251
-
252
- describe "#initialize" do
253
- it "initializes with empty logs" do
254
- expect(adapter.all_logs).to eq([])
255
- end
256
- end
257
-
258
- describe "#record_access" do
259
- it "stores log entry" do
260
- log_entry = { event_type: "test", user_id: "user1", timestamp: Time.now.utc.iso8601 }
261
- adapter.record_access(log_entry)
262
- expect(adapter.all_logs.size).to eq(1)
263
- end
264
-
265
- it "stores duplicate of log entry" do
266
- log_entry = { event_type: "test", user_id: "user1", timestamp: Time.now.utc.iso8601 }
267
- adapter.record_access(log_entry)
268
- log_entry[:modified] = true
269
- expect(adapter.all_logs.first[:modified]).to be_nil
270
- end
271
-
272
- it "is thread-safe" do
273
- threads = []
274
- 10.times do |i|
275
- threads << Thread.new do
276
- 10.times do
277
- adapter.record_access({ event_type: "test", user_id: "user#{i}", timestamp: Time.now.utc.iso8601 })
278
- end
279
- end
280
- end
281
- threads.each(&:join)
282
- expect(adapter.all_logs.size).to eq(100)
283
- end
284
- end
285
-
286
- describe "#query_access_logs" do
287
- before do
288
- adapter.record_access({ event_type: "login", user_id: "user1", timestamp: (Time.now.utc - 7200).iso8601 })
289
- adapter.record_access({ event_type: "login", user_id: "user2", timestamp: (Time.now.utc - 3600).iso8601 })
290
- adapter.record_access({ event_type: "logout", user_id: "user1", timestamp: Time.now.utc.iso8601 })
291
- end
292
-
293
- it "filters by user_id" do
294
- logs = adapter.query_access_logs(user_id: "user1")
295
- expect(logs.size).to eq(2)
296
- expect(logs.all? { |log| log[:user_id] == "user1" }).to be true
297
- end
298
-
299
- it "filters by event_type" do
300
- logs = adapter.query_access_logs(event_type: "login")
301
- expect(logs.size).to eq(2)
302
- expect(logs.all? { |log| log[:event_type] == "login" }).to be true
303
- end
304
-
305
- it "filters by start_time" do
306
- start_time = Time.now.utc - 1800
307
- logs = adapter.query_access_logs(start_time: start_time)
308
- expect(logs.size).to eq(1)
309
- end
310
-
311
- it "filters by end_time" do
312
- end_time = Time.now.utc - 1800
313
- logs = adapter.query_access_logs(end_time: end_time)
314
- expect(logs.size).to eq(2)
315
- end
316
-
317
- it "limits results" do
318
- logs = adapter.query_access_logs(limit: 1)
319
- expect(logs.size).to eq(1)
320
- end
321
-
322
- it "handles string timestamps" do
323
- start_time = (Time.now.utc - 1800).iso8601
324
- logs = adapter.query_access_logs(start_time: start_time)
325
- expect(logs).to be_an(Array)
326
- end
327
-
328
- it "returns results in reverse order" do
329
- logs = adapter.query_access_logs
330
- expect(logs.first[:event_type]).to eq("logout")
331
- expect(logs.last[:event_type]).to eq("login")
332
- end
333
-
334
- it "is thread-safe" do
335
- threads = []
336
- 5.times do
337
- threads << Thread.new do
338
- adapter.query_access_logs(user_id: "user1")
339
- end
340
- end
341
- threads.each(&:join)
342
- # Should not raise errors
343
- expect(adapter.query_access_logs.size).to eq(3)
344
- end
345
- end
346
-
347
- describe "#all_logs" do
348
- it "returns copy of logs" do
349
- adapter.record_access({ event_type: "test", timestamp: Time.now.utc.iso8601 })
350
- logs1 = adapter.all_logs
351
- logs2 = adapter.all_logs
352
- expect(logs1).not_to be(logs2)
353
- logs1 << { modified: true }
354
- expect(adapter.all_logs.size).to eq(1)
355
- end
356
- end
357
-
358
- describe "#clear" do
359
- it "clears all logs" do
360
- adapter.record_access({ event_type: "test", timestamp: Time.now.utc.iso8601 })
361
- expect(adapter.all_logs.size).to eq(1)
362
- adapter.clear
363
- expect(adapter.all_logs.size).to eq(0)
364
- end
365
-
366
- it "is thread-safe" do
367
- adapter.record_access({ event_type: "test", timestamp: Time.now.utc.iso8601 })
368
- threads = []
369
- 5.times do
370
- threads << Thread.new do
371
- adapter.clear
372
- end
373
- end
374
- threads.each(&:join)
375
- expect(adapter.all_logs.size).to eq(0)
376
- end
377
- end
378
- end
379
-
380
- RSpec.describe DecisionAgent::Audit::AccessAdapter do
381
- let(:adapter) { described_class.new }
382
-
383
- describe "#record_access" do
384
- it "raises NotImplementedError" do
385
- expect { adapter.record_access({}) }.to raise_error(NotImplementedError, /must implement #record_access/)
386
- end
387
- end
388
-
389
- describe "#query_access_logs" do
390
- it "raises NotImplementedError" do
391
- expect { adapter.query_access_logs({}) }.to raise_error(NotImplementedError, /must implement #query_access_logs/)
392
- end
393
- end
394
- end
@@ -1,112 +0,0 @@
1
- require "spec_helper"
2
-
3
- RSpec.describe DecisionAgent::Auth::Authenticator do
4
- let(:authenticator) { DecisionAgent::Auth::Authenticator.new }
5
-
6
- describe "#create_user" do
7
- it "creates a new user" do
8
- user = authenticator.create_user(
9
- email: "test@example.com",
10
- password: "password123"
11
- )
12
-
13
- expect(user.email).to eq("test@example.com")
14
- expect(user.id).to be_a(String)
15
- end
16
-
17
- it "creates a user with roles" do
18
- user = authenticator.create_user(
19
- email: "admin@example.com",
20
- password: "password123",
21
- roles: %i[admin editor]
22
- )
23
-
24
- expect(user.roles).to include(:admin, :editor)
25
- end
26
- end
27
-
28
- describe "#login" do
29
- before do
30
- authenticator.create_user(
31
- email: "test@example.com",
32
- password: "password123"
33
- )
34
- end
35
-
36
- it "returns a session for valid credentials" do
37
- session = authenticator.login("test@example.com", "password123")
38
-
39
- expect(session).to be_a(DecisionAgent::Auth::Session)
40
- expect(session.user_id).to be_a(String)
41
- end
42
-
43
- it "returns nil for invalid email" do
44
- session = authenticator.login("wrong@example.com", "password123")
45
- expect(session).to be_nil
46
- end
47
-
48
- it "returns nil for invalid password" do
49
- session = authenticator.login("test@example.com", "wrongpassword")
50
- expect(session).to be_nil
51
- end
52
-
53
- it "returns nil for inactive user" do
54
- user = authenticator.find_user_by_email("test@example.com")
55
- user.active = false
56
-
57
- session = authenticator.login("test@example.com", "password123")
58
- expect(session).to be_nil
59
- end
60
- end
61
-
62
- describe "#logout" do
63
- it "deletes the session" do
64
- authenticator.create_user(
65
- email: "test@example.com",
66
- password: "password123"
67
- )
68
-
69
- session = authenticator.login("test@example.com", "password123")
70
- token = session.token
71
-
72
- authenticator.logout(token)
73
-
74
- expect(authenticator.authenticate(token)).to be_nil
75
- end
76
- end
77
-
78
- describe "#authenticate" do
79
- it "returns user and session for valid token" do
80
- authenticator.create_user(
81
- email: "test@example.com",
82
- password: "password123"
83
- )
84
-
85
- session = authenticator.login("test@example.com", "password123")
86
- result = authenticator.authenticate(session.token)
87
-
88
- expect(result).to be_a(Hash)
89
- expect(result[:user]).to be_a(DecisionAgent::Auth::User)
90
- expect(result[:session]).to be_a(DecisionAgent::Auth::Session)
91
- end
92
-
93
- it "returns nil for invalid token" do
94
- result = authenticator.authenticate("invalid_token")
95
- expect(result).to be_nil
96
- end
97
-
98
- it "returns nil for expired session" do
99
- authenticator.create_user(
100
- email: "test@example.com",
101
- password: "password123"
102
- )
103
-
104
- session = authenticator.login("test@example.com", "password123")
105
- # Manually expire the session
106
- session.instance_variable_set(:@expires_at, Time.now.utc - 1)
107
-
108
- result = authenticator.authenticate(session.token)
109
- expect(result).to be_nil
110
- end
111
- end
112
- end