Add focused smoke tests for the remaining 8 gem classes + adapter-raises edge
Sandi S2 (smoke coverage was 4/12) + Tobi T7 (adapter exception path
untested) consensus actions. 38 new tests across 9 files take total
from 15 -> 53 tests, 50 -> 134 assertions.
Per-class breakdown:
session_test.rb (2 tests)
Contract on initialize parameters (positional record + keyword
callables) and public surface (ensure!/recreate!/abort!/just_created?).
AR fixtures stay in the host suite.
turn_test.rb (4 tests)
Required + optional keyword arg contracts (locks the 9 required
+ 8 optional keys against drift). Public surface = [:call] only.
Result struct exercised as a value object with status predicates
and cost/token delegation.
message_artifacts_test.rb (2 tests)
Contract on initialize parameters; public surface = [:attach_from].
impostor_test.rb (3 tests)
Initialize keyword contract; delegation to ActiveStorage attachment
via a Struct double on #filename.
sandbox_test.rb (5 tests)
Real tmpdir instantiation: #path / #exists? / #files (Enumerator
when block-less, yields SandboxFile values when files present) /
#file lookup-by-basename returns nil for missing.
sandbox_file_test.rb (4 tests)
Real tmpdir + file: basic readers, marcel-backed content_type
detection, #safe? size-cap rejection, #as_artifact identity
conversion returns Opencode::Artifact.
transform_test.rb (8 tests)
Documents the abstract contract (source_filename / destination_filename /
render all raise NotImplementedError). A trivial concrete subclass
inside the test exercises the default implementations of
#applies_to?, #trusted?, #owned_filenames, and #purge_impostors?
that delegate to the two abstract filename methods.
tool_display_test.rb (5 tests)
Known tool canonicalization, status predicates (running/completed/
errored/in_flight/terminal), unknown-tool fallback, nil-part
tolerance (callers sometimes pass non-tool parts_json entries).
uploaded_files_prompt_test.rb (3 tests)
Initialize keyword contract; #text returns raw content + empty
sandbox_file_names map when no files attached; public surface check.
Plus 2 new tests in error_reporter_test.rb (C4):
- test_adapter_exceptions_propagate: adapter that raises must
propagate, not silently swallow — operators need to know the
error tracker is broken.
- test_report_returns_adapter_return_value: report passes through
the adapter's return value verbatim (Rails.error.report returns
the error itself; callers can chain).
Faithful to actual implementations: every test was first written from
the API I expected, then corrected against the class internals when
errors surfaced. The corrections themselves document the contract:
SandboxFile expects a String sandbox_prefix with trailing separator
(not a Pathname), Transform's filename methods are abstract not
nil-defaulting, etc.
This commit is contained in:
@@ -47,4 +47,29 @@ class Opencode::ErrorReporterTest < Minitest::Test
|
||||
Opencode::ErrorReporter.report(RuntimeError.new("kaboom"))
|
||||
assert invoked, "Adapter should be invoked even with no kwargs"
|
||||
end
|
||||
|
||||
def test_adapter_exceptions_propagate
|
||||
# If the host's adapter itself raises (Honeybadger HTTP failure,
|
||||
# Sentry quota error, etc.) the gem must propagate — silently
|
||||
# swallowing the adapter's own errors would hide an outage from
|
||||
# operators who think their error tracker is healthy.
|
||||
Opencode::ErrorReporter.adapter = ->(_error, **_opts) {
|
||||
raise StandardError, "adapter blew up"
|
||||
}
|
||||
|
||||
raised = assert_raises(StandardError) do
|
||||
Opencode::ErrorReporter.report(RuntimeError.new("original"))
|
||||
end
|
||||
assert_equal "adapter blew up", raised.message
|
||||
end
|
||||
|
||||
def test_report_returns_adapter_return_value
|
||||
# Useful for hosts wanting Rails.error.report's standard return
|
||||
# (the error itself). Verifies the call shape doesn't transform it.
|
||||
sentinel = Object.new
|
||||
Opencode::ErrorReporter.adapter = ->(_error, **_opts) { sentinel }
|
||||
|
||||
result = Opencode::ErrorReporter.report(StandardError.new("x"))
|
||||
assert_same sentinel, result
|
||||
end
|
||||
end
|
||||
|
||||
26
test/opencode/impostor_test.rb
Normal file
26
test/opencode/impostor_test.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
# Contract smoke for Opencode::Impostor. Wraps an ActiveStorage
|
||||
# attachment that's been replaced by a Transform-rendered artifact.
|
||||
# Behavioral tests against real ActiveStorage::Attachment live in
|
||||
# the host application.
|
||||
class Opencode::ImpostorTest < Minitest::Test
|
||||
def test_initialize_takes_attachment_keyword
|
||||
params = Opencode::Impostor.instance_method(:initialize).parameters
|
||||
assert_includes params, [ :keyreq, :attachment ],
|
||||
"Impostor must require an attachment: keyword (ActiveStorage::Attachment-like)"
|
||||
end
|
||||
|
||||
def test_public_api
|
||||
assert_equal %i[filename purge!].sort,
|
||||
Opencode::Impostor.instance_methods(false).sort
|
||||
end
|
||||
|
||||
def test_filename_delegates_to_attachment
|
||||
attachment_double = Struct.new(:filename).new("legacy.html")
|
||||
impostor = Opencode::Impostor.new(attachment: attachment_double)
|
||||
assert_equal "legacy.html", impostor.filename
|
||||
end
|
||||
end
|
||||
26
test/opencode/message_artifacts_test.rb
Normal file
26
test/opencode/message_artifacts_test.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
# Contract smoke for Opencode::MessageArtifacts (the idempotent
|
||||
# ActiveStorage-backed artifact attachment pipeline). Behavioral
|
||||
# coverage — including ActiveStorage attachment, Transform application,
|
||||
# error reporting via Opencode::ErrorReporter — lives in the host app.
|
||||
class Opencode::MessageArtifactsTest < Minitest::Test
|
||||
def test_initialize_takes_message_feature_and_optional_transforms
|
||||
params = Opencode::MessageArtifacts.instance_method(:initialize).parameters
|
||||
by_kind = params.group_by(&:first).transform_values { |list| list.map(&:last) }
|
||||
|
||||
assert_includes by_kind[:keyreq], :message,
|
||||
"MessageArtifacts must require a message: keyword"
|
||||
assert_includes by_kind[:keyreq], :feature,
|
||||
"MessageArtifacts must require a feature: keyword (used in error reports)"
|
||||
assert_includes by_kind[:key] || [], :transforms,
|
||||
"MessageArtifacts must accept an optional transforms: keyword"
|
||||
end
|
||||
|
||||
def test_public_api_is_attach_from
|
||||
assert_equal [ :attach_from ], Opencode::MessageArtifacts.instance_methods(false),
|
||||
"MessageArtifacts's only public verb is #attach_from"
|
||||
end
|
||||
end
|
||||
62
test/opencode/sandbox_file_test.rb
Normal file
62
test/opencode/sandbox_file_test.rb
Normal file
@@ -0,0 +1,62 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
require "fileutils"
|
||||
require "tmpdir"
|
||||
require "marcel" # SandboxFile#content_type uses Marcel::MimeType.for
|
||||
|
||||
# Smoke test for Opencode::SandboxFile: instantiate against a real
|
||||
# pathname, verify basename/size/content/content_type readers and the
|
||||
# identity conversion to Opencode::Artifact via #as_artifact.
|
||||
class Opencode::SandboxFileTest < Minitest::Test
|
||||
def setup
|
||||
@tmpdir = Dir.mktmpdir("opencode-rails-sandbox-file-test-")
|
||||
@path = File.join(@tmpdir, "notes.md")
|
||||
File.write(@path, "# hello\nworld\n")
|
||||
# SandboxFile uses `start_with?` against this prefix to detect path
|
||||
# escape; it expects a String with trailing separator so that
|
||||
# /sandbox-1 doesn't false-positive on /sandbox-10/foo.
|
||||
@sandbox_prefix = File.join(@tmpdir, "")
|
||||
end
|
||||
|
||||
def teardown
|
||||
FileUtils.remove_entry(@tmpdir) if @tmpdir && File.exist?(@tmpdir)
|
||||
end
|
||||
|
||||
def test_basic_readers
|
||||
file = Opencode::SandboxFile.new(
|
||||
path: @path, sandbox_prefix: @sandbox_prefix, max_bytes: 10_000
|
||||
)
|
||||
|
||||
assert_equal "notes.md", file.basename
|
||||
assert file.size.positive?
|
||||
assert_equal "# hello\nworld\n", file.content
|
||||
assert file.safe?, "small text file inside sandbox should be safe"
|
||||
end
|
||||
|
||||
def test_content_type_detection_via_marcel
|
||||
file = Opencode::SandboxFile.new(
|
||||
path: @path, sandbox_prefix: @sandbox_prefix, max_bytes: 10_000
|
||||
)
|
||||
# Marcel detects .md as text/markdown.
|
||||
assert_match(/markdown|text/, file.content_type)
|
||||
end
|
||||
|
||||
def test_safe_rejects_files_over_size_cap
|
||||
file = Opencode::SandboxFile.new(
|
||||
path: @path, sandbox_prefix: @sandbox_prefix, max_bytes: 5
|
||||
)
|
||||
refute file.safe?, "file larger than max_bytes must be unsafe"
|
||||
end
|
||||
|
||||
def test_as_artifact_returns_opencode_artifact_value
|
||||
file = Opencode::SandboxFile.new(
|
||||
path: @path, sandbox_prefix: @sandbox_prefix, max_bytes: 10_000
|
||||
)
|
||||
|
||||
artifact = file.as_artifact
|
||||
assert_instance_of Opencode::Artifact, artifact
|
||||
assert_equal "notes.md", artifact.filename
|
||||
assert_equal "# hello\nworld\n", artifact.content
|
||||
end
|
||||
end
|
||||
53
test/opencode/sandbox_test.rb
Normal file
53
test/opencode/sandbox_test.rb
Normal file
@@ -0,0 +1,53 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
require "fileutils"
|
||||
require "tmpdir"
|
||||
require "marcel" # SandboxFile (yielded by Sandbox#files) needs marcel
|
||||
|
||||
# Smoke test for Opencode::Sandbox: instantiate against a real tmpdir,
|
||||
# verify #path, #exists?, and that #files / #file return empty / nil
|
||||
# when no sandbox files are present. Behavioral coverage of actual file
|
||||
# enumeration lives in the host application where AIGL + Blackline +
|
||||
# Raven sandbox configurations exercise the path.
|
||||
class Opencode::SandboxTest < Minitest::Test
|
||||
def setup
|
||||
@tmpdir = Dir.mktmpdir("opencode-rails-sandbox-test-")
|
||||
end
|
||||
|
||||
def teardown
|
||||
FileUtils.remove_entry(@tmpdir) if @tmpdir && File.exist?(@tmpdir)
|
||||
end
|
||||
|
||||
def test_path_and_exists_when_directory_present
|
||||
sandbox = Opencode::Sandbox.new(path: @tmpdir)
|
||||
assert_equal @tmpdir, sandbox.path
|
||||
assert sandbox.exists?
|
||||
end
|
||||
|
||||
def test_exists_false_when_path_missing
|
||||
sandbox = Opencode::Sandbox.new(path: File.join(@tmpdir, "missing"))
|
||||
refute sandbox.exists?
|
||||
end
|
||||
|
||||
def test_files_returns_enumerator_yielding_nothing_when_empty
|
||||
sandbox = Opencode::Sandbox.new(path: @tmpdir)
|
||||
# No block given => Enumerator.
|
||||
assert_kind_of Enumerator, sandbox.files
|
||||
assert_equal [], sandbox.files.to_a
|
||||
end
|
||||
|
||||
def test_files_yields_sandbox_files_for_real_entries
|
||||
File.write(File.join(@tmpdir, "notes.md"), "x")
|
||||
File.write(File.join(@tmpdir, "map.md"), "y")
|
||||
|
||||
sandbox = Opencode::Sandbox.new(path: @tmpdir)
|
||||
basenames = sandbox.files.map(&:basename).sort
|
||||
assert_equal %w[map.md notes.md], basenames
|
||||
end
|
||||
|
||||
def test_file_returns_nil_for_missing_relative_name
|
||||
sandbox = Opencode::Sandbox.new(path: @tmpdir)
|
||||
assert_nil sandbox.file("nope.txt")
|
||||
end
|
||||
end
|
||||
27
test/opencode/session_test.rb
Normal file
27
test/opencode/session_test.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
# Contract smoke for Opencode::Session. Behavioral coverage (idempotent
|
||||
# ensure!/recreate!/abort! with row-level locking, race-safety,
|
||||
# permissions_for callable handoff) lives in the host application
|
||||
# where AR fixtures + a real ActiveRecord row exist.
|
||||
class Opencode::SessionTest < Minitest::Test
|
||||
def test_initialize_takes_record_and_two_keyword_callables
|
||||
params = Opencode::Session.instance_method(:initialize).parameters
|
||||
|
||||
assert_includes params, [ :req, :record ],
|
||||
"Session must take a positional record (an AR row with #with_lock, #title, etc.)"
|
||||
assert_includes params, [ :keyreq, :permissions_for ],
|
||||
"Session must require a permissions_for: callable (host-injected per-product permissions)"
|
||||
assert_includes params, [ :key, :on_error ],
|
||||
"Session must accept an optional on_error: callable for adapter-style error reporting"
|
||||
end
|
||||
|
||||
def test_public_api_is_ensure_recreate_abort_just_created
|
||||
methods = Opencode::Session.instance_methods(false).sort
|
||||
assert_equal %i[abort! ensure! just_created? recreate!].sort, methods.sort,
|
||||
"Session's public surface should be exactly: ensure!/recreate!/abort!/just_created?. " \
|
||||
"Found: #{methods.inspect}"
|
||||
end
|
||||
end
|
||||
55
test/opencode/tool_display_test.rb
Normal file
55
test/opencode/tool_display_test.rb
Normal file
@@ -0,0 +1,55 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
# Smoke tests for Opencode::ToolDisplay — the view-model that converts
|
||||
# raw tool-part hashes into Turbo-Stream-friendly props. We exercise
|
||||
# the predicate surface for the canonical 'read' tool plus the
|
||||
# unknown-tool fallback. Exhaustive per-tool render tests live in the
|
||||
# host where the renderer templates are exercised.
|
||||
class Opencode::ToolDisplayTest < Minitest::Test
|
||||
def test_known_read_tool_canonicalization
|
||||
display = Opencode::ToolDisplay.new(
|
||||
"type" => "tool", "tool" => "read", "status" => "completed",
|
||||
"input" => { "filePath" => "/sandbox/notes.md" }
|
||||
)
|
||||
|
||||
assert_equal "read", display.canonical_tool
|
||||
assert display.known?
|
||||
assert display.completed?
|
||||
assert display.terminal?
|
||||
refute display.errored?
|
||||
refute display.in_flight?
|
||||
end
|
||||
|
||||
def test_running_status
|
||||
display = Opencode::ToolDisplay.new("type" => "tool", "tool" => "read", "status" => "running")
|
||||
assert display.in_flight?
|
||||
refute display.terminal?
|
||||
refute display.completed?
|
||||
end
|
||||
|
||||
def test_errored_status
|
||||
display = Opencode::ToolDisplay.new(
|
||||
"type" => "tool", "tool" => "edit", "status" => "error", "error" => "permission denied"
|
||||
)
|
||||
assert display.errored?
|
||||
assert display.terminal?
|
||||
refute display.completed?
|
||||
end
|
||||
|
||||
def test_unknown_tool_falls_back_gracefully
|
||||
display = Opencode::ToolDisplay.new("type" => "tool", "tool" => "wat", "status" => "completed")
|
||||
refute display.known?,
|
||||
"Unknown tools must not claim to be known — host renderer dispatches a fallback view"
|
||||
refute_nil display.canonical_tool,
|
||||
"Unknown tools still need a canonical_tool so DOM ids stay stable"
|
||||
end
|
||||
|
||||
def test_nil_part_initializes_safely
|
||||
# ToolDisplay tolerates a nil part because callers sometimes pass
|
||||
# message.parts_json entries that aren't tool parts.
|
||||
display = Opencode::ToolDisplay.new(nil)
|
||||
refute display.known?
|
||||
end
|
||||
end
|
||||
69
test/opencode/transform_test.rb
Normal file
69
test/opencode/transform_test.rb
Normal file
@@ -0,0 +1,69 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
# Smoke test for Opencode::Transform — the base class for
|
||||
# content-rewriting transforms. It is intentionally abstract:
|
||||
# #source_filename, #destination_filename, and #render all raise
|
||||
# NotImplementedError. Subclasses (host-side) provide the meat.
|
||||
# These tests document the abstract contract.
|
||||
class Opencode::TransformTest < Minitest::Test
|
||||
def test_source_filename_is_abstract
|
||||
err = assert_raises(NotImplementedError) { Opencode::Transform.new.source_filename }
|
||||
assert_match(/must implement #source_filename/, err.message)
|
||||
end
|
||||
|
||||
def test_destination_filename_is_abstract
|
||||
err = assert_raises(NotImplementedError) { Opencode::Transform.new.destination_filename }
|
||||
assert_match(/must implement #destination_filename/, err.message)
|
||||
end
|
||||
|
||||
def test_render_is_abstract
|
||||
err = assert_raises(NotImplementedError) { Opencode::Transform.new.render(Object.new) }
|
||||
assert_match(/must implement #render/, err.message)
|
||||
end
|
||||
|
||||
def test_purge_impostors_defaults_to_false
|
||||
refute Opencode::Transform.new.purge_impostors?,
|
||||
"Default #purge_impostors? must be false — conservative opt-in by subclasses"
|
||||
end
|
||||
|
||||
# A trivial concrete subclass exercises the defaults that DO exist
|
||||
# (#applies_to?, #trusted?, #owned_filenames all delegate to the
|
||||
# two abstract filename methods).
|
||||
class FakeTransform < Opencode::Transform
|
||||
def source_filename = "agent-output.json"
|
||||
def destination_filename = "rendered.html"
|
||||
end
|
||||
|
||||
Attachment = Struct.new(:filename, keyword_init: true)
|
||||
Basenamed = Struct.new(:basename, keyword_init: true)
|
||||
|
||||
def test_applies_to_matches_source_filename_by_default
|
||||
transform = FakeTransform.new
|
||||
matching = Basenamed.new(basename: "agent-output.json")
|
||||
other = Basenamed.new(basename: "something-else.json")
|
||||
|
||||
assert transform.applies_to?(matching)
|
||||
refute transform.applies_to?(other)
|
||||
end
|
||||
|
||||
def test_trusted_matches_destination_filename_by_default
|
||||
transform = FakeTransform.new
|
||||
trusted = Attachment.new(filename: "rendered.html")
|
||||
untrusted = Attachment.new(filename: "agent-output.json")
|
||||
|
||||
assert transform.trusted?(trusted)
|
||||
refute transform.trusted?(untrusted)
|
||||
end
|
||||
|
||||
def test_owned_filenames_is_source_and_destination
|
||||
assert_equal %w[agent-output.json rendered.html],
|
||||
FakeTransform.new.owned_filenames
|
||||
end
|
||||
|
||||
def test_error_is_a_subclass_of_standarderror
|
||||
assert_operator Opencode::Transform::Error, :<, StandardError,
|
||||
"Transform::Error must be rescuable by `rescue StandardError`"
|
||||
end
|
||||
end
|
||||
62
test/opencode/turn_test.rb
Normal file
62
test/opencode/turn_test.rb
Normal file
@@ -0,0 +1,62 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
# Contract smoke for Opencode::Turn (the orchestrator) and its inner
|
||||
# Result value object. Behavioral coverage (the full send -> stream ->
|
||||
# recover -> finalize loop) lives in the host application — Turn needs
|
||||
# an Opencode::Client, an AR Message, a subject record, etc., which are
|
||||
# all integration-level concerns.
|
||||
class Opencode::TurnTest < Minitest::Test
|
||||
REQUIRED_INIT_KEYS = %i[
|
||||
message subject query_text client session_for observer_factory
|
||||
system_context agent_name tracer
|
||||
].freeze
|
||||
|
||||
OPTIONAL_INIT_KEYS = %i[
|
||||
on_finalized on_turn_finished on_activity_tick
|
||||
empty_stream_retry_delay final_exchange_timeout
|
||||
final_exchange_retry_delay error_fallback_content error_feature
|
||||
].freeze
|
||||
|
||||
def test_required_keyword_arguments
|
||||
params = Opencode::Turn.instance_method(:initialize).parameters
|
||||
required = params.select { |kind, _| kind == :keyreq }.map(&:last).sort
|
||||
|
||||
assert_equal REQUIRED_INIT_KEYS.sort, required,
|
||||
"Turn's required keyword args drifted. Expected: #{REQUIRED_INIT_KEYS.sort}, got: #{required}"
|
||||
end
|
||||
|
||||
def test_optional_keyword_arguments_match_documented_surface
|
||||
params = Opencode::Turn.instance_method(:initialize).parameters
|
||||
optional = params.select { |kind, _| kind == :key }.map(&:last).sort
|
||||
|
||||
assert_equal OPTIONAL_INIT_KEYS.sort, optional,
|
||||
"Turn's optional keyword args drifted. Expected: #{OPTIONAL_INIT_KEYS.sort}, got: #{optional}"
|
||||
end
|
||||
|
||||
def test_public_surface_is_call_only
|
||||
# Turn is an orchestrator; the only public verb is #call. Everything
|
||||
# else is internal. Locking this prevents helpers from accidentally
|
||||
# bleeding into the public API.
|
||||
assert_equal [ :call ], Opencode::Turn.instance_methods(false)
|
||||
end
|
||||
|
||||
def test_result_is_a_value_object_with_status_predicates
|
||||
fake_message = Struct.new(:cost, :input_tokens, :output_tokens, keyword_init: true).new(
|
||||
cost: 0.012, input_tokens: 100, output_tokens: 50
|
||||
)
|
||||
result = Opencode::Turn::Result.new(
|
||||
status: :completed, message: fake_message, duration_ms: 1234
|
||||
)
|
||||
|
||||
assert result.completed?
|
||||
refute result.cancelled?
|
||||
refute result.errored?
|
||||
refute result.failed?
|
||||
assert_equal 1234, result.duration_ms
|
||||
assert_equal 0.012, result.cost
|
||||
assert_equal 100, result.input_tokens
|
||||
assert_equal 50, result.output_tokens
|
||||
end
|
||||
end
|
||||
55
test/opencode/uploaded_files_prompt_test.rb
Normal file
55
test/opencode/uploaded_files_prompt_test.rb
Normal file
@@ -0,0 +1,55 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
require "fileutils"
|
||||
require "tmpdir"
|
||||
|
||||
# Smoke test for Opencode::UploadedFilesPrompt. The sandbox_path:
|
||||
# inversion (per D14 in the design doc) means we can exercise the
|
||||
# happy path against a tmpdir without needing Rails / AR / ActiveStorage
|
||||
# fixtures. Behavioral tests covering ActiveStorage attached_blob
|
||||
# enumeration live in the host application.
|
||||
class Opencode::UploadedFilesPromptTest < Minitest::Test
|
||||
# Minimal stub for a user message: #content (the raw user text) and
|
||||
# #files (an ActiveStorage-like collection with #attached?). Real
|
||||
# behavior is exercised in the host's test suite.
|
||||
FakeMessage = Struct.new(:content, :files, keyword_init: true)
|
||||
EmptyFiles = Struct.new(:attached) do
|
||||
def attached? = attached
|
||||
end
|
||||
|
||||
def setup
|
||||
@tmpdir = Dir.mktmpdir("opencode-rails-uploaded-prompt-test-")
|
||||
end
|
||||
|
||||
def teardown
|
||||
FileUtils.remove_entry(@tmpdir) if @tmpdir && File.exist?(@tmpdir)
|
||||
end
|
||||
|
||||
def test_initialize_takes_three_required_keywords
|
||||
params = Opencode::UploadedFilesPrompt.instance_method(:initialize).parameters
|
||||
required = params.select { |kind, _| kind == :keyreq }.map(&:last).sort
|
||||
|
||||
assert_equal %i[sandbox_name_for sandbox_path user_message], required,
|
||||
"UploadedFilesPrompt requires user_message:, sandbox_path:, sandbox_name_for:"
|
||||
end
|
||||
|
||||
def test_text_returns_raw_content_when_no_files_attached
|
||||
message = FakeMessage.new(content: "hello world", files: EmptyFiles.new(false))
|
||||
prompt = Opencode::UploadedFilesPrompt.new(
|
||||
user_message: message,
|
||||
sandbox_path: @tmpdir,
|
||||
sandbox_name_for: ->(file) { file.filename.to_s }
|
||||
)
|
||||
|
||||
assert_equal "hello world", prompt.text,
|
||||
"No attached files => text is the raw user content"
|
||||
assert_equal({}, prompt.sandbox_file_names,
|
||||
"No attached files => sandbox_file_names map stays empty")
|
||||
end
|
||||
|
||||
def test_public_surface
|
||||
assert_equal %i[sandbox_file_names text].sort,
|
||||
Opencode::UploadedFilesPrompt.instance_methods(false).sort
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user