Skip to content

Commit 4da72d6

Browse files
committed
feat(quotes): validate all overrides attributes
1 parent 75754a6 commit 4da72d6

4 files changed

Lines changed: 170 additions & 16 deletions

File tree

app/services/quote_versions/validators/one_off/business_validator.rb

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,18 @@ def validate_add_on_existence(add_on, index)
5454
end
5555

5656
def validate_datetimes(add_on, index)
57-
from = add_on.dig("payload", "from_datetime")
58-
to = add_on.dig("payload", "to_datetime")
59-
return if from.nil? && to.nil?
60-
61-
if from.nil?
62-
add_error(field: add_on_field(index, "payload.from_datetime"), error_code: "value_is_mandatory")
63-
elsif to.nil?
64-
add_error(field: add_on_field(index, "payload.to_datetime"), error_code: "value_is_mandatory")
65-
elsif Time.zone.parse(from) > Time.zone.parse(to)
66-
add_error(field: add_on_field(index, "payload.from_datetime"), error_code: "invalid_date_range")
57+
%w[payload overrides].each do |section|
58+
from = add_on.dig(section, "from_datetime")
59+
to = add_on.dig(section, "to_datetime")
60+
next if from.nil? && to.nil?
61+
62+
if from.nil?
63+
add_error(field: add_on_field(index, "#{section}.from_datetime"), error_code: "value_is_mandatory")
64+
elsif to.nil?
65+
add_error(field: add_on_field(index, "#{section}.to_datetime"), error_code: "value_is_mandatory")
66+
elsif Time.zone.parse(from) > Time.zone.parse(to)
67+
add_error(field: add_on_field(index, "#{section}.from_datetime"), error_code: "invalid_date_range")
68+
end
6769
end
6870
end
6971

app/services/quote_versions/validators/one_off/schema.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ module Schema
7474
"additionalProperties" => {"not" => {}, "x-error" => "unsupported_key"},
7575
"x-error" => {"type" => "invalid_type"},
7676
"properties" => {
77+
"description" => {
78+
"type" => "string",
79+
"minLength" => 1,
80+
"x-error" => {"type" => "invalid_type", "minLength" => "invalid_value"}
81+
},
82+
"units" => {
83+
"type" => "number",
84+
"exclusiveMinimum" => 0,
85+
"x-error" => {"type" => "invalid_type", "exclusiveMinimum" => "invalid_value"}
86+
},
7787
"unit_amount_cents" => {
7888
"type" => "integer",
7989
"minimum" => 0,
@@ -83,6 +93,21 @@ module Schema
8393
"type" => "integer",
8494
"minimum" => 0,
8595
"x-error" => {"type" => "invalid_type", "minimum" => "invalid_value"}
96+
},
97+
"invoice_display_name" => {
98+
"type" => "string",
99+
"minLength" => 1,
100+
"x-error" => {"type" => "invalid_type", "minLength" => "invalid_value"}
101+
},
102+
"from_datetime" => {
103+
"type" => %w[string null],
104+
"format" => "date-time",
105+
"x-error" => {"type" => "invalid_type", "format" => "invalid_format"}
106+
},
107+
"to_datetime" => {
108+
"type" => %w[string null],
109+
"format" => "date-time",
110+
"x-error" => {"type" => "invalid_type", "format" => "invalid_format"}
86111
}
87112
}
88113
}

spec/services/quote_versions/validators/one_off/business_validator_spec.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,45 @@
124124
end
125125
end
126126

127+
context "when only overrides from_datetime is present" do
128+
let(:add_on_item) { super().merge("overrides" => {"from_datetime" => "2026-01-01T00:00:00Z"}) }
129+
130+
it "requires overrides to_datetime" do
131+
expect(validator).not_to be_valid
132+
expect(result.error.messages).to eq({"billing_items.add_ons.0.overrides.to_datetime": ["value_is_mandatory"]})
133+
end
134+
end
135+
136+
context "when only overrides to_datetime is present" do
137+
let(:add_on_item) { super().merge("overrides" => {"to_datetime" => "2026-02-01T00:00:00Z"}) }
138+
139+
it "requires overrides from_datetime" do
140+
expect(validator).not_to be_valid
141+
expect(result.error.messages).to eq({"billing_items.add_ons.0.overrides.from_datetime": ["value_is_mandatory"]})
142+
end
143+
end
144+
145+
context "when overrides from_datetime is after to_datetime" do
146+
let(:add_on_item) do
147+
super().merge("overrides" => {"from_datetime" => "2026-02-01T00:00:00Z", "to_datetime" => "2026-01-01T00:00:00Z"})
148+
end
149+
150+
it "returns an invalid_date_range error" do
151+
expect(validator).not_to be_valid
152+
expect(result.error.messages).to eq({"billing_items.add_ons.0.overrides.from_datetime": ["invalid_date_range"]})
153+
end
154+
end
155+
156+
context "when overrides from_datetime equals to_datetime" do
157+
let(:add_on_item) do
158+
super().merge("overrides" => {"from_datetime" => "2026-01-01T00:00:00Z", "to_datetime" => "2026-01-01T00:00:00Z"})
159+
end
160+
161+
it "is valid" do
162+
expect(validator).to be_valid
163+
end
164+
end
165+
127166
context "with errors on multiple add_ons" do
128167
let(:billing_items) do
129168
{

spec/services/quote_versions/validators/one_off/structural_validator_spec.rb

Lines changed: 94 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,18 @@
1212
"id" => "48e59220-6722-49c1-8cdf-eacd040e2a56",
1313
"local_id" => "3d08b2df-4e4c-4d58-b415-a525c1663735",
1414
"payload" => payload,
15-
"overrides" => {
16-
"unit_amount_cents" => 12_000,
17-
"total_amount_cents" => 12_000
18-
}
15+
"overrides" => overrides
16+
}
17+
end
18+
let(:overrides) do
19+
{
20+
"description" => "Setup fee",
21+
"units" => 2,
22+
"unit_amount_cents" => 12_000,
23+
"total_amount_cents" => 12_000,
24+
"invoice_display_name" => "Custom setup",
25+
"from_datetime" => "2026-01-05T00:00:00Z",
26+
"to_datetime" => "2026-01-20T00:00:00Z"
1927
}
2028
end
2129
let(:payload) do
@@ -85,11 +93,91 @@
8593
end
8694

8795
context "when overrides contains an unknown key" do
88-
let(:add_on_item) { super().merge("overrides" => {"units" => 2}) }
96+
let(:overrides) { super().merge("tax_codes" => ["vat_20"]) }
8997

9098
it "returns an unsupported_key error" do
9199
expect(validator).not_to be_valid
92-
expect(result.error.messages).to eq({"billing_items.add_ons.0.overrides.units": ["unsupported_key"]})
100+
expect(result.error.messages).to eq({"billing_items.add_ons.0.overrides.tax_codes": ["unsupported_key"]})
101+
end
102+
end
103+
104+
context "when overrides description is empty" do
105+
let(:overrides) { super().merge("description" => "") }
106+
107+
it "returns an invalid_value error" do
108+
expect(validator).not_to be_valid
109+
expect(result.error.messages).to eq({"billing_items.add_ons.0.overrides.description": ["invalid_value"]})
110+
end
111+
end
112+
113+
context "when overrides description is null" do
114+
let(:overrides) { super().merge("description" => nil) }
115+
116+
it "returns an invalid_type error" do
117+
expect(validator).not_to be_valid
118+
expect(result.error.messages).to eq({"billing_items.add_ons.0.overrides.description": ["invalid_type"]})
119+
end
120+
end
121+
122+
context "when overrides units is zero" do
123+
let(:overrides) { super().merge("units" => 0) }
124+
125+
it "returns an invalid_value error" do
126+
expect(validator).not_to be_valid
127+
expect(result.error.messages).to eq({"billing_items.add_ons.0.overrides.units": ["invalid_value"]})
128+
end
129+
end
130+
131+
context "when overrides units is a string" do
132+
let(:overrides) { super().merge("units" => "2") }
133+
134+
it "returns an invalid_type error" do
135+
expect(validator).not_to be_valid
136+
expect(result.error.messages).to eq({"billing_items.add_ons.0.overrides.units": ["invalid_type"]})
137+
end
138+
end
139+
140+
context "when overrides invoice_display_name is empty" do
141+
let(:overrides) { super().merge("invoice_display_name" => "") }
142+
143+
it "returns an invalid_value error" do
144+
expect(validator).not_to be_valid
145+
expect(result.error.messages).to eq({"billing_items.add_ons.0.overrides.invoice_display_name": ["invalid_value"]})
146+
end
147+
end
148+
149+
context "when overrides invoice_display_name is null" do
150+
let(:overrides) { super().merge("invoice_display_name" => nil) }
151+
152+
it "returns an invalid_type error" do
153+
expect(validator).not_to be_valid
154+
expect(result.error.messages).to eq({"billing_items.add_ons.0.overrides.invoice_display_name": ["invalid_type"]})
155+
end
156+
end
157+
158+
context "when overrides from_datetime is not an ISO 8601 date-time" do
159+
let(:overrides) { super().merge("from_datetime" => "not-a-date") }
160+
161+
it "returns an invalid_format error" do
162+
expect(validator).not_to be_valid
163+
expect(result.error.messages).to eq({"billing_items.add_ons.0.overrides.from_datetime": ["invalid_format"]})
164+
end
165+
end
166+
167+
context "when overrides to_datetime has a wrong type" do
168+
let(:overrides) { super().merge("to_datetime" => 123) }
169+
170+
it "returns an invalid_type error" do
171+
expect(validator).not_to be_valid
172+
expect(result.error.messages).to eq({"billing_items.add_ons.0.overrides.to_datetime": ["invalid_type"]})
173+
end
174+
end
175+
176+
context "when overrides datetimes are null" do
177+
let(:overrides) { super().merge("from_datetime" => nil, "to_datetime" => nil) }
178+
179+
it "is valid" do
180+
expect(validator).to be_valid
93181
end
94182
end
95183

0 commit comments

Comments
 (0)