Module: RubyLLM::Contract::MinitestHelpers
- Defined in:
- lib/ruby_llm/contract/minitest.rb
Instance Method Summary collapse
- #assert_eval_passes(step, eval_name, minimum_score: nil, maximum_cost: nil, context: {}, msg: nil) ⇒ Object
- #assert_satisfies_contract(result, msg = nil) ⇒ Object
- #refute_satisfies_contract(result, msg = nil) ⇒ Object
-
#setup ⇒ Object
Snapshot adapter before each test so teardown can restore it.
-
#stub_all_steps(response: nil, responses: nil, &block) ⇒ Object
Set a global test adapter for ALL steps.
-
#stub_step(step_class, response: nil, responses: nil, &block) ⇒ Object
Stub a specific step to return a canned response without API calls.
-
#stub_steps(stubs, &block) ⇒ Object
Stub multiple steps at once with different responses.
-
#teardown ⇒ Object
Auto-cleanup: clear overrides AND restore original adapter.
Instance Method Details
#assert_eval_passes(step, eval_name, minimum_score: nil, maximum_cost: nil, context: {}, msg: nil) ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/ruby_llm/contract/minitest.rb', line 31 def assert_eval_passes(step, eval_name, minimum_score: nil, maximum_cost: nil, context: {}, msg: nil) report = step.run_eval(eval_name, context: context) if minimum_score assert report.score >= minimum_score, msg || "Expected #{eval_name} eval score >= #{minimum_score}, got #{report.score.round(2)} (#{report.pass_rate})" else assert report.passed?, msg || "Expected #{eval_name} eval to pass, got #{report.score.round(2)} (#{report.pass_rate})" end if maximum_cost assert report.total_cost <= maximum_cost, msg || "Expected #{eval_name} eval cost <= $#{format("%.4f", maximum_cost)}, got $#{format("%.4f", report.total_cost)}" end report end |
#assert_satisfies_contract(result, msg = nil) ⇒ Object
22 23 24 25 |
# File 'lib/ruby_llm/contract/minitest.rb', line 22 def assert_satisfies_contract(result, msg = nil) assert result.ok?, msg || "Expected step result to satisfy contract, " \ "but got status: #{result.status}. Errors: #{result.validation_errors.join(", ")}" end |
#refute_satisfies_contract(result, msg = nil) ⇒ Object
27 28 29 |
# File 'lib/ruby_llm/contract/minitest.rb', line 27 def refute_satisfies_contract(result, msg = nil) refute result.ok?, msg || "Expected step result NOT to satisfy contract, but it passed" end |
#setup ⇒ Object
Snapshot adapter before each test so teardown can restore it.
9 10 11 12 |
# File 'lib/ruby_llm/contract/minitest.rb', line 9 def setup super if defined?(super) @_contract_original_adapter = RubyLLM::Contract.configuration.default_adapter end |
#stub_all_steps(response: nil, responses: nil, &block) ⇒ Object
Set a global test adapter for ALL steps.
stub_all_steps(response: { default: true })
Supports an optional block form — the previous adapter is restored after the block returns (even if it raises):
stub_all_steps(response: { default: true }) do
# all steps use test adapter
end
# original adapter restored
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/ruby_llm/contract/minitest.rb', line 139 def stub_all_steps(response: nil, responses: nil, &block) adapter = if responses Adapters::Test.new(responses: responses) else Adapters::Test.new(response: response) end if block previous = RubyLLM::Contract.configuration.default_adapter begin RubyLLM::Contract.configuration.default_adapter = adapter yield ensure RubyLLM::Contract.configuration.default_adapter = previous end else RubyLLM::Contract.configure { |c| c.default_adapter = adapter } end end |
#stub_step(step_class, response: nil, responses: nil, &block) ⇒ Object
Stub a specific step to return a canned response without API calls. Routes per-step — other steps are not affected.
stub_step(ClassifyTicket, response: { priority: "high" })
Supports an optional block form — the override is removed after the block returns (even if it raises):
stub_step(ClassifyTicket, response: data) do
result = ClassifyTicket.run("test")
end
# ClassifyTicket.run no longer stubbed
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/ruby_llm/contract/minitest.rb', line 63 def stub_step(step_class, response: nil, responses: nil, &block) adapter = if responses Adapters::Test.new(responses: responses) else Adapters::Test.new(response: response) end overrides = RubyLLM::Contract.step_adapter_overrides previous = overrides[step_class] overrides[step_class] = adapter if block begin yield ensure if previous overrides[step_class] = previous else overrides.delete(step_class) end end end end |
#stub_steps(stubs, &block) ⇒ Object
Stub multiple steps at once with different responses. Takes a hash of step_class => options. Requires a block.
stub_steps(
ClassifyTicket => { response: { priority: "high" } },
RouteToTeam => { response: { team: "billing" } }
) do
result = TicketPipeline.run("test")
end
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/ruby_llm/contract/minitest.rb', line 97 def stub_steps(stubs, &block) raise ArgumentError, "stub_steps requires a block" unless block overrides = RubyLLM::Contract.step_adapter_overrides previous = {} stubs.each do |step_class, opts| opts = opts.transform_keys(&:to_sym) adapter = if opts[:responses] Adapters::Test.new(responses: opts[:responses]) else Adapters::Test.new(response: opts[:response]) end previous[step_class] = overrides[step_class] overrides[step_class] = adapter end begin yield ensure stubs.each_key do |step_class| if previous[step_class] overrides[step_class] = previous[step_class] else overrides.delete(step_class) end end end end |
#teardown ⇒ Object
Auto-cleanup: clear overrides AND restore original adapter. Prevents both non-block stub_step and stub_all_steps from leaking.
16 17 18 19 20 |
# File 'lib/ruby_llm/contract/minitest.rb', line 16 def teardown RubyLLM::Contract.step_adapter_overrides.clear RubyLLM::Contract.configuration.default_adapter = @_contract_original_adapter super if defined?(super) end |