decision_agent 0.3.0 → 1.1.0

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 (220) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +234 -14
  3. data/lib/decision_agent/ab_testing/ab_test.rb +5 -1
  4. data/lib/decision_agent/ab_testing/ab_test_assignment.rb +2 -0
  5. data/lib/decision_agent/ab_testing/ab_test_manager.rb +2 -0
  6. data/lib/decision_agent/ab_testing/ab_testing_agent.rb +2 -0
  7. data/lib/decision_agent/ab_testing/storage/activerecord_adapter.rb +2 -13
  8. data/lib/decision_agent/ab_testing/storage/adapter.rb +2 -0
  9. data/lib/decision_agent/ab_testing/storage/memory_adapter.rb +2 -0
  10. data/lib/decision_agent/agent.rb +78 -9
  11. data/lib/decision_agent/audit/adapter.rb +2 -0
  12. data/lib/decision_agent/audit/logger_adapter.rb +2 -0
  13. data/lib/decision_agent/audit/null_adapter.rb +2 -0
  14. data/lib/decision_agent/auth/access_audit_logger.rb +2 -0
  15. data/lib/decision_agent/auth/authenticator.rb +2 -0
  16. data/lib/decision_agent/auth/password_reset_manager.rb +2 -0
  17. data/lib/decision_agent/auth/password_reset_token.rb +2 -0
  18. data/lib/decision_agent/auth/permission.rb +2 -0
  19. data/lib/decision_agent/auth/permission_checker.rb +2 -0
  20. data/lib/decision_agent/auth/rbac_adapter.rb +2 -0
  21. data/lib/decision_agent/auth/rbac_config.rb +2 -0
  22. data/lib/decision_agent/auth/role.rb +2 -0
  23. data/lib/decision_agent/auth/session.rb +2 -0
  24. data/lib/decision_agent/auth/session_manager.rb +2 -0
  25. data/lib/decision_agent/auth/user.rb +2 -0
  26. data/lib/decision_agent/context.rb +14 -0
  27. data/lib/decision_agent/decision.rb +113 -4
  28. data/lib/decision_agent/dmn/adapter.rb +2 -0
  29. data/lib/decision_agent/dmn/cache.rb +2 -2
  30. data/lib/decision_agent/dmn/decision_graph.rb +7 -7
  31. data/lib/decision_agent/dmn/decision_tree.rb +16 -8
  32. data/lib/decision_agent/dmn/errors.rb +2 -0
  33. data/lib/decision_agent/dmn/exporter.rb +2 -0
  34. data/lib/decision_agent/dmn/feel/evaluator.rb +130 -114
  35. data/lib/decision_agent/dmn/feel/functions.rb +2 -0
  36. data/lib/decision_agent/dmn/feel/parser.rb +2 -0
  37. data/lib/decision_agent/dmn/feel/simple_parser.rb +98 -77
  38. data/lib/decision_agent/dmn/feel/transformer.rb +56 -102
  39. data/lib/decision_agent/dmn/feel/types.rb +2 -0
  40. data/lib/decision_agent/dmn/importer.rb +2 -0
  41. data/lib/decision_agent/dmn/model.rb +2 -4
  42. data/lib/decision_agent/dmn/parser.rb +2 -0
  43. data/lib/decision_agent/dmn/testing.rb +3 -2
  44. data/lib/decision_agent/dmn/validator.rb +5 -3
  45. data/lib/decision_agent/dmn/visualizer.rb +7 -6
  46. data/lib/decision_agent/dsl/condition_evaluator.rb +242 -1375
  47. data/lib/decision_agent/dsl/helpers/cache_helpers.rb +82 -0
  48. data/lib/decision_agent/dsl/helpers/comparison_helpers.rb +98 -0
  49. data/lib/decision_agent/dsl/helpers/date_helpers.rb +91 -0
  50. data/lib/decision_agent/dsl/helpers/geospatial_helpers.rb +85 -0
  51. data/lib/decision_agent/dsl/helpers/operator_evaluation_helpers.rb +160 -0
  52. data/lib/decision_agent/dsl/helpers/parameter_parsing_helpers.rb +206 -0
  53. data/lib/decision_agent/dsl/helpers/template_helpers.rb +39 -0
  54. data/lib/decision_agent/dsl/helpers/utility_helpers.rb +45 -0
  55. data/lib/decision_agent/dsl/operators/base.rb +70 -0
  56. data/lib/decision_agent/dsl/operators/basic_comparison_operators.rb +80 -0
  57. data/lib/decision_agent/dsl/operators/collection_operators.rb +60 -0
  58. data/lib/decision_agent/dsl/operators/date_arithmetic_operators.rb +206 -0
  59. data/lib/decision_agent/dsl/operators/date_time_operators.rb +47 -0
  60. data/lib/decision_agent/dsl/operators/duration_operators.rb +149 -0
  61. data/lib/decision_agent/dsl/operators/financial_operators.rb +237 -0
  62. data/lib/decision_agent/dsl/operators/geospatial_operators.rb +106 -0
  63. data/lib/decision_agent/dsl/operators/mathematical_operators.rb +234 -0
  64. data/lib/decision_agent/dsl/operators/moving_window_operators.rb +135 -0
  65. data/lib/decision_agent/dsl/operators/numeric_operators.rb +120 -0
  66. data/lib/decision_agent/dsl/operators/rate_operators.rb +65 -0
  67. data/lib/decision_agent/dsl/operators/statistical_aggregations.rb +187 -0
  68. data/lib/decision_agent/dsl/operators/string_aggregations.rb +84 -0
  69. data/lib/decision_agent/dsl/operators/string_operators.rb +72 -0
  70. data/lib/decision_agent/dsl/operators/time_component_operators.rb +72 -0
  71. data/lib/decision_agent/dsl/rule_parser.rb +2 -0
  72. data/lib/decision_agent/dsl/schema_validator.rb +37 -14
  73. data/lib/decision_agent/errors.rb +2 -0
  74. data/lib/decision_agent/evaluation.rb +14 -2
  75. data/lib/decision_agent/evaluators/base.rb +2 -0
  76. data/lib/decision_agent/evaluators/dmn_evaluator.rb +108 -19
  77. data/lib/decision_agent/evaluators/json_rule_evaluator.rb +56 -11
  78. data/lib/decision_agent/evaluators/static_evaluator.rb +2 -0
  79. data/lib/decision_agent/explainability/condition_trace.rb +85 -0
  80. data/lib/decision_agent/explainability/explainability_result.rb +50 -0
  81. data/lib/decision_agent/explainability/rule_trace.rb +41 -0
  82. data/lib/decision_agent/explainability/trace_collector.rb +26 -0
  83. data/lib/decision_agent/monitoring/alert_manager.rb +7 -16
  84. data/lib/decision_agent/monitoring/dashboard_server.rb +383 -250
  85. data/lib/decision_agent/monitoring/metrics_collector.rb +2 -0
  86. data/lib/decision_agent/monitoring/monitored_agent.rb +2 -0
  87. data/lib/decision_agent/monitoring/prometheus_exporter.rb +3 -1
  88. data/lib/decision_agent/replay/replay.rb +4 -1
  89. data/lib/decision_agent/scoring/base.rb +2 -0
  90. data/lib/decision_agent/scoring/consensus.rb +2 -0
  91. data/lib/decision_agent/scoring/max_weight.rb +2 -0
  92. data/lib/decision_agent/scoring/threshold.rb +2 -0
  93. data/lib/decision_agent/scoring/weighted_average.rb +2 -0
  94. data/lib/decision_agent/simulation/errors.rb +20 -0
  95. data/lib/decision_agent/simulation/impact_analyzer.rb +500 -0
  96. data/lib/decision_agent/simulation/monte_carlo_simulator.rb +638 -0
  97. data/lib/decision_agent/simulation/replay_engine.rb +488 -0
  98. data/lib/decision_agent/simulation/scenario_engine.rb +320 -0
  99. data/lib/decision_agent/simulation/scenario_library.rb +165 -0
  100. data/lib/decision_agent/simulation/shadow_test_engine.rb +274 -0
  101. data/lib/decision_agent/simulation/what_if_analyzer.rb +1008 -0
  102. data/lib/decision_agent/simulation.rb +19 -0
  103. data/lib/decision_agent/testing/batch_test_importer.rb +6 -2
  104. data/lib/decision_agent/testing/batch_test_runner.rb +5 -2
  105. data/lib/decision_agent/testing/test_coverage_analyzer.rb +2 -0
  106. data/lib/decision_agent/testing/test_result_comparator.rb +2 -0
  107. data/lib/decision_agent/testing/test_scenario.rb +2 -0
  108. data/lib/decision_agent/version.rb +3 -1
  109. data/lib/decision_agent/versioning/activerecord_adapter.rb +108 -43
  110. data/lib/decision_agent/versioning/adapter.rb +9 -0
  111. data/lib/decision_agent/versioning/file_storage_adapter.rb +19 -6
  112. data/lib/decision_agent/versioning/version_manager.rb +9 -0
  113. data/lib/decision_agent/web/dmn_editor/serialization.rb +74 -0
  114. data/lib/decision_agent/web/dmn_editor/xml_builder.rb +107 -0
  115. data/lib/decision_agent/web/dmn_editor.rb +8 -67
  116. data/lib/decision_agent/web/middleware/auth_middleware.rb +2 -0
  117. data/lib/decision_agent/web/middleware/permission_middleware.rb +3 -1
  118. data/lib/decision_agent/web/public/app.js +186 -26
  119. data/lib/decision_agent/web/public/batch_testing.html +80 -6
  120. data/lib/decision_agent/web/public/dmn-editor.html +2 -2
  121. data/lib/decision_agent/web/public/dmn-editor.js +74 -8
  122. data/lib/decision_agent/web/public/index.html +69 -3
  123. data/lib/decision_agent/web/public/login.html +1 -1
  124. data/lib/decision_agent/web/public/sample_batch.csv +11 -0
  125. data/lib/decision_agent/web/public/sample_impact.csv +11 -0
  126. data/lib/decision_agent/web/public/sample_replay.csv +11 -0
  127. data/lib/decision_agent/web/public/sample_rules.json +118 -0
  128. data/lib/decision_agent/web/public/sample_shadow.csv +11 -0
  129. data/lib/decision_agent/web/public/sample_whatif.csv +11 -0
  130. data/lib/decision_agent/web/public/simulation.html +146 -0
  131. data/lib/decision_agent/web/public/simulation_impact.html +495 -0
  132. data/lib/decision_agent/web/public/simulation_replay.html +547 -0
  133. data/lib/decision_agent/web/public/simulation_shadow.html +561 -0
  134. data/lib/decision_agent/web/public/simulation_whatif.html +549 -0
  135. data/lib/decision_agent/web/public/styles.css +65 -0
  136. data/lib/decision_agent/web/public/users.html +1 -1
  137. data/lib/decision_agent/web/rack_helpers.rb +106 -0
  138. data/lib/decision_agent/web/rack_request_helpers.rb +196 -0
  139. data/lib/decision_agent/web/server.rb +2126 -1374
  140. data/lib/decision_agent.rb +19 -1
  141. data/lib/generators/decision_agent/install/install_generator.rb +2 -0
  142. data/lib/generators/decision_agent/install/templates/ab_test_assignment_model.rb +2 -0
  143. data/lib/generators/decision_agent/install/templates/ab_test_model.rb +2 -0
  144. data/lib/generators/decision_agent/install/templates/ab_testing_migration.rb +2 -0
  145. data/lib/generators/decision_agent/install/templates/migration.rb +2 -0
  146. data/lib/generators/decision_agent/install/templates/rule.rb +2 -0
  147. data/lib/generators/decision_agent/install/templates/rule_version.rb +2 -0
  148. metadata +103 -89
  149. data/spec/ab_testing/ab_test_assignment_spec.rb +0 -253
  150. data/spec/ab_testing/ab_test_manager_spec.rb +0 -612
  151. data/spec/ab_testing/ab_test_spec.rb +0 -270
  152. data/spec/ab_testing/ab_testing_agent_spec.rb +0 -655
  153. data/spec/ab_testing/storage/adapter_spec.rb +0 -64
  154. data/spec/ab_testing/storage/memory_adapter_spec.rb +0 -485
  155. data/spec/activerecord_thread_safety_spec.rb +0 -553
  156. data/spec/advanced_operators_spec.rb +0 -3150
  157. data/spec/agent_spec.rb +0 -289
  158. data/spec/api_contract_spec.rb +0 -430
  159. data/spec/audit_adapters_spec.rb +0 -92
  160. data/spec/auth/access_audit_logger_spec.rb +0 -394
  161. data/spec/auth/authenticator_spec.rb +0 -112
  162. data/spec/auth/password_reset_spec.rb +0 -294
  163. data/spec/auth/permission_checker_spec.rb +0 -207
  164. data/spec/auth/permission_spec.rb +0 -73
  165. data/spec/auth/rbac_adapter_spec.rb +0 -778
  166. data/spec/auth/rbac_config_spec.rb +0 -82
  167. data/spec/auth/role_spec.rb +0 -51
  168. data/spec/auth/session_manager_spec.rb +0 -172
  169. data/spec/auth/session_spec.rb +0 -112
  170. data/spec/auth/user_spec.rb +0 -130
  171. data/spec/comprehensive_edge_cases_spec.rb +0 -1777
  172. data/spec/context_spec.rb +0 -127
  173. data/spec/decision_agent_spec.rb +0 -96
  174. data/spec/decision_spec.rb +0 -423
  175. data/spec/dmn/decision_graph_spec.rb +0 -282
  176. data/spec/dmn/decision_tree_spec.rb +0 -203
  177. data/spec/dmn/feel/errors_spec.rb +0 -18
  178. data/spec/dmn/feel/functions_spec.rb +0 -400
  179. data/spec/dmn/feel/simple_parser_spec.rb +0 -274
  180. data/spec/dmn/feel/types_spec.rb +0 -176
  181. data/spec/dmn/feel_parser_spec.rb +0 -489
  182. data/spec/dmn/hit_policy_spec.rb +0 -202
  183. data/spec/dmn/integration_spec.rb +0 -226
  184. data/spec/dsl/condition_evaluator_spec.rb +0 -774
  185. data/spec/dsl_validation_spec.rb +0 -648
  186. data/spec/edge_cases_spec.rb +0 -353
  187. data/spec/evaluation_spec.rb +0 -364
  188. data/spec/evaluation_validator_spec.rb +0 -165
  189. data/spec/examples/feedback_aware_evaluator_spec.rb +0 -460
  190. data/spec/examples.txt +0 -1909
  191. data/spec/fixtures/dmn/complex_decision.dmn +0 -81
  192. data/spec/fixtures/dmn/invalid_structure.dmn +0 -31
  193. data/spec/fixtures/dmn/simple_decision.dmn +0 -40
  194. data/spec/issue_verification_spec.rb +0 -759
  195. data/spec/json_rule_evaluator_spec.rb +0 -587
  196. data/spec/monitoring/alert_manager_spec.rb +0 -378
  197. data/spec/monitoring/metrics_collector_spec.rb +0 -501
  198. data/spec/monitoring/monitored_agent_spec.rb +0 -225
  199. data/spec/monitoring/prometheus_exporter_spec.rb +0 -242
  200. data/spec/monitoring/storage/activerecord_adapter_spec.rb +0 -498
  201. data/spec/monitoring/storage/base_adapter_spec.rb +0 -61
  202. data/spec/monitoring/storage/memory_adapter_spec.rb +0 -247
  203. data/spec/performance_optimizations_spec.rb +0 -493
  204. data/spec/replay_edge_cases_spec.rb +0 -699
  205. data/spec/replay_spec.rb +0 -210
  206. data/spec/rfc8785_canonicalization_spec.rb +0 -215
  207. data/spec/scoring_spec.rb +0 -225
  208. data/spec/spec_helper.rb +0 -60
  209. data/spec/testing/batch_test_importer_spec.rb +0 -693
  210. data/spec/testing/batch_test_runner_spec.rb +0 -307
  211. data/spec/testing/test_coverage_analyzer_spec.rb +0 -292
  212. data/spec/testing/test_result_comparator_spec.rb +0 -392
  213. data/spec/testing/test_scenario_spec.rb +0 -113
  214. data/spec/thread_safety_spec.rb +0 -490
  215. data/spec/thread_safety_spec.rb.broken +0 -878
  216. data/spec/versioning/adapter_spec.rb +0 -156
  217. data/spec/versioning_spec.rb +0 -1030
  218. data/spec/web/middleware/auth_middleware_spec.rb +0 -133
  219. data/spec/web/middleware/permission_middleware_spec.rb +0 -247
  220. 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