mirror of
https://github.com/we-promise/sure.git
synced 2026-09-07 15:44:21 +00:00
* Add synthetic PDF health checks * Require exact marker in PDF health probes * Report PDF health paths separately * Refactor application code * Remove unrelated schema dump changes * Simplify synthetic PDF validation --------- Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
124 lines
3.5 KiB
Ruby
124 lines
3.5 KiB
Ruby
require "test_helper"
|
|
|
|
class Provider::Openai::PdfProcessorTest < ActiveSupport::TestCase
|
|
setup do
|
|
@pdf_content = "%PDF-1.4 fake bytes".b
|
|
end
|
|
|
|
test "extracts only allowlisted error fields into span output when the API call fails" do
|
|
error = StandardError.new("boom")
|
|
def error.response_body
|
|
{
|
|
"error" => { "type" => "invalid_request_error", "message" => "invalid request", "code" => "bad_pdf" },
|
|
"request" => { "messages" => "statement text that should never leak" }
|
|
}
|
|
end
|
|
def error.response_headers
|
|
{ "x-request-id" => "req_abc123" }
|
|
end
|
|
|
|
captured_output = nil
|
|
trace = stub_trace { |output| captured_output = output }
|
|
|
|
assert_raises(StandardError) do
|
|
build_processor(error, trace).process
|
|
end
|
|
|
|
assert_equal(
|
|
{ type: "invalid_request_error", message: "invalid request", code: "bad_pdf", request_id: "req_abc123" },
|
|
captured_output[:error_detail]
|
|
)
|
|
end
|
|
|
|
test "error_detail is nil in span output when the error exposes no response_body" do
|
|
error = StandardError.new("boom")
|
|
|
|
captured_output = nil
|
|
trace = stub_trace { |output| captured_output = output }
|
|
|
|
assert_raises(StandardError) do
|
|
build_processor(error, trace).process
|
|
end
|
|
|
|
assert_nil captured_output[:error_detail]
|
|
end
|
|
|
|
test "error_detail falls back to a placeholder when reading response_body itself raises" do
|
|
error = StandardError.new("boom")
|
|
def error.response_body
|
|
raise "response_body accessor exploded"
|
|
end
|
|
|
|
captured_output = nil
|
|
trace = stub_trace { |output| captured_output = output }
|
|
|
|
assert_raises(StandardError) do
|
|
build_processor(error, trace).process
|
|
end
|
|
|
|
assert_match(/detail unavailable/i, captured_output[:error_detail])
|
|
end
|
|
|
|
test "text mode exercises only text extraction" do
|
|
expected = Provider::LlmConcept::PdfProcessingResult.new(
|
|
summary: "Synthetic PDF",
|
|
document_type: "other",
|
|
extracted_data: {}
|
|
)
|
|
processor = Provider::Openai::PdfProcessor.new(
|
|
mock,
|
|
model: "gpt-4.1",
|
|
pdf_content: @pdf_content,
|
|
max_response_tokens: 512,
|
|
processing_mode: :text
|
|
)
|
|
processor.expects(:process_with_text_extraction).returns(expected)
|
|
processor.expects(:process_with_vision).never
|
|
|
|
assert_equal expected, processor.process
|
|
end
|
|
|
|
test "vision mode exercises only vision processing" do
|
|
expected = Provider::LlmConcept::PdfProcessingResult.new(
|
|
summary: "Synthetic PDF",
|
|
document_type: "other",
|
|
extracted_data: {}
|
|
)
|
|
processor = Provider::Openai::PdfProcessor.new(
|
|
mock,
|
|
model: "gpt-4.1",
|
|
pdf_content: @pdf_content,
|
|
max_response_tokens: 512,
|
|
processing_mode: :vision
|
|
)
|
|
processor.expects(:process_with_text_extraction).never
|
|
processor.expects(:process_with_vision).returns(expected)
|
|
|
|
assert_equal expected, processor.process
|
|
end
|
|
|
|
private
|
|
def build_processor(error, trace)
|
|
client = mock
|
|
client.expects(:chat).raises(error)
|
|
|
|
processor = Provider::Openai::PdfProcessor.new(
|
|
client,
|
|
model: "gpt-4.1",
|
|
pdf_content: @pdf_content,
|
|
langfuse_trace: trace,
|
|
max_response_tokens: 1000
|
|
)
|
|
processor.stubs(:extract_text_from_pdf).returns("Statement text")
|
|
processor
|
|
end
|
|
|
|
def stub_trace
|
|
span = mock
|
|
span.expects(:end).with { |args| yield(args[:output]); true }
|
|
trace = mock
|
|
trace.stubs(:span).returns(span)
|
|
trace
|
|
end
|
|
end
|