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,82 +0,0 @@
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
@@ -1,51 +0,0 @@
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
@@ -1,172 +0,0 @@
1
- require "spec_helper"
2
-
3
- RSpec.describe DecisionAgent::Auth::SessionManager do
4
- let(:manager) { DecisionAgent::Auth::SessionManager.new }
5
-
6
- describe "#initialize" do
7
- it "initializes with empty sessions" do
8
- expect(manager.count).to eq(0)
9
- end
10
- end
11
-
12
- describe "#create_session" do
13
- it "creates a new session" do
14
- session = manager.create_session("user123")
15
- expect(session).to be_a(DecisionAgent::Auth::Session)
16
- expect(session.user_id).to eq("user123")
17
- end
18
-
19
- it "stores the session" do
20
- session = manager.create_session("user123")
21
- retrieved = manager.get_session(session.token)
22
- expect(retrieved).to eq(session)
23
- end
24
-
25
- it "accepts custom expiration time" do
26
- session = manager.create_session("user123", expires_in: 7200)
27
- expect(session.expires_at).to be > Time.now.utc + 7100
28
- end
29
- end
30
-
31
- describe "#get_session" do
32
- it "returns session for valid token" do
33
- session = manager.create_session("user123")
34
- retrieved = manager.get_session(session.token)
35
- expect(retrieved).to eq(session)
36
- end
37
-
38
- it "returns nil for invalid token" do
39
- expect(manager.get_session("invalid_token")).to be_nil
40
- end
41
-
42
- it "returns nil for expired session" do
43
- session = manager.create_session("user123", expires_in: -1)
44
- expect(manager.get_session(session.token)).to be_nil
45
- end
46
- end
47
-
48
- describe "#delete_session" do
49
- it "deletes a session" do
50
- session = manager.create_session("user123")
51
- manager.delete_session(session.token)
52
- expect(manager.get_session(session.token)).to be_nil
53
- end
54
-
55
- it "does not raise error for non-existent session" do
56
- expect { manager.delete_session("nonexistent") }.not_to raise_error
57
- end
58
- end
59
-
60
- describe "#delete_user_sessions" do
61
- it "deletes all sessions for a user" do
62
- session1 = manager.create_session("user123")
63
- session2 = manager.create_session("user123")
64
- session3 = manager.create_session("user456")
65
-
66
- manager.delete_user_sessions("user123")
67
-
68
- expect(manager.get_session(session1.token)).to be_nil
69
- expect(manager.get_session(session2.token)).to be_nil
70
- expect(manager.get_session(session3.token)).to eq(session3) # Other user's session still exists
71
- end
72
-
73
- it "does not raise error for user with no sessions" do
74
- expect { manager.delete_user_sessions("nonexistent") }.not_to raise_error
75
- end
76
- end
77
-
78
- describe "#cleanup_expired_sessions" do
79
- it "removes expired sessions" do
80
- # Create expired session
81
- expired_session = manager.create_session("user123", expires_in: -1)
82
- # Create valid session
83
- valid_session = manager.create_session("user456", expires_in: 3600)
84
-
85
- expect(manager.count).to eq(2)
86
-
87
- # Force cleanup by setting last_cleanup far in the past and creating another session
88
- manager.instance_variable_set(:@last_cleanup, Time.now - 400) # More than 300 seconds
89
- manager.create_session("user789", expires_in: 3600)
90
-
91
- # Expired session should be cleaned up
92
- expect(manager.get_session(expired_session.token)).to be_nil
93
- expect(manager.get_session(valid_session.token)).to eq(valid_session)
94
- end
95
-
96
- it "only runs cleanup after cleanup_interval" do
97
- manager = DecisionAgent::Auth::SessionManager.new
98
-
99
- # Create an expired session
100
- expired_session = manager.create_session("user123", expires_in: -1)
101
- expect(manager.count).to eq(1)
102
-
103
- # The cleanup_expired_sessions is called during create_session, but it checks the interval
104
- # Let's test by manually setting last_cleanup to far in the past
105
- manager.instance_variable_set(:@last_cleanup, Time.now - 400) # More than 300 seconds
106
- manager.create_session("user456", expires_in: 3600)
107
-
108
- # The expired session should be cleaned up now
109
- expect(manager.get_session(expired_session.token)).to be_nil
110
- end
111
- end
112
-
113
- describe "#count" do
114
- it "returns zero initially" do
115
- expect(manager.count).to eq(0)
116
- end
117
-
118
- it "returns correct count after creating sessions" do
119
- manager.create_session("user123")
120
- expect(manager.count).to eq(1)
121
-
122
- manager.create_session("user123")
123
- expect(manager.count).to eq(2)
124
-
125
- manager.create_session("user456")
126
- expect(manager.count).to eq(3)
127
- end
128
-
129
- it "reflects deletions" do
130
- session1 = manager.create_session("user123")
131
- manager.create_session("user123")
132
- expect(manager.count).to eq(2)
133
-
134
- manager.delete_session(session1.token)
135
- expect(manager.count).to eq(1)
136
-
137
- manager.delete_user_sessions("user123")
138
- expect(manager.count).to eq(0)
139
- end
140
-
141
- it "does not count expired sessions" do
142
- manager.create_session("user123", expires_in: -1)
143
- # Expired sessions are not counted when retrieved, but are still in storage until cleanup
144
- # So count will include them until cleanup runs
145
- expect(manager.count).to eq(1)
146
-
147
- # After cleanup
148
- manager.instance_variable_set(:@last_cleanup, Time.now - 400)
149
- manager.create_session("user456", expires_in: 3600)
150
- # Now expired session should be cleaned up
151
- expect(manager.count).to eq(1)
152
- end
153
- end
154
-
155
- describe "thread safety" do
156
- it "handles concurrent access safely" do
157
- threads = []
158
- 10.times do
159
- threads << Thread.new do
160
- 10.times do
161
- session = manager.create_session("user#{rand(100)}")
162
- manager.get_session(session.token)
163
- manager.count
164
- end
165
- end
166
- end
167
-
168
- threads.each(&:join)
169
- expect(manager.count).to eq(100)
170
- end
171
- end
172
- end
@@ -1,112 +0,0 @@
1
- require "spec_helper"
2
-
3
- RSpec.describe DecisionAgent::Auth::Session do
4
- describe "#initialize" do
5
- it "creates a session with user_id" do
6
- session = DecisionAgent::Auth::Session.new(user_id: "user123")
7
- expect(session.user_id).to eq("user123")
8
- expect(session.token).to be_a(String)
9
- expect(session.token.length).to eq(64) # 32 bytes hex = 64 chars
10
- end
11
-
12
- it "sets expiration time" do
13
- session = DecisionAgent::Auth::Session.new(user_id: "user123", expires_in: 1800)
14
- expect(session.expires_at).to be > Time.now.utc
15
- expect(session.expires_at).to be <= Time.now.utc + 1801
16
- end
17
-
18
- it "uses default expiration of 3600 seconds" do
19
- session = DecisionAgent::Auth::Session.new(user_id: "user123")
20
- expected_expiry = session.created_at + 3600
21
- expect(session.expires_at).to be_within(1).of(expected_expiry)
22
- end
23
- end
24
-
25
- describe "#expired?" do
26
- it "returns false for valid session" do
27
- session = DecisionAgent::Auth::Session.new(user_id: "user123", expires_in: 3600)
28
- expect(session.expired?).to be false
29
- end
30
-
31
- it "returns true for expired session" do
32
- session = DecisionAgent::Auth::Session.new(user_id: "user123", expires_in: -1)
33
- expect(session.expired?).to be true
34
- end
35
- end
36
-
37
- describe "#valid?" do
38
- it "returns true for non-expired session" do
39
- session = DecisionAgent::Auth::Session.new(user_id: "user123", expires_in: 3600)
40
- expect(session.valid?).to be true
41
- end
42
-
43
- it "returns false for expired session" do
44
- session = DecisionAgent::Auth::Session.new(user_id: "user123", expires_in: -1)
45
- expect(session.valid?).to be false
46
- end
47
-
48
- it "returns false when expired? returns true" do
49
- session = DecisionAgent::Auth::Session.new(user_id: "user123", expires_in: -1)
50
- expect(session.expired?).to be true
51
- expect(session.valid?).to be false
52
- end
53
-
54
- it "returns true when expired? returns false" do
55
- session = DecisionAgent::Auth::Session.new(user_id: "user123", expires_in: 3600)
56
- expect(session.expired?).to be false
57
- expect(session.valid?).to be true
58
- end
59
- end
60
-
61
- describe "#to_h" do
62
- it "returns hash with all session attributes" do
63
- session = DecisionAgent::Auth::Session.new(user_id: "user123", expires_in: 3600)
64
- hash = session.to_h
65
-
66
- expect(hash).to be_a(Hash)
67
- expect(hash[:token]).to eq(session.token)
68
- expect(hash[:user_id]).to eq("user123")
69
- expect(hash[:created_at]).to be_a(String)
70
- expect(hash[:expires_at]).to be_a(String)
71
- end
72
-
73
- it "serializes timestamps as ISO8601 strings" do
74
- session = DecisionAgent::Auth::Session.new(user_id: "user123")
75
- hash = session.to_h
76
-
77
- expect { Time.iso8601(hash[:created_at]) }.not_to raise_error
78
- expect { Time.iso8601(hash[:expires_at]) }.not_to raise_error
79
- end
80
-
81
- it "includes correct user_id" do
82
- session = DecisionAgent::Auth::Session.new(user_id: "user456")
83
- hash = session.to_h
84
- expect(hash[:user_id]).to eq("user456")
85
- end
86
-
87
- it "includes correct token" do
88
- session = DecisionAgent::Auth::Session.new(user_id: "user123")
89
- hash = session.to_h
90
- expect(hash[:token]).to eq(session.token)
91
- end
92
- end
93
-
94
- describe "#created_at" do
95
- it "is set to current UTC time" do
96
- before = Time.now.utc
97
- session = DecisionAgent::Auth::Session.new(user_id: "user123")
98
- after = Time.now.utc
99
-
100
- expect(session.created_at).to be >= before
101
- expect(session.created_at).to be <= after
102
- end
103
- end
104
-
105
- describe "#expires_at" do
106
- it "is set based on expires_in parameter" do
107
- session = DecisionAgent::Auth::Session.new(user_id: "user123", expires_in: 7200)
108
- expected = session.created_at + 7200
109
- expect(session.expires_at).to be_within(1).of(expected)
110
- end
111
- end
112
- end
@@ -1,130 +0,0 @@
1
- require "spec_helper"
2
-
3
- RSpec.describe DecisionAgent::Auth::User do
4
- describe "#initialize" do
5
- it "creates a user with email and password" do
6
- user = DecisionAgent::Auth::User.new(
7
- email: "test@example.com",
8
- password: "password123"
9
- )
10
-
11
- expect(user.email).to eq("test@example.com")
12
- expect(user.id).to be_a(String)
13
- expect(user.active).to be true
14
- expect(user.roles).to eq([])
15
- end
16
-
17
- it "creates a user with roles" do
18
- user = DecisionAgent::Auth::User.new(
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
-
27
- it "raises error if neither password nor password_hash provided" do
28
- expect do
29
- DecisionAgent::Auth::User.new(email: "test@example.com")
30
- end.to raise_error(ArgumentError, /password/)
31
- end
32
- end
33
-
34
- describe "#authenticate" do
35
- it "returns true for correct password" do
36
- user = DecisionAgent::Auth::User.new(
37
- email: "test@example.com",
38
- password: "password123"
39
- )
40
-
41
- expect(user.authenticate("password123")).to be true
42
- end
43
-
44
- it "returns false for incorrect password" do
45
- user = DecisionAgent::Auth::User.new(
46
- email: "test@example.com",
47
- password: "password123"
48
- )
49
-
50
- expect(user.authenticate("wrongpassword")).to be false
51
- end
52
-
53
- it "returns false for inactive user" do
54
- user = DecisionAgent::Auth::User.new(
55
- email: "test@example.com",
56
- password: "password123",
57
- active: false
58
- )
59
-
60
- expect(user.authenticate("password123")).to be false
61
- end
62
- end
63
-
64
- describe "#assign_role" do
65
- it "adds a role to the user" do
66
- user = DecisionAgent::Auth::User.new(
67
- email: "test@example.com",
68
- password: "password123"
69
- )
70
-
71
- user.assign_role(:editor)
72
- expect(user.roles).to include(:editor)
73
- end
74
-
75
- it "does not add duplicate roles" do
76
- user = DecisionAgent::Auth::User.new(
77
- email: "test@example.com",
78
- password: "password123"
79
- )
80
-
81
- user.assign_role(:editor)
82
- user.assign_role(:editor)
83
-
84
- expect(user.roles.count(:editor)).to eq(1)
85
- end
86
- end
87
-
88
- describe "#remove_role" do
89
- it "removes a role from the user" do
90
- user = DecisionAgent::Auth::User.new(
91
- email: "test@example.com",
92
- password: "password123",
93
- roles: %i[editor viewer]
94
- )
95
-
96
- user.remove_role(:editor)
97
- expect(user.roles).not_to include(:editor)
98
- expect(user.roles).to include(:viewer)
99
- end
100
- end
101
-
102
- describe "#has_role?" do
103
- it "returns true if user has the role" do
104
- user = DecisionAgent::Auth::User.new(
105
- email: "test@example.com",
106
- password: "password123",
107
- roles: [:editor]
108
- )
109
-
110
- expect(user.has_role?(:editor)).to be true
111
- expect(user.has_role?(:admin)).to be false
112
- end
113
- end
114
-
115
- describe "#to_h" do
116
- it "returns a hash representation of the user" do
117
- user = DecisionAgent::Auth::User.new(
118
- email: "test@example.com",
119
- password: "password123",
120
- roles: [:editor]
121
- )
122
-
123
- hash = user.to_h
124
- expect(hash[:email]).to eq("test@example.com")
125
- expect(hash[:roles]).to eq(["editor"])
126
- expect(hash[:active]).to be true
127
- expect(hash[:id]).to be_a(String)
128
- end
129
- end
130
- end