decision_agent 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +84 -233
- data/lib/decision_agent/ab_testing/ab_testing_agent.rb +46 -10
- data/lib/decision_agent/agent.rb +5 -3
- data/lib/decision_agent/auth/access_audit_logger.rb +122 -0
- data/lib/decision_agent/auth/authenticator.rb +127 -0
- data/lib/decision_agent/auth/password_reset_manager.rb +57 -0
- data/lib/decision_agent/auth/password_reset_token.rb +33 -0
- data/lib/decision_agent/auth/permission.rb +29 -0
- data/lib/decision_agent/auth/permission_checker.rb +43 -0
- data/lib/decision_agent/auth/rbac_adapter.rb +278 -0
- data/lib/decision_agent/auth/rbac_config.rb +51 -0
- data/lib/decision_agent/auth/role.rb +56 -0
- data/lib/decision_agent/auth/session.rb +33 -0
- data/lib/decision_agent/auth/session_manager.rb +57 -0
- data/lib/decision_agent/auth/user.rb +70 -0
- data/lib/decision_agent/context.rb +24 -4
- data/lib/decision_agent/decision.rb +10 -3
- data/lib/decision_agent/dsl/condition_evaluator.rb +378 -1
- data/lib/decision_agent/dsl/schema_validator.rb +8 -1
- data/lib/decision_agent/errors.rb +38 -0
- data/lib/decision_agent/evaluation.rb +10 -3
- data/lib/decision_agent/evaluation_validator.rb +8 -13
- data/lib/decision_agent/monitoring/dashboard_server.rb +1 -0
- data/lib/decision_agent/monitoring/metrics_collector.rb +17 -5
- data/lib/decision_agent/testing/batch_test_importer.rb +373 -0
- data/lib/decision_agent/testing/batch_test_runner.rb +244 -0
- data/lib/decision_agent/testing/test_coverage_analyzer.rb +191 -0
- data/lib/decision_agent/testing/test_result_comparator.rb +235 -0
- data/lib/decision_agent/testing/test_scenario.rb +42 -0
- data/lib/decision_agent/version.rb +10 -1
- data/lib/decision_agent/versioning/activerecord_adapter.rb +1 -1
- data/lib/decision_agent/versioning/file_storage_adapter.rb +96 -28
- data/lib/decision_agent/web/middleware/auth_middleware.rb +45 -0
- data/lib/decision_agent/web/middleware/permission_middleware.rb +94 -0
- data/lib/decision_agent/web/public/app.js +184 -29
- data/lib/decision_agent/web/public/batch_testing.html +640 -0
- data/lib/decision_agent/web/public/index.html +37 -9
- data/lib/decision_agent/web/public/login.html +298 -0
- data/lib/decision_agent/web/public/users.html +679 -0
- data/lib/decision_agent/web/server.rb +873 -7
- data/lib/decision_agent.rb +52 -0
- data/lib/generators/decision_agent/install/templates/rule_version.rb +1 -1
- data/spec/ab_testing/ab_test_assignment_spec.rb +253 -0
- data/spec/ab_testing/ab_test_manager_spec.rb +282 -0
- data/spec/ab_testing/ab_testing_agent_spec.rb +481 -0
- data/spec/ab_testing/storage/adapter_spec.rb +64 -0
- data/spec/ab_testing/storage/memory_adapter_spec.rb +485 -0
- data/spec/advanced_operators_spec.rb +1003 -0
- data/spec/agent_spec.rb +40 -0
- data/spec/audit_adapters_spec.rb +18 -0
- data/spec/auth/access_audit_logger_spec.rb +394 -0
- data/spec/auth/authenticator_spec.rb +112 -0
- data/spec/auth/password_reset_spec.rb +294 -0
- data/spec/auth/permission_checker_spec.rb +207 -0
- data/spec/auth/permission_spec.rb +73 -0
- data/spec/auth/rbac_adapter_spec.rb +550 -0
- data/spec/auth/rbac_config_spec.rb +82 -0
- data/spec/auth/role_spec.rb +51 -0
- data/spec/auth/session_manager_spec.rb +172 -0
- data/spec/auth/session_spec.rb +112 -0
- data/spec/auth/user_spec.rb +130 -0
- data/spec/context_spec.rb +43 -0
- data/spec/decision_agent_spec.rb +96 -0
- data/spec/decision_spec.rb +423 -0
- data/spec/dsl/condition_evaluator_spec.rb +774 -0
- data/spec/evaluation_spec.rb +364 -0
- data/spec/evaluation_validator_spec.rb +165 -0
- data/spec/examples.txt +1542 -612
- data/spec/monitoring/metrics_collector_spec.rb +220 -2
- data/spec/monitoring/storage/activerecord_adapter_spec.rb +153 -1
- data/spec/monitoring/storage/base_adapter_spec.rb +61 -0
- data/spec/performance_optimizations_spec.rb +486 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/testing/batch_test_importer_spec.rb +693 -0
- data/spec/testing/batch_test_runner_spec.rb +307 -0
- data/spec/testing/test_coverage_analyzer_spec.rb +292 -0
- data/spec/testing/test_result_comparator_spec.rb +392 -0
- data/spec/testing/test_scenario_spec.rb +113 -0
- data/spec/versioning/adapter_spec.rb +156 -0
- data/spec/versioning_spec.rb +253 -0
- data/spec/web/middleware/auth_middleware_spec.rb +133 -0
- data/spec/web/middleware/permission_middleware_spec.rb +247 -0
- data/spec/web_ui_rack_spec.rb +1705 -0
- metadata +99 -6
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
RSpec.describe DecisionAgent::Auth::PasswordResetToken do
|
|
4
|
+
describe "#initialize" do
|
|
5
|
+
it "creates a token with user_id" do
|
|
6
|
+
token = DecisionAgent::Auth::PasswordResetToken.new(user_id: "user123")
|
|
7
|
+
expect(token.user_id).to eq("user123")
|
|
8
|
+
expect(token.token).to be_a(String)
|
|
9
|
+
expect(token.token.length).to eq(64) # 32 bytes hex = 64 chars
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "sets expiration time" do
|
|
13
|
+
token = DecisionAgent::Auth::PasswordResetToken.new(user_id: "user123", expires_in: 1800)
|
|
14
|
+
expect(token.expires_at).to be > Time.now.utc
|
|
15
|
+
expect(token.expires_at).to be <= Time.now.utc + 1801
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe "#expired?" do
|
|
20
|
+
it "returns false for valid token" do
|
|
21
|
+
token = DecisionAgent::Auth::PasswordResetToken.new(user_id: "user123", expires_in: 3600)
|
|
22
|
+
expect(token.expired?).to be false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "returns true for expired token" do
|
|
26
|
+
token = DecisionAgent::Auth::PasswordResetToken.new(user_id: "user123", expires_in: -1)
|
|
27
|
+
expect(token.expired?).to be true
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe "#valid?" do
|
|
32
|
+
it "returns true for non-expired token" do
|
|
33
|
+
token = DecisionAgent::Auth::PasswordResetToken.new(user_id: "user123", expires_in: 3600)
|
|
34
|
+
expect(token.valid?).to be true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "returns false for expired token" do
|
|
38
|
+
token = DecisionAgent::Auth::PasswordResetToken.new(user_id: "user123", expires_in: -1)
|
|
39
|
+
expect(token.valid?).to be false
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
RSpec.describe DecisionAgent::Auth::PasswordResetManager do
|
|
45
|
+
let(:manager) { DecisionAgent::Auth::PasswordResetManager.new }
|
|
46
|
+
|
|
47
|
+
describe "#create_token" do
|
|
48
|
+
it "creates a new reset token" do
|
|
49
|
+
token = manager.create_token("user123")
|
|
50
|
+
expect(token).to be_a(DecisionAgent::Auth::PasswordResetToken)
|
|
51
|
+
expect(token.user_id).to eq("user123")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "stores the token" do
|
|
55
|
+
token = manager.create_token("user123")
|
|
56
|
+
retrieved = manager.get_token(token.token)
|
|
57
|
+
expect(retrieved).to eq(token)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe "#get_token" do
|
|
62
|
+
it "returns token for valid token string" do
|
|
63
|
+
token = manager.create_token("user123")
|
|
64
|
+
retrieved = manager.get_token(token.token)
|
|
65
|
+
expect(retrieved).to eq(token)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "returns nil for invalid token" do
|
|
69
|
+
expect(manager.get_token("invalid_token")).to be_nil
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "returns nil for expired token" do
|
|
73
|
+
token = manager.create_token("user123", expires_in: -1)
|
|
74
|
+
expect(manager.get_token(token.token)).to be_nil
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
describe "#delete_token" do
|
|
79
|
+
it "deletes a token" do
|
|
80
|
+
token = manager.create_token("user123")
|
|
81
|
+
manager.delete_token(token.token)
|
|
82
|
+
expect(manager.get_token(token.token)).to be_nil
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
describe "#delete_user_tokens" do
|
|
87
|
+
it "deletes all tokens for a user" do
|
|
88
|
+
token1 = manager.create_token("user123")
|
|
89
|
+
token2 = manager.create_token("user123")
|
|
90
|
+
token3 = manager.create_token("user456")
|
|
91
|
+
|
|
92
|
+
manager.delete_user_tokens("user123")
|
|
93
|
+
|
|
94
|
+
expect(manager.get_token(token1.token)).to be_nil
|
|
95
|
+
expect(manager.get_token(token2.token)).to be_nil
|
|
96
|
+
expect(manager.get_token(token3.token)).to eq(token3) # Other user's token still exists
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
describe "#count" do
|
|
101
|
+
it "returns zero initially" do
|
|
102
|
+
expect(manager.count).to eq(0)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "returns correct count after creating tokens" do
|
|
106
|
+
manager.create_token("user123")
|
|
107
|
+
expect(manager.count).to eq(1)
|
|
108
|
+
|
|
109
|
+
manager.create_token("user123")
|
|
110
|
+
expect(manager.count).to eq(2)
|
|
111
|
+
|
|
112
|
+
manager.create_token("user456")
|
|
113
|
+
expect(manager.count).to eq(3)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "reflects deletions" do
|
|
117
|
+
token1 = manager.create_token("user123")
|
|
118
|
+
manager.create_token("user123")
|
|
119
|
+
expect(manager.count).to eq(2)
|
|
120
|
+
|
|
121
|
+
manager.delete_token(token1.token)
|
|
122
|
+
expect(manager.count).to eq(1)
|
|
123
|
+
|
|
124
|
+
manager.delete_user_tokens("user123")
|
|
125
|
+
expect(manager.count).to eq(0)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
describe "#cleanup_expired_tokens" do
|
|
130
|
+
it "removes expired tokens" do
|
|
131
|
+
# Create expired token
|
|
132
|
+
expired_token = manager.create_token("user123", expires_in: -1)
|
|
133
|
+
# Create valid token
|
|
134
|
+
valid_token = manager.create_token("user456", expires_in: 3600)
|
|
135
|
+
|
|
136
|
+
expect(manager.count).to eq(2)
|
|
137
|
+
|
|
138
|
+
# Force cleanup by calling it directly (it's called during create_token, but we can test it)
|
|
139
|
+
# We'll create another token to trigger cleanup
|
|
140
|
+
manager.create_token("user789", expires_in: 3600)
|
|
141
|
+
|
|
142
|
+
# Expired token should be cleaned up
|
|
143
|
+
expect(manager.get_token(expired_token.token)).to be_nil
|
|
144
|
+
expect(manager.get_token(valid_token.token)).to eq(valid_token)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it "only runs cleanup after cleanup_interval" do
|
|
148
|
+
manager = DecisionAgent::Auth::PasswordResetManager.new
|
|
149
|
+
|
|
150
|
+
# Create an expired token
|
|
151
|
+
expired_token = manager.create_token("user123", expires_in: -1)
|
|
152
|
+
expect(manager.count).to eq(1)
|
|
153
|
+
|
|
154
|
+
# Create another token immediately - cleanup should not run yet if interval not passed
|
|
155
|
+
# But since we use -1 expires_in, the token is already expired, so get_token will return nil
|
|
156
|
+
# The cleanup_expired_tokens is called during create_token, but it checks the interval
|
|
157
|
+
# Let's test by manually setting last_cleanup to far in the past
|
|
158
|
+
manager.instance_variable_set(:@last_cleanup, Time.now - 400) # More than 300 seconds
|
|
159
|
+
manager.create_token("user456", expires_in: 3600)
|
|
160
|
+
|
|
161
|
+
# The expired token should be cleaned up now
|
|
162
|
+
expect(manager.get_token(expired_token.token)).to be_nil
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
RSpec.describe DecisionAgent::Auth::Authenticator do
|
|
168
|
+
let(:authenticator) { DecisionAgent::Auth::Authenticator.new }
|
|
169
|
+
|
|
170
|
+
describe "#request_password_reset" do
|
|
171
|
+
before do
|
|
172
|
+
authenticator.create_user(
|
|
173
|
+
email: "test@example.com",
|
|
174
|
+
password: "password123"
|
|
175
|
+
)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it "returns a token for valid user" do
|
|
179
|
+
token = authenticator.request_password_reset("test@example.com")
|
|
180
|
+
expect(token).to be_a(DecisionAgent::Auth::PasswordResetToken)
|
|
181
|
+
expect(token.user_id).to be_a(String)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it "returns nil for non-existent user" do
|
|
185
|
+
token = authenticator.request_password_reset("nonexistent@example.com")
|
|
186
|
+
expect(token).to be_nil
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it "returns nil for inactive user" do
|
|
190
|
+
user = authenticator.find_user_by_email("test@example.com")
|
|
191
|
+
user.active = false
|
|
192
|
+
|
|
193
|
+
token = authenticator.request_password_reset("test@example.com")
|
|
194
|
+
expect(token).to be_nil
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
it "deletes existing tokens when creating new one" do
|
|
198
|
+
token1 = authenticator.request_password_reset("test@example.com")
|
|
199
|
+
token2 = authenticator.request_password_reset("test@example.com")
|
|
200
|
+
|
|
201
|
+
expect(authenticator.password_reset_manager.get_token(token1.token)).to be_nil
|
|
202
|
+
expect(authenticator.password_reset_manager.get_token(token2.token)).to eq(token2)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
describe "#reset_password" do
|
|
207
|
+
before do
|
|
208
|
+
authenticator.create_user(
|
|
209
|
+
email: "test@example.com",
|
|
210
|
+
password: "oldpassword"
|
|
211
|
+
)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
it "resets password with valid token" do
|
|
215
|
+
token = authenticator.request_password_reset("test@example.com")
|
|
216
|
+
user = authenticator.reset_password(token.token, "newpassword123")
|
|
217
|
+
|
|
218
|
+
expect(user).to be_a(DecisionAgent::Auth::User)
|
|
219
|
+
expect(user.authenticate("newpassword123")).to be true
|
|
220
|
+
expect(user.authenticate("oldpassword")).to be false
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
it "returns nil for invalid token" do
|
|
224
|
+
user = authenticator.reset_password("invalid_token", "newpassword123")
|
|
225
|
+
expect(user).to be_nil
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
it "returns nil for expired token" do
|
|
229
|
+
token = authenticator.request_password_reset("test@example.com")
|
|
230
|
+
# Manually expire the token
|
|
231
|
+
token.instance_variable_set(:@expires_at, Time.now.utc - 1)
|
|
232
|
+
|
|
233
|
+
user = authenticator.reset_password(token.token, "newpassword123")
|
|
234
|
+
expect(user).to be_nil
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
it "invalidates all user sessions after password reset" do
|
|
238
|
+
session1 = authenticator.login("test@example.com", "oldpassword")
|
|
239
|
+
session2 = authenticator.login("test@example.com", "oldpassword")
|
|
240
|
+
|
|
241
|
+
token = authenticator.request_password_reset("test@example.com")
|
|
242
|
+
authenticator.reset_password(token.token, "newpassword123")
|
|
243
|
+
|
|
244
|
+
expect(authenticator.authenticate(session1.token)).to be_nil
|
|
245
|
+
expect(authenticator.authenticate(session2.token)).to be_nil
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
it "deletes the token after use" do
|
|
249
|
+
token = authenticator.request_password_reset("test@example.com")
|
|
250
|
+
authenticator.reset_password(token.token, "newpassword123")
|
|
251
|
+
|
|
252
|
+
expect(authenticator.password_reset_manager.get_token(token.token)).to be_nil
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
it "deletes all tokens for user after reset" do
|
|
256
|
+
token1 = authenticator.request_password_reset("test@example.com")
|
|
257
|
+
token2 = authenticator.request_password_reset("test@example.com")
|
|
258
|
+
|
|
259
|
+
authenticator.reset_password(token2.token, "newpassword123")
|
|
260
|
+
|
|
261
|
+
expect(authenticator.password_reset_manager.get_token(token1.token)).to be_nil
|
|
262
|
+
expect(authenticator.password_reset_manager.get_token(token2.token)).to be_nil
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
RSpec.describe DecisionAgent::Auth::User do
|
|
268
|
+
describe "#update_password" do
|
|
269
|
+
it "updates the password" do
|
|
270
|
+
user = DecisionAgent::Auth::User.new(
|
|
271
|
+
email: "test@example.com",
|
|
272
|
+
password: "oldpassword"
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
user.update_password("newpassword123")
|
|
276
|
+
|
|
277
|
+
expect(user.authenticate("newpassword123")).to be true
|
|
278
|
+
expect(user.authenticate("oldpassword")).to be false
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
it "updates the updated_at timestamp" do
|
|
282
|
+
user = DecisionAgent::Auth::User.new(
|
|
283
|
+
email: "test@example.com",
|
|
284
|
+
password: "oldpassword"
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
original_updated_at = user.updated_at
|
|
288
|
+
sleep(0.01) # Small delay to ensure timestamp difference
|
|
289
|
+
user.update_password("newpassword123")
|
|
290
|
+
|
|
291
|
+
expect(user.updated_at).to be > original_updated_at
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
RSpec.describe DecisionAgent::Auth::PermissionChecker do
|
|
4
|
+
let(:checker) { DecisionAgent::Auth::PermissionChecker.new }
|
|
5
|
+
|
|
6
|
+
describe "#can?" do
|
|
7
|
+
let(:admin_user) do
|
|
8
|
+
DecisionAgent::Auth::User.new(
|
|
9
|
+
email: "admin@example.com",
|
|
10
|
+
password: "password123",
|
|
11
|
+
roles: [:admin]
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
let(:editor_user) do
|
|
16
|
+
DecisionAgent::Auth::User.new(
|
|
17
|
+
email: "editor@example.com",
|
|
18
|
+
password: "password123",
|
|
19
|
+
roles: [:editor]
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
let(:viewer_user) do
|
|
24
|
+
DecisionAgent::Auth::User.new(
|
|
25
|
+
email: "viewer@example.com",
|
|
26
|
+
password: "password123",
|
|
27
|
+
roles: [:viewer]
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "returns true if user has permission" do
|
|
32
|
+
expect(checker.can?(admin_user, :write)).to be true
|
|
33
|
+
expect(checker.can?(editor_user, :write)).to be true
|
|
34
|
+
expect(checker.can?(viewer_user, :read)).to be true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "returns false if user lacks permission" do
|
|
38
|
+
expect(checker.can?(viewer_user, :write)).to be false
|
|
39
|
+
expect(checker.can?(editor_user, :delete)).to be false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "returns false for nil user" do
|
|
43
|
+
expect(checker.can?(nil, :read)).to be false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "returns false for inactive user" do
|
|
47
|
+
admin_user.active = false
|
|
48
|
+
expect(checker.can?(admin_user, :read)).to be false
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe "#require_permission!" do
|
|
53
|
+
let(:user) do
|
|
54
|
+
DecisionAgent::Auth::User.new(
|
|
55
|
+
email: "user@example.com",
|
|
56
|
+
password: "password123",
|
|
57
|
+
roles: [:viewer]
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "does not raise if user has permission" do
|
|
62
|
+
expect do
|
|
63
|
+
checker.require_permission!(user, :read)
|
|
64
|
+
end.not_to raise_error
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "raises PermissionDeniedError if user lacks permission" do
|
|
68
|
+
expect do
|
|
69
|
+
checker.require_permission!(user, :write)
|
|
70
|
+
end.to raise_error(DecisionAgent::PermissionDeniedError)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
describe "#has_role?" do
|
|
75
|
+
let(:user) do
|
|
76
|
+
DecisionAgent::Auth::User.new(
|
|
77
|
+
email: "user@example.com",
|
|
78
|
+
password: "password123",
|
|
79
|
+
roles: [:editor]
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "returns true if user has role" do
|
|
84
|
+
expect(checker.has_role?(user, :editor)).to be true
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "returns false if user lacks role" do
|
|
88
|
+
expect(checker.has_role?(user, :admin)).to be false
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "returns false for nil user" do
|
|
92
|
+
expect(checker.has_role?(nil, :editor)).to be false
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
describe "#require_role!" do
|
|
97
|
+
let(:user) do
|
|
98
|
+
DecisionAgent::Auth::User.new(
|
|
99
|
+
email: "user@example.com",
|
|
100
|
+
password: "password123",
|
|
101
|
+
roles: [:editor]
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "does not raise if user has role" do
|
|
106
|
+
expect do
|
|
107
|
+
checker.require_role!(user, :editor)
|
|
108
|
+
end.not_to raise_error
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "returns true if user has role" do
|
|
112
|
+
result = checker.require_role!(user, :editor)
|
|
113
|
+
expect(result).to be true
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "raises PermissionDeniedError if user lacks role" do
|
|
117
|
+
expect do
|
|
118
|
+
checker.require_role!(user, :admin)
|
|
119
|
+
end.to raise_error(DecisionAgent::PermissionDeniedError, /User does not have role: admin/)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
describe "#active?" do
|
|
124
|
+
let(:user) do
|
|
125
|
+
DecisionAgent::Auth::User.new(
|
|
126
|
+
email: "user@example.com",
|
|
127
|
+
password: "password123"
|
|
128
|
+
)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it "returns true for active user" do
|
|
132
|
+
user.active = true
|
|
133
|
+
expect(checker.active?(user)).to be true
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it "returns false for inactive user" do
|
|
137
|
+
user.active = false
|
|
138
|
+
expect(checker.active?(user)).to be false
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it "returns true for user without active attribute" do
|
|
142
|
+
user = double("User", id: "123", email: "test@example.com")
|
|
143
|
+
expect(checker.active?(user)).to be true
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it "returns false for nil user" do
|
|
147
|
+
expect(checker.active?(nil)).to be false
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
describe "#user_id" do
|
|
152
|
+
let(:user) do
|
|
153
|
+
DecisionAgent::Auth::User.new(
|
|
154
|
+
email: "user@example.com",
|
|
155
|
+
password: "password123"
|
|
156
|
+
)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it "returns user id" do
|
|
160
|
+
expect(checker.user_id(user)).to eq(user.id)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it "returns nil for nil user" do
|
|
164
|
+
expect(checker.user_id(nil)).to be_nil
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it "handles user without id method" do
|
|
168
|
+
user = double("User", to_s: "user_string")
|
|
169
|
+
expect(checker.user_id(user)).to eq("user_string")
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
describe "#user_email" do
|
|
174
|
+
let(:user) do
|
|
175
|
+
DecisionAgent::Auth::User.new(
|
|
176
|
+
email: "user@example.com",
|
|
177
|
+
password: "password123"
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
it "returns user email" do
|
|
182
|
+
expect(checker.user_email(user)).to eq("user@example.com")
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it "returns nil for nil user" do
|
|
186
|
+
expect(checker.user_email(nil)).to be_nil
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it "returns nil for user without email method" do
|
|
190
|
+
user = double("User", id: "123")
|
|
191
|
+
expect(checker.user_email(user)).to be_nil
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
describe "#adapter" do
|
|
196
|
+
it "uses default adapter when none provided" do
|
|
197
|
+
checker = DecisionAgent::Auth::PermissionChecker.new
|
|
198
|
+
expect(checker.adapter).to be_a(DecisionAgent::Auth::DefaultAdapter)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
it "uses provided adapter" do
|
|
202
|
+
custom_adapter = DecisionAgent::Auth::DefaultAdapter.new
|
|
203
|
+
checker = DecisionAgent::Auth::PermissionChecker.new(adapter: custom_adapter)
|
|
204
|
+
expect(checker.adapter).to eq(custom_adapter)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
RSpec.describe DecisionAgent::Auth::Permission do
|
|
4
|
+
describe ".all" do
|
|
5
|
+
it "returns all permission symbols" do
|
|
6
|
+
permissions = DecisionAgent::Auth::Permission.all
|
|
7
|
+
expect(permissions).to be_an(Array)
|
|
8
|
+
expect(permissions).to include(:read, :write, :delete, :approve, :deploy, :manage_users, :audit)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "returns only symbol keys" do
|
|
12
|
+
permissions = DecisionAgent::Auth::Permission.all
|
|
13
|
+
expect(permissions.all? { |p| p.is_a?(Symbol) }).to be true
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe ".exists?" do
|
|
18
|
+
it "returns true for valid permissions" do
|
|
19
|
+
expect(DecisionAgent::Auth::Permission.exists?(:read)).to be true
|
|
20
|
+
expect(DecisionAgent::Auth::Permission.exists?(:write)).to be true
|
|
21
|
+
expect(DecisionAgent::Auth::Permission.exists?(:delete)).to be true
|
|
22
|
+
expect(DecisionAgent::Auth::Permission.exists?(:approve)).to be true
|
|
23
|
+
expect(DecisionAgent::Auth::Permission.exists?(:deploy)).to be true
|
|
24
|
+
expect(DecisionAgent::Auth::Permission.exists?(:manage_users)).to be true
|
|
25
|
+
expect(DecisionAgent::Auth::Permission.exists?(:audit)).to be true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "converts string to symbol" do
|
|
29
|
+
expect(DecisionAgent::Auth::Permission.exists?("read")).to be true
|
|
30
|
+
expect(DecisionAgent::Auth::Permission.exists?("write")).to be true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "returns false for invalid permissions" do
|
|
34
|
+
expect(DecisionAgent::Auth::Permission.exists?(:invalid)).to be false
|
|
35
|
+
expect(DecisionAgent::Auth::Permission.exists?("invalid")).to be false
|
|
36
|
+
expect(DecisionAgent::Auth::Permission.exists?(:unknown)).to be false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "raises error for nil (not handled)" do
|
|
40
|
+
expect do
|
|
41
|
+
DecisionAgent::Auth::Permission.exists?(nil)
|
|
42
|
+
end.to raise_error(NoMethodError)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe ".description_for" do
|
|
47
|
+
it "returns description for valid permissions" do
|
|
48
|
+
expect(DecisionAgent::Auth::Permission.description_for(:read)).to eq("Read access to rules and versions")
|
|
49
|
+
expect(DecisionAgent::Auth::Permission.description_for(:write)).to eq("Create and modify rules")
|
|
50
|
+
expect(DecisionAgent::Auth::Permission.description_for(:delete)).to eq("Delete rules and versions")
|
|
51
|
+
expect(DecisionAgent::Auth::Permission.description_for(:approve)).to eq("Approve rule changes")
|
|
52
|
+
expect(DecisionAgent::Auth::Permission.description_for(:deploy)).to eq("Deploy rule versions")
|
|
53
|
+
expect(DecisionAgent::Auth::Permission.description_for(:manage_users)).to eq("Manage users and roles")
|
|
54
|
+
expect(DecisionAgent::Auth::Permission.description_for(:audit)).to eq("Access audit logs")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "converts string to symbol" do
|
|
58
|
+
expect(DecisionAgent::Auth::Permission.description_for("read")).to eq("Read access to rules and versions")
|
|
59
|
+
expect(DecisionAgent::Auth::Permission.description_for("write")).to eq("Create and modify rules")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "returns nil for invalid permissions" do
|
|
63
|
+
expect(DecisionAgent::Auth::Permission.description_for(:invalid)).to be_nil
|
|
64
|
+
expect(DecisionAgent::Auth::Permission.description_for("invalid")).to be_nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "raises error for nil (not handled)" do
|
|
68
|
+
expect do
|
|
69
|
+
DecisionAgent::Auth::Permission.description_for(nil)
|
|
70
|
+
end.to raise_error(NoMethodError)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|