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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +272 -7
  3. data/lib/decision_agent/agent.rb +72 -1
  4. data/lib/decision_agent/context.rb +1 -0
  5. data/lib/decision_agent/data_enrichment/cache/memory_adapter.rb +86 -0
  6. data/lib/decision_agent/data_enrichment/cache_adapter.rb +49 -0
  7. data/lib/decision_agent/data_enrichment/circuit_breaker.rb +135 -0
  8. data/lib/decision_agent/data_enrichment/client.rb +220 -0
  9. data/lib/decision_agent/data_enrichment/config.rb +78 -0
  10. data/lib/decision_agent/data_enrichment/errors.rb +36 -0
  11. data/lib/decision_agent/decision.rb +102 -2
  12. data/lib/decision_agent/dmn/feel/evaluator.rb +28 -6
  13. data/lib/decision_agent/dsl/condition_evaluator.rb +982 -839
  14. data/lib/decision_agent/dsl/schema_validator.rb +51 -13
  15. data/lib/decision_agent/evaluators/dmn_evaluator.rb +106 -19
  16. data/lib/decision_agent/evaluators/json_rule_evaluator.rb +69 -9
  17. data/lib/decision_agent/explainability/condition_trace.rb +83 -0
  18. data/lib/decision_agent/explainability/explainability_result.rb +52 -0
  19. data/lib/decision_agent/explainability/rule_trace.rb +39 -0
  20. data/lib/decision_agent/explainability/trace_collector.rb +24 -0
  21. data/lib/decision_agent/monitoring/alert_manager.rb +5 -1
  22. data/lib/decision_agent/simulation/errors.rb +18 -0
  23. data/lib/decision_agent/simulation/impact_analyzer.rb +498 -0
  24. data/lib/decision_agent/simulation/monte_carlo_simulator.rb +635 -0
  25. data/lib/decision_agent/simulation/replay_engine.rb +486 -0
  26. data/lib/decision_agent/simulation/scenario_engine.rb +318 -0
  27. data/lib/decision_agent/simulation/scenario_library.rb +163 -0
  28. data/lib/decision_agent/simulation/shadow_test_engine.rb +287 -0
  29. data/lib/decision_agent/simulation/what_if_analyzer.rb +1002 -0
  30. data/lib/decision_agent/simulation.rb +17 -0
  31. data/lib/decision_agent/version.rb +1 -1
  32. data/lib/decision_agent/versioning/activerecord_adapter.rb +23 -8
  33. data/lib/decision_agent/web/public/app.js +119 -0
  34. data/lib/decision_agent/web/public/index.html +49 -0
  35. data/lib/decision_agent/web/public/simulation.html +130 -0
  36. data/lib/decision_agent/web/public/simulation_impact.html +478 -0
  37. data/lib/decision_agent/web/public/simulation_replay.html +551 -0
  38. data/lib/decision_agent/web/public/simulation_shadow.html +546 -0
  39. data/lib/decision_agent/web/public/simulation_whatif.html +532 -0
  40. data/lib/decision_agent/web/public/styles.css +65 -0
  41. data/lib/decision_agent/web/server.rb +594 -23
  42. data/lib/decision_agent.rb +60 -2
  43. metadata +53 -73
  44. data/spec/ab_testing/ab_test_assignment_spec.rb +0 -253
  45. data/spec/ab_testing/ab_test_manager_spec.rb +0 -612
  46. data/spec/ab_testing/ab_test_spec.rb +0 -270
  47. data/spec/ab_testing/ab_testing_agent_spec.rb +0 -655
  48. data/spec/ab_testing/storage/adapter_spec.rb +0 -64
  49. data/spec/ab_testing/storage/memory_adapter_spec.rb +0 -485
  50. data/spec/activerecord_thread_safety_spec.rb +0 -553
  51. data/spec/advanced_operators_spec.rb +0 -3150
  52. data/spec/agent_spec.rb +0 -289
  53. data/spec/api_contract_spec.rb +0 -430
  54. data/spec/audit_adapters_spec.rb +0 -92
  55. data/spec/auth/access_audit_logger_spec.rb +0 -394
  56. data/spec/auth/authenticator_spec.rb +0 -112
  57. data/spec/auth/password_reset_spec.rb +0 -294
  58. data/spec/auth/permission_checker_spec.rb +0 -207
  59. data/spec/auth/permission_spec.rb +0 -73
  60. data/spec/auth/rbac_adapter_spec.rb +0 -778
  61. data/spec/auth/rbac_config_spec.rb +0 -82
  62. data/spec/auth/role_spec.rb +0 -51
  63. data/spec/auth/session_manager_spec.rb +0 -172
  64. data/spec/auth/session_spec.rb +0 -112
  65. data/spec/auth/user_spec.rb +0 -130
  66. data/spec/comprehensive_edge_cases_spec.rb +0 -1777
  67. data/spec/context_spec.rb +0 -127
  68. data/spec/decision_agent_spec.rb +0 -96
  69. data/spec/decision_spec.rb +0 -423
  70. data/spec/dmn/decision_graph_spec.rb +0 -282
  71. data/spec/dmn/decision_tree_spec.rb +0 -203
  72. data/spec/dmn/feel/errors_spec.rb +0 -18
  73. data/spec/dmn/feel/functions_spec.rb +0 -400
  74. data/spec/dmn/feel/simple_parser_spec.rb +0 -274
  75. data/spec/dmn/feel/types_spec.rb +0 -176
  76. data/spec/dmn/feel_parser_spec.rb +0 -489
  77. data/spec/dmn/hit_policy_spec.rb +0 -202
  78. data/spec/dmn/integration_spec.rb +0 -226
  79. data/spec/dsl/condition_evaluator_spec.rb +0 -774
  80. data/spec/dsl_validation_spec.rb +0 -648
  81. data/spec/edge_cases_spec.rb +0 -353
  82. data/spec/evaluation_spec.rb +0 -364
  83. data/spec/evaluation_validator_spec.rb +0 -165
  84. data/spec/examples/feedback_aware_evaluator_spec.rb +0 -460
  85. data/spec/examples.txt +0 -1909
  86. data/spec/fixtures/dmn/complex_decision.dmn +0 -81
  87. data/spec/fixtures/dmn/invalid_structure.dmn +0 -31
  88. data/spec/fixtures/dmn/simple_decision.dmn +0 -40
  89. data/spec/issue_verification_spec.rb +0 -759
  90. data/spec/json_rule_evaluator_spec.rb +0 -587
  91. data/spec/monitoring/alert_manager_spec.rb +0 -378
  92. data/spec/monitoring/metrics_collector_spec.rb +0 -501
  93. data/spec/monitoring/monitored_agent_spec.rb +0 -225
  94. data/spec/monitoring/prometheus_exporter_spec.rb +0 -242
  95. data/spec/monitoring/storage/activerecord_adapter_spec.rb +0 -498
  96. data/spec/monitoring/storage/base_adapter_spec.rb +0 -61
  97. data/spec/monitoring/storage/memory_adapter_spec.rb +0 -247
  98. data/spec/performance_optimizations_spec.rb +0 -493
  99. data/spec/replay_edge_cases_spec.rb +0 -699
  100. data/spec/replay_spec.rb +0 -210
  101. data/spec/rfc8785_canonicalization_spec.rb +0 -215
  102. data/spec/scoring_spec.rb +0 -225
  103. data/spec/spec_helper.rb +0 -60
  104. data/spec/testing/batch_test_importer_spec.rb +0 -693
  105. data/spec/testing/batch_test_runner_spec.rb +0 -307
  106. data/spec/testing/test_coverage_analyzer_spec.rb +0 -292
  107. data/spec/testing/test_result_comparator_spec.rb +0 -392
  108. data/spec/testing/test_scenario_spec.rb +0 -113
  109. data/spec/thread_safety_spec.rb +0 -490
  110. data/spec/thread_safety_spec.rb.broken +0 -878
  111. data/spec/versioning/adapter_spec.rb +0 -156
  112. data/spec/versioning_spec.rb +0 -1030
  113. data/spec/web/middleware/auth_middleware_spec.rb +0 -133
  114. data/spec/web/middleware/permission_middleware_spec.rb +0 -247
  115. data/spec/web_ui_rack_spec.rb +0 -2134
@@ -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