Skip to content

Commit 1ad6be1

Browse files
committed
Add integration tests for Params extension
Adds comprehensive integration tests demonstrating real-world usage: - Validating operation inputs with success and failure scenarios - Value coercion according to schema types - Nested structure validation with detailed error reporting - Custom wrapped methods via operate_on - Schema inheritance from parent classes
1 parent e87aac7 commit 1ad6be1

File tree

1 file changed

+272
-0
lines changed

1 file changed

+272
-0
lines changed
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
# frozen_string_literal: true
2+
3+
require "spec_helper"
4+
5+
RSpec.describe Dry::Operation::Extensions::Params do
6+
include Dry::Monads[:result]
7+
8+
describe "validating operation inputs" do
9+
it "validates params and allows operation to proceed on success" do
10+
create_user = Class.new(Dry::Operation) do
11+
include Dry::Operation::Extensions::Params
12+
13+
params do
14+
required(:name).filled(:string)
15+
required(:email).filled(:string)
16+
optional(:age).maybe(:integer)
17+
end
18+
19+
def call(input)
20+
user = step create_user_record(input)
21+
step send_welcome_email(user)
22+
user
23+
end
24+
25+
private
26+
27+
def create_user_record(attrs)
28+
Success(attrs.merge(id: 1))
29+
end
30+
31+
def send_welcome_email(_user)
32+
Success(true)
33+
end
34+
end
35+
36+
result = create_user.new.call(name: "John Doe", email: "[email protected]", age: 25)
37+
38+
expect(result).to be_success
39+
expect(result.value!).to eq(id: 1, name: "John Doe", email: "[email protected]", age: 25)
40+
end
41+
42+
it "returns validation failure before executing operation logic" do
43+
executed_steps = []
44+
45+
create_user = Class.new(Dry::Operation) do
46+
include Dry::Operation::Extensions::Params
47+
48+
params do
49+
required(:name).filled(:string)
50+
required(:email).filled(:string)
51+
end
52+
53+
define_method(:call) do |input|
54+
executed_steps << :call_started
55+
user = step create_user_record(input)
56+
executed_steps << :user_created
57+
user
58+
end
59+
60+
define_method(:create_user_record) do |attrs|
61+
executed_steps << :create_user_record
62+
Success(attrs.merge(id: 1))
63+
end
64+
end
65+
66+
result = create_user.new.call(name: "", email: "invalid")
67+
68+
expect(result).to be_failure
69+
expect(result.failure).to eq([:invalid_params, {name: ["must be filled"]}])
70+
expect(executed_steps).to be_empty
71+
end
72+
73+
it "coerces input values according to schema" do
74+
calculate = Class.new(Dry::Operation) do
75+
include Dry::Operation::Extensions::Params
76+
77+
params do
78+
required(:x).value(:integer)
79+
required(:y).value(:integer)
80+
end
81+
82+
def call(input)
83+
input[:x] + input[:y]
84+
end
85+
end
86+
87+
result = calculate.new.call(x: "10", y: "20")
88+
89+
expect(result).to be_success
90+
expect(result.value!).to eq(30)
91+
end
92+
end
93+
94+
describe "with nested schemas" do
95+
it "validates nested structures" do
96+
create_order = Class.new(Dry::Operation) do
97+
include Dry::Operation::Extensions::Params
98+
99+
params do
100+
required(:customer).hash do
101+
required(:name).filled(:string)
102+
required(:email).filled(:string)
103+
end
104+
required(:items).array(:hash) do
105+
required(:product_id).filled(:integer)
106+
required(:quantity).filled(:integer)
107+
end
108+
end
109+
110+
def call(input)
111+
input
112+
end
113+
end
114+
115+
result = create_order.new.call(
116+
customer: {name: "John", email: "[email protected]"},
117+
items: [
118+
{product_id: 1, quantity: 2},
119+
{product_id: 2, quantity: 1}
120+
]
121+
)
122+
123+
expect(result).to be_success
124+
end
125+
126+
it "returns detailed validation errors for nested structures" do
127+
create_order = Class.new(Dry::Operation) do
128+
include Dry::Operation::Extensions::Params
129+
130+
params do
131+
required(:customer).hash do
132+
required(:name).filled(:string)
133+
required(:email).filled(:string)
134+
end
135+
end
136+
137+
def call(input)
138+
input
139+
end
140+
end
141+
142+
result = create_order.new.call(customer: {name: "", email: ""})
143+
144+
expect(result).to be_failure
145+
failure_type, errors = result.failure
146+
expect(failure_type).to eq(:invalid_params)
147+
expect(errors[:customer]).to include(:name, :email)
148+
end
149+
end
150+
151+
describe "with custom methods via operate_on" do
152+
it "validates params for custom wrapped methods" do
153+
processor = Class.new(Dry::Operation) do
154+
include Dry::Operation::Extensions::Params
155+
156+
operate_on :process, :transform
157+
158+
params do
159+
required(:value).filled(:string)
160+
end
161+
162+
def process(input)
163+
input[:value].upcase
164+
end
165+
166+
def transform(input)
167+
input[:value].downcase
168+
end
169+
end
170+
171+
instance = processor.new
172+
173+
result = instance.process(value: "hello")
174+
expect(result).to eq(Success("HELLO"))
175+
176+
result = instance.transform(value: "WORLD")
177+
expect(result).to eq(Success("world"))
178+
179+
result = instance.process(value: "")
180+
expect(result).to be_failure
181+
expect(result.failure).to eq([:invalid_params, {value: ["must be filled"]}])
182+
end
183+
end
184+
185+
describe "with schema classes" do
186+
it "accepts a pre-defined schema class" do
187+
user_schema = Dry::Schema.Params do
188+
required(:name).filled(:string)
189+
required(:email).filled(:string)
190+
optional(:age).maybe(:integer)
191+
end
192+
193+
create_user = Class.new(Dry::Operation) do
194+
include Dry::Operation::Extensions::Params
195+
196+
params user_schema
197+
198+
def call(input)
199+
input
200+
end
201+
end
202+
203+
result = create_user.new.call(name: "Alice", email: "[email protected]")
204+
expect(result).to be_success
205+
expect(result.value!).to include(name: "Alice", email: "[email protected]")
206+
207+
result = create_user.new.call(name: "", email: "invalid")
208+
expect(result).to be_failure
209+
expect(result.failure.first).to eq(:invalid_params)
210+
end
211+
212+
it "allows schema reuse across multiple operations" do
213+
shared_schema = Dry::Schema.Params do
214+
required(:user_id).filled(:integer)
215+
required(:action).filled(:string)
216+
end
217+
218+
audit_operation = Class.new(Dry::Operation) do
219+
include Dry::Operation::Extensions::Params
220+
221+
params shared_schema
222+
223+
def call(input)
224+
"Audited: #{input[:action]} by user #{input[:user_id]}"
225+
end
226+
end
227+
228+
log_operation = Class.new(Dry::Operation) do
229+
include Dry::Operation::Extensions::Params
230+
231+
params shared_schema
232+
233+
def call(input)
234+
"Logged: #{input[:action]} by user #{input[:user_id]}"
235+
end
236+
end
237+
238+
result = audit_operation.new.call(user_id: 1, action: "login")
239+
expect(result).to be_success
240+
expect(result.value!).to eq("Audited: login by user 1")
241+
242+
result = log_operation.new.call(user_id: 2, action: "logout")
243+
expect(result).to be_success
244+
expect(result.value!).to eq("Logged: logout by user 2")
245+
end
246+
end
247+
248+
describe "inheritance" do
249+
it "inherits params schema from parent class" do
250+
base_operation = Class.new(Dry::Operation) do
251+
include Dry::Operation::Extensions::Params
252+
253+
params do
254+
required(:name).filled(:string)
255+
end
256+
end
257+
258+
child_operation = Class.new(base_operation) do
259+
def call(input)
260+
"Hello, #{input[:name]}!"
261+
end
262+
end
263+
264+
result = child_operation.new.call(name: "Alice")
265+
expect(result).to be_success
266+
expect(result.value!).to eq("Hello, Alice!")
267+
268+
result = child_operation.new.call(name: "")
269+
expect(result).to be_failure
270+
end
271+
end
272+
end

0 commit comments

Comments
 (0)