decision_agent 0.1.3 → 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_test.rb +197 -0
- data/lib/decision_agent/ab_testing/ab_test_assignment.rb +76 -0
- data/lib/decision_agent/ab_testing/ab_test_manager.rb +317 -0
- data/lib/decision_agent/ab_testing/ab_testing_agent.rb +188 -0
- data/lib/decision_agent/ab_testing/storage/activerecord_adapter.rb +155 -0
- data/lib/decision_agent/ab_testing/storage/adapter.rb +67 -0
- data/lib/decision_agent/ab_testing/storage/memory_adapter.rb +116 -0
- 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 +164 -7
- data/lib/decision_agent/monitoring/storage/activerecord_adapter.rb +253 -0
- data/lib/decision_agent/monitoring/storage/base_adapter.rb +90 -0
- data/lib/decision_agent/monitoring/storage/memory_adapter.rb +222 -0
- 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 +59 -0
- data/lib/generators/decision_agent/install/install_generator.rb +37 -0
- data/lib/generators/decision_agent/install/templates/ab_test_assignment_model.rb +45 -0
- data/lib/generators/decision_agent/install/templates/ab_test_model.rb +54 -0
- data/lib/generators/decision_agent/install/templates/ab_testing_migration.rb +43 -0
- data/lib/generators/decision_agent/install/templates/ab_testing_tasks.rake +189 -0
- data/lib/generators/decision_agent/install/templates/decision_agent_tasks.rake +114 -0
- data/lib/generators/decision_agent/install/templates/decision_log.rb +57 -0
- data/lib/generators/decision_agent/install/templates/error_metric.rb +53 -0
- data/lib/generators/decision_agent/install/templates/evaluation_metric.rb +43 -0
- data/lib/generators/decision_agent/install/templates/monitoring_migration.rb +109 -0
- data/lib/generators/decision_agent/install/templates/performance_metric.rb +76 -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 +612 -0
- data/spec/ab_testing/ab_test_spec.rb +270 -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 -548
- data/spec/issue_verification_spec.rb +95 -21
- data/spec/monitoring/metrics_collector_spec.rb +221 -3
- data/spec/monitoring/monitored_agent_spec.rb +1 -1
- data/spec/monitoring/prometheus_exporter_spec.rb +1 -1
- data/spec/monitoring/storage/activerecord_adapter_spec.rb +498 -0
- data/spec/monitoring/storage/base_adapter_spec.rb +61 -0
- data/spec/monitoring/storage/memory_adapter_spec.rb +247 -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 +123 -6
|
@@ -254,6 +254,54 @@ RSpec.describe "Issue Verification Tests" do
|
|
|
254
254
|
)
|
|
255
255
|
end.to raise_error(ActiveRecord::RecordNotUnique)
|
|
256
256
|
end
|
|
257
|
+
|
|
258
|
+
it "verifies application-level constraint for single active version (all databases)" do
|
|
259
|
+
# For databases that don't support partial unique indexes (like SQLite),
|
|
260
|
+
# the application should enforce only one active version per rule
|
|
261
|
+
|
|
262
|
+
ActiveRecord::Schema.define do
|
|
263
|
+
create_table :rule_versions, force: true do |t|
|
|
264
|
+
t.string :rule_id, null: false
|
|
265
|
+
t.integer :version_number, null: false
|
|
266
|
+
t.text :content, null: false
|
|
267
|
+
t.string :status, default: "active", null: false
|
|
268
|
+
t.timestamps
|
|
269
|
+
end
|
|
270
|
+
add_index :rule_versions, %i[rule_id version_number], unique: true
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
class TestRuleVersion6 < ActiveRecord::Base
|
|
274
|
+
self.table_name = "rule_versions"
|
|
275
|
+
|
|
276
|
+
# Application-level validation (works on all databases)
|
|
277
|
+
validate :only_one_active_per_rule, if: -> { status == "active" }
|
|
278
|
+
|
|
279
|
+
def only_one_active_per_rule
|
|
280
|
+
existing = self.class.where(rule_id: rule_id, status: "active")
|
|
281
|
+
existing = existing.where.not(id: id) if persisted?
|
|
282
|
+
return unless existing.exists?
|
|
283
|
+
|
|
284
|
+
errors.add(:base, "Only one active version allowed per rule")
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
TestRuleVersion6.create!(
|
|
289
|
+
rule_id: "test_rule",
|
|
290
|
+
version_number: 1,
|
|
291
|
+
content: { test: "v1" }.to_json,
|
|
292
|
+
status: "active"
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# Try to create second active version - should fail with validation error
|
|
296
|
+
expect do
|
|
297
|
+
TestRuleVersion6.create!(
|
|
298
|
+
rule_id: "test_rule",
|
|
299
|
+
version_number: 2,
|
|
300
|
+
content: { test: "v2" }.to_json,
|
|
301
|
+
status: "active"
|
|
302
|
+
)
|
|
303
|
+
end.to raise_error(ActiveRecord::RecordInvalid, /Only one active version allowed/)
|
|
304
|
+
end
|
|
257
305
|
end
|
|
258
306
|
end
|
|
259
307
|
end
|
|
@@ -466,7 +514,11 @@ RSpec.describe "Issue Verification Tests" do
|
|
|
466
514
|
add_index :rule_versions, %i[rule_id version_number], unique: true
|
|
467
515
|
end
|
|
468
516
|
|
|
469
|
-
|
|
517
|
+
if defined?(RuleVersion)
|
|
518
|
+
# Clear existing validations if RuleVersion was defined by another spec
|
|
519
|
+
RuleVersion.clear_validators!
|
|
520
|
+
RuleVersion.reset_callbacks(:validate)
|
|
521
|
+
else
|
|
470
522
|
class ::RuleVersion < ActiveRecord::Base
|
|
471
523
|
end
|
|
472
524
|
end
|
|
@@ -495,33 +547,55 @@ RSpec.describe "Issue Verification Tests" do
|
|
|
495
547
|
end.to raise_error(DecisionAgent::ValidationError, /Invalid JSON/)
|
|
496
548
|
end
|
|
497
549
|
|
|
498
|
-
it "
|
|
499
|
-
#
|
|
500
|
-
|
|
550
|
+
it "handles empty string content in JSON parsing" do
|
|
551
|
+
# Even if the database allows empty strings (no NOT NULL + no validation),
|
|
552
|
+
# the adapter should handle it gracefully when parsing JSON
|
|
553
|
+
version = RuleVersion.create!(
|
|
554
|
+
rule_id: "test_rule",
|
|
555
|
+
version_number: 1,
|
|
556
|
+
content: "", # EMPTY STRING!
|
|
557
|
+
created_by: "test",
|
|
558
|
+
status: "active"
|
|
559
|
+
)
|
|
501
560
|
|
|
502
|
-
#
|
|
503
|
-
|
|
504
|
-
|
|
561
|
+
# serialize_version should catch JSON parsing errors
|
|
562
|
+
expect do
|
|
563
|
+
adapter.send(:serialize_version, version)
|
|
564
|
+
end.to raise_error(DecisionAgent::ValidationError, /Invalid JSON/)
|
|
505
565
|
end
|
|
506
566
|
|
|
507
|
-
it "
|
|
508
|
-
#
|
|
509
|
-
# The database
|
|
510
|
-
skip "Schema has NOT NULL constraint on content column"
|
|
567
|
+
it "enforces NOT NULL constraint on content column" do
|
|
568
|
+
# The schema has NOT NULL constraint on content column
|
|
569
|
+
# The database should raise an error when trying to create with nil content
|
|
511
570
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
571
|
+
expect do
|
|
572
|
+
RuleVersion.create!(
|
|
573
|
+
rule_id: "test_rule",
|
|
574
|
+
version_number: 1,
|
|
575
|
+
content: nil, # NIL!
|
|
576
|
+
created_by: "test",
|
|
577
|
+
status: "active"
|
|
578
|
+
)
|
|
579
|
+
end.to raise_error(ActiveRecord::NotNullViolation)
|
|
516
580
|
end
|
|
517
581
|
|
|
518
|
-
it "
|
|
519
|
-
#
|
|
520
|
-
|
|
582
|
+
it "handles content with special UTF-8 characters correctly" do
|
|
583
|
+
# Instead of testing malformed UTF-8 (which ActiveRecord rejects),
|
|
584
|
+
# test that valid UTF-8 special characters are handled correctly
|
|
585
|
+
special_content = {
|
|
586
|
+
"unicode" => "Hello \u4E16\u754C",
|
|
587
|
+
"emoji" => "\u{1F44D}",
|
|
588
|
+
"special" => "\n\t\r"
|
|
589
|
+
}
|
|
521
590
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
591
|
+
version = adapter.create_version(
|
|
592
|
+
rule_id: "test_rule",
|
|
593
|
+
content: special_content,
|
|
594
|
+
metadata: { created_by: "test" }
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
loaded = adapter.get_version(version_id: version[:id])
|
|
598
|
+
expect(loaded[:content]).to eq(special_content)
|
|
525
599
|
end
|
|
526
600
|
|
|
527
601
|
it "raises ValidationError when content is truncated JSON" do
|
|
@@ -2,7 +2,7 @@ require "spec_helper"
|
|
|
2
2
|
require "decision_agent/monitoring/metrics_collector"
|
|
3
3
|
|
|
4
4
|
RSpec.describe DecisionAgent::Monitoring::MetricsCollector do
|
|
5
|
-
let(:collector) { described_class.new(window_size: 60) }
|
|
5
|
+
let(:collector) { described_class.new(window_size: 60, storage: :memory) }
|
|
6
6
|
let(:decision) do
|
|
7
7
|
double(
|
|
8
8
|
"Decision",
|
|
@@ -266,7 +266,7 @@ RSpec.describe DecisionAgent::Monitoring::MetricsCollector do
|
|
|
266
266
|
|
|
267
267
|
describe "metric cleanup" do
|
|
268
268
|
it "removes old metrics outside window" do
|
|
269
|
-
collector = described_class.new(window_size: 1)
|
|
269
|
+
collector = described_class.new(window_size: 1, storage: :memory, cleanup_threshold: 1)
|
|
270
270
|
|
|
271
271
|
collector.record_decision(decision, context)
|
|
272
272
|
expect(collector.metrics_count[:decisions]).to eq(1)
|
|
@@ -274,8 +274,226 @@ RSpec.describe DecisionAgent::Monitoring::MetricsCollector do
|
|
|
274
274
|
sleep 1.5
|
|
275
275
|
|
|
276
276
|
collector.record_decision(decision, context)
|
|
277
|
-
# Old metric should be cleaned up
|
|
277
|
+
# Old metric should be cleaned up (threshold=1 means cleanup on every record)
|
|
278
278
|
expect(collector.metrics_count[:decisions]).to eq(1)
|
|
279
279
|
end
|
|
280
280
|
end
|
|
281
|
+
|
|
282
|
+
describe "#record_evaluation" do
|
|
283
|
+
let(:evaluation) do
|
|
284
|
+
double(
|
|
285
|
+
"Evaluation",
|
|
286
|
+
decision: "approve",
|
|
287
|
+
weight: 0.9,
|
|
288
|
+
evaluator_name: "test_evaluator"
|
|
289
|
+
)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
it "notifies observers" do
|
|
293
|
+
observed = []
|
|
294
|
+
collector.add_observer do |type, metric|
|
|
295
|
+
observed << [type, metric]
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
collector.record_evaluation(evaluation)
|
|
299
|
+
|
|
300
|
+
expect(observed.size).to eq(1)
|
|
301
|
+
expect(observed[0][0]).to eq(:evaluation)
|
|
302
|
+
expect(observed[0][1][:decision]).to eq("approve")
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
describe "#record_performance" do
|
|
307
|
+
it "notifies observers" do
|
|
308
|
+
observed = []
|
|
309
|
+
collector.add_observer do |type, metric|
|
|
310
|
+
observed << [type, metric]
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
collector.record_performance(operation: "test", duration_ms: 10.0, success: true)
|
|
314
|
+
|
|
315
|
+
expect(observed.size).to eq(1)
|
|
316
|
+
expect(observed[0][0]).to eq(:performance)
|
|
317
|
+
expect(observed[0][1][:operation]).to eq("test")
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
describe "#record_error" do
|
|
322
|
+
it "notifies observers" do
|
|
323
|
+
observed = []
|
|
324
|
+
collector.add_observer do |type, metric|
|
|
325
|
+
observed << [type, metric]
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
collector.record_error(StandardError.new("Test"))
|
|
329
|
+
|
|
330
|
+
expect(observed.size).to eq(1)
|
|
331
|
+
expect(observed[0][0]).to eq(:error)
|
|
332
|
+
expect(observed[0][1][:error_class]).to eq("StandardError")
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
it "handles different error types" do
|
|
336
|
+
expect { collector.record_error(ArgumentError.new("Arg error")) }.not_to raise_error
|
|
337
|
+
expect { collector.record_error(TypeError.new("Type error")) }.not_to raise_error
|
|
338
|
+
expect { collector.record_error(Exception.new("Exception")) }.not_to raise_error
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
describe "#add_observer" do
|
|
343
|
+
it "adds an observer callback" do
|
|
344
|
+
callback = proc { |type, metric| }
|
|
345
|
+
collector.add_observer(&callback)
|
|
346
|
+
# Observer should be stored
|
|
347
|
+
expect(collector.instance_variable_get(:@observers)).to include(callback)
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
it "handles observer errors gracefully" do
|
|
351
|
+
# Add observer that raises error
|
|
352
|
+
collector.add_observer do |_type, _metric|
|
|
353
|
+
raise "Observer error"
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# Should not raise, just warn
|
|
357
|
+
expect { collector.record_decision(decision, context) }.not_to raise_error
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
describe "#statistics" do
|
|
362
|
+
before do
|
|
363
|
+
3.times do
|
|
364
|
+
evaluation = double("Evaluation", decision: "approve", weight: 0.8, evaluator_name: "eval1")
|
|
365
|
+
collector.record_evaluation(evaluation)
|
|
366
|
+
end
|
|
367
|
+
2.times do
|
|
368
|
+
evaluation = double("Evaluation", decision: "reject", weight: 0.6, evaluator_name: "eval2")
|
|
369
|
+
collector.record_evaluation(evaluation)
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
it "computes evaluation statistics" do
|
|
374
|
+
stats = collector.statistics
|
|
375
|
+
expect(stats[:evaluations][:total]).to eq(5)
|
|
376
|
+
expect(stats[:evaluations][:avg_weight]).to be_within(0.01).of(0.72)
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
it "handles empty decisions gracefully" do
|
|
380
|
+
empty_collector = described_class.new(storage: :memory)
|
|
381
|
+
stats = empty_collector.statistics
|
|
382
|
+
expect(stats[:decisions]).to eq({})
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
it "handles decisions without duration_ms" do
|
|
386
|
+
decision_no_duration = double(
|
|
387
|
+
"Decision",
|
|
388
|
+
decision: "approve",
|
|
389
|
+
confidence: 0.5,
|
|
390
|
+
evaluations: []
|
|
391
|
+
)
|
|
392
|
+
collector.record_decision(decision_no_duration, context)
|
|
393
|
+
stats = collector.statistics
|
|
394
|
+
expect(stats[:decisions][:avg_duration_ms]).to be_nil
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
describe "#time_series" do
|
|
399
|
+
it "handles empty metric types" do
|
|
400
|
+
series = collector.time_series(metric_type: :nonexistent, bucket_size: 60, time_range: 3600)
|
|
401
|
+
expect(series).to eq([])
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
it "filters metrics by time range" do
|
|
405
|
+
# Record some old metrics (simulated)
|
|
406
|
+
old_time = Time.now.utc - 7200
|
|
407
|
+
allow(Time).to receive(:now).and_return(Time.at(old_time.to_i))
|
|
408
|
+
5.times { collector.record_decision(decision, context) }
|
|
409
|
+
|
|
410
|
+
# Record new metrics
|
|
411
|
+
allow(Time).to receive(:now).and_call_original
|
|
412
|
+
3.times { collector.record_decision(decision, context) }
|
|
413
|
+
|
|
414
|
+
series = collector.time_series(metric_type: :decisions, bucket_size: 60, time_range: 3600)
|
|
415
|
+
# Should only include recent metrics
|
|
416
|
+
total = series.sum { |s| s[:count] }
|
|
417
|
+
expect(total).to be <= 3
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
describe "#cleanup_old_metrics_from_storage" do
|
|
422
|
+
it "delegates to storage adapter if it has cleanup method" do
|
|
423
|
+
# Using memory adapter which doesn't have cleanup
|
|
424
|
+
expect(collector.cleanup_old_metrics_from_storage(older_than: 3600)).to eq(0)
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
describe "#initialize_storage_adapter" do
|
|
429
|
+
it "uses memory storage when :memory specified" do
|
|
430
|
+
collector = described_class.new(storage: :memory)
|
|
431
|
+
expect(collector.storage_adapter).to be_a(DecisionAgent::Monitoring::Storage::MemoryAdapter)
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
it "raises error for unknown storage option" do
|
|
435
|
+
expect do
|
|
436
|
+
described_class.new(storage: :unknown)
|
|
437
|
+
end.to raise_error(ArgumentError, /Unknown storage option/)
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
describe "error severity determination" do
|
|
442
|
+
it "determines severity for ArgumentError as medium" do
|
|
443
|
+
error = ArgumentError.new("test")
|
|
444
|
+
collector.record_error(error)
|
|
445
|
+
# Just verify it doesn't raise
|
|
446
|
+
expect(collector.metrics_count[:errors]).to eq(1)
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
it "determines severity for TypeError as medium" do
|
|
450
|
+
error = TypeError.new("test")
|
|
451
|
+
collector.record_error(error)
|
|
452
|
+
expect(collector.metrics_count[:errors]).to eq(1)
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
it "determines severity for Exception as critical" do
|
|
456
|
+
error = Exception.new("test")
|
|
457
|
+
collector.record_error(error)
|
|
458
|
+
expect(collector.metrics_count[:errors]).to eq(1)
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
describe "decision status determination" do
|
|
463
|
+
it "determines status for high confidence decisions" do
|
|
464
|
+
high_conf_decision = double(
|
|
465
|
+
"Decision",
|
|
466
|
+
decision: "approve",
|
|
467
|
+
confidence: 0.9,
|
|
468
|
+
evaluations: []
|
|
469
|
+
)
|
|
470
|
+
collector.record_decision(high_conf_decision, context)
|
|
471
|
+
# Just verify it records successfully
|
|
472
|
+
expect(collector.metrics_count[:decisions]).to eq(1)
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
it "determines status for low confidence decisions" do
|
|
476
|
+
low_conf_decision = double(
|
|
477
|
+
"Decision",
|
|
478
|
+
decision: "approve",
|
|
479
|
+
confidence: 0.2,
|
|
480
|
+
evaluations: []
|
|
481
|
+
)
|
|
482
|
+
collector.record_decision(low_conf_decision, context)
|
|
483
|
+
expect(collector.metrics_count[:decisions]).to eq(1)
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
describe "#compute_performance_stats" do
|
|
488
|
+
it "computes percentile statistics" do
|
|
489
|
+
durations = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
|
|
490
|
+
durations.each do |duration|
|
|
491
|
+
collector.record_performance(operation: "test", duration_ms: duration, success: true)
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
stats = collector.statistics
|
|
495
|
+
expect(stats[:performance][:p95_duration_ms]).to be >= 90
|
|
496
|
+
expect(stats[:performance][:p99_duration_ms]).to be >= 95
|
|
497
|
+
end
|
|
498
|
+
end
|
|
281
499
|
end
|
|
@@ -3,7 +3,7 @@ require "decision_agent/monitoring/metrics_collector"
|
|
|
3
3
|
require "decision_agent/monitoring/monitored_agent"
|
|
4
4
|
|
|
5
5
|
RSpec.describe DecisionAgent::Monitoring::MonitoredAgent do
|
|
6
|
-
let(:collector) { DecisionAgent::Monitoring::MetricsCollector.new }
|
|
6
|
+
let(:collector) { DecisionAgent::Monitoring::MetricsCollector.new(storage: :memory) }
|
|
7
7
|
let(:evaluator) do
|
|
8
8
|
double(
|
|
9
9
|
"Evaluator",
|
|
@@ -3,7 +3,7 @@ require "decision_agent/monitoring/metrics_collector"
|
|
|
3
3
|
require "decision_agent/monitoring/prometheus_exporter"
|
|
4
4
|
|
|
5
5
|
RSpec.describe DecisionAgent::Monitoring::PrometheusExporter do
|
|
6
|
-
let(:collector) { DecisionAgent::Monitoring::MetricsCollector.new }
|
|
6
|
+
let(:collector) { DecisionAgent::Monitoring::MetricsCollector.new(storage: :memory) }
|
|
7
7
|
let(:exporter) { described_class.new(metrics_collector: collector, namespace: "test") }
|
|
8
8
|
|
|
9
9
|
let(:decision) do
|