Skip to content

Commit 19ca8b7

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 - Params class reuse across multiple operations - Contract method with custom validation rules - Params class inheritance from parent classes
1 parent 729ebe1 commit 19ca8b7

File tree

1 file changed

+306
-0
lines changed

1 file changed

+306
-0
lines changed
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
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 params classes" do
186+
it "accepts a pre-defined params class" do
187+
user_params = Class.new(Dry::Operation::Extensions::Params::Params) do
188+
params do
189+
required(:name).filled(:string)
190+
required(:email).filled(:string)
191+
optional(:age).maybe(:integer)
192+
end
193+
end
194+
195+
create_user = Class.new(Dry::Operation) do
196+
include Dry::Operation::Extensions::Params
197+
198+
params user_params
199+
200+
def call(input)
201+
input
202+
end
203+
end
204+
205+
result = create_user.new.call(name: "Alice", email: "[email protected]")
206+
expect(result).to be_success
207+
expect(result.value!).to include(name: "Alice", email: "[email protected]")
208+
209+
result = create_user.new.call(name: "", email: "invalid")
210+
expect(result).to be_failure
211+
expect(result.failure.first).to eq(:invalid_params)
212+
end
213+
214+
it "allows params class reuse across multiple operations" do
215+
shared_params = Class.new(Dry::Operation::Extensions::Params::Params) do
216+
params do
217+
required(:user_id).filled(:integer)
218+
required(:action).filled(:string)
219+
end
220+
end
221+
222+
audit_operation = Class.new(Dry::Operation) do
223+
include Dry::Operation::Extensions::Params
224+
225+
params shared_params
226+
227+
def call(input)
228+
"Audited: #{input[:action]} by user #{input[:user_id]}"
229+
end
230+
end
231+
232+
log_operation = Class.new(Dry::Operation) do
233+
include Dry::Operation::Extensions::Params
234+
235+
params shared_params
236+
237+
def call(input)
238+
"Logged: #{input[:action]} by user #{input[:user_id]}"
239+
end
240+
end
241+
242+
result = audit_operation.new.call(user_id: 1, action: "login")
243+
expect(result).to be_success
244+
expect(result.value!).to eq("Audited: login by user 1")
245+
246+
result = log_operation.new.call(user_id: 2, action: "logout")
247+
expect(result).to be_success
248+
expect(result.value!).to eq("Logged: logout by user 2")
249+
end
250+
end
251+
252+
describe "with contract" do
253+
it "validates with custom rules" do
254+
create_user = Class.new(Dry::Operation) do
255+
include Dry::Operation::Extensions::Params
256+
257+
contract do
258+
params do
259+
required(:name).filled(:string)
260+
required(:age).filled(:integer)
261+
end
262+
263+
rule(:age) do
264+
key.failure("must be 18 or older") if value < 18
265+
end
266+
end
267+
268+
def call(input)
269+
input
270+
end
271+
end
272+
273+
result = create_user.new.call(name: "Alice", age: 25)
274+
expect(result).to be_success
275+
276+
result = create_user.new.call(name: "Bob", age: 16)
277+
expect(result).to be_failure
278+
expect(result.failure).to eq([:invalid_params, {age: ["must be 18 or older"]}])
279+
end
280+
end
281+
282+
describe "inheritance" do
283+
it "inherits params class from parent" do
284+
base_operation = Class.new(Dry::Operation) do
285+
include Dry::Operation::Extensions::Params
286+
287+
params do
288+
required(:name).filled(:string)
289+
end
290+
end
291+
292+
child_operation = Class.new(base_operation) do
293+
def call(input)
294+
"Hello, #{input[:name]}!"
295+
end
296+
end
297+
298+
result = child_operation.new.call(name: "Alice")
299+
expect(result).to be_success
300+
expect(result.value!).to eq("Hello, Alice!")
301+
302+
result = child_operation.new.call(name: "")
303+
expect(result).to be_failure
304+
end
305+
end
306+
end

0 commit comments

Comments
 (0)