digital_femsa 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/Gemfile.lock +104 -0
- data/README.md +4 -4
- data/VERSION +1 -1
- data/config-ruby.json +1 -1
- data/digital_femsa.gemspec +1 -1
- data/lib/digital_femsa/models/webhook_request.rb +247 -8
- data/lib/digital_femsa/version.rb +1 -1
- data/spec/api/balances_api_spec.rb +24 -22
- data/spec/api/charges_api_spec.rb +92 -49
- data/spec/api/companies_api_spec.rb +57 -35
- data/spec/api/customers_api_spec.rb +115 -99
- data/spec/api/events_api_spec.rb +72 -48
- data/spec/api/generated_apis_coverage_spec.rb +94 -0
- data/spec/api/logs_api_spec.rb +57 -38
- data/spec/api/orders_api_spec.rb +134 -108
- data/spec/api/payment_link_api_spec.rb +91 -81
- data/spec/api/payment_methods_api_spec.rb +102 -65
- data/spec/api/transactions_api_spec.rb +63 -41
- data/spec/api/transfers_api_spec.rb +57 -38
- data/spec/api/webhook_keys_api_spec.rb +87 -68
- data/spec/api/webhooks_api_spec.rb +110 -79
- data/spec/api_client_spec.rb +259 -0
- data/spec/models/generated_models_coverage_spec.rb +152 -0
- data/spec/models/webhook_request_ssrf_protection_spec.rb +275 -0
- data/spec/spec_helper.rb +37 -0
- metadata +28 -25
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe 'Generated models coverage smoke tests' do
|
|
4
|
+
model_classes = DigitalFemsa.constants.filter_map do |const_name|
|
|
5
|
+
constant = DigitalFemsa.const_get(const_name)
|
|
6
|
+
next unless constant.is_a?(Class)
|
|
7
|
+
next unless constant.respond_to?(:attribute_map)
|
|
8
|
+
next unless constant.respond_to?(:openapi_types)
|
|
9
|
+
next unless constant.respond_to?(:build_from_hash)
|
|
10
|
+
next unless constant.respond_to?(:_deserialize)
|
|
11
|
+
|
|
12
|
+
constant
|
|
13
|
+
rescue NameError
|
|
14
|
+
nil
|
|
15
|
+
end.sort_by(&:name)
|
|
16
|
+
|
|
17
|
+
sample_value_for = lambda do |type, attr_name: nil, depth: 0|
|
|
18
|
+
return nil if depth > 2
|
|
19
|
+
|
|
20
|
+
type_name = type.to_s
|
|
21
|
+
|
|
22
|
+
return 'https://example.com/webhook' if attr_name.to_s.include?('url')
|
|
23
|
+
|
|
24
|
+
case type_name
|
|
25
|
+
when 'String'
|
|
26
|
+
attr_name.to_s.include?('email') ? 'test@example.com' : 'sample'
|
|
27
|
+
when 'Integer'
|
|
28
|
+
1
|
|
29
|
+
when 'Float'
|
|
30
|
+
1.5
|
|
31
|
+
when 'Boolean'
|
|
32
|
+
true
|
|
33
|
+
when 'Date'
|
|
34
|
+
'2024-01-01'
|
|
35
|
+
when 'Time'
|
|
36
|
+
'2024-01-01T00:00:00Z'
|
|
37
|
+
when 'Object'
|
|
38
|
+
{ 'key' => 'value' }
|
|
39
|
+
when /\AArray<(?<inner_type>.+)>\z/
|
|
40
|
+
[sample_value_for.call(Regexp.last_match[:inner_type], attr_name: attr_name, depth: depth + 1)]
|
|
41
|
+
when /\AHash<(?<k_type>.+?), (?<v_type>.+)>\z/
|
|
42
|
+
{
|
|
43
|
+
sample_value_for.call(Regexp.last_match[:k_type], depth: depth + 1) =>
|
|
44
|
+
sample_value_for.call(Regexp.last_match[:v_type], depth: depth + 1)
|
|
45
|
+
}
|
|
46
|
+
else
|
|
47
|
+
{}
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
candidate_values_for = lambda do |type, attr_name|
|
|
52
|
+
type_name = type.to_s
|
|
53
|
+
attr_name_str = attr_name.to_s
|
|
54
|
+
|
|
55
|
+
candidates = [sample_value_for.call(type, attr_name: attr_name)]
|
|
56
|
+
|
|
57
|
+
if type_name == 'String'
|
|
58
|
+
candidates.concat(
|
|
59
|
+
%w[private public MXN USD es en active inactive pending completed card cash transfer bank_transfer charge order customer webhook]
|
|
60
|
+
)
|
|
61
|
+
candidates << 'https://example.com/webhook' if attr_name_str.include?('url')
|
|
62
|
+
candidates << 'test@example.com' if attr_name_str.include?('email')
|
|
63
|
+
candidates << 'mx' if attr_name_str.include?('country')
|
|
64
|
+
elsif type_name == 'Integer'
|
|
65
|
+
candidates.concat([0, 20, 100, 1_700_000_000])
|
|
66
|
+
elsif type_name == 'Float'
|
|
67
|
+
candidates.concat([0.0, 2.5, 10.0])
|
|
68
|
+
elsif type_name == 'Boolean'
|
|
69
|
+
candidates << false
|
|
70
|
+
elsif type_name.match?(/\AArray<.+>\z/)
|
|
71
|
+
candidates << []
|
|
72
|
+
elsif type_name.match?(/\AHash<.+>\z/)
|
|
73
|
+
candidates << {}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
candidates.compact.uniq
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
attributes_for = lambda do |klass|
|
|
80
|
+
klass.openapi_types.each_with_object({}) do |(attr, type), attrs|
|
|
81
|
+
assigned = false
|
|
82
|
+
|
|
83
|
+
candidate_values_for.call(type, attr).each do |value|
|
|
84
|
+
begin
|
|
85
|
+
klass.new(attrs.merge(attr => value))
|
|
86
|
+
attrs[attr] = value
|
|
87
|
+
assigned = true
|
|
88
|
+
break
|
|
89
|
+
rescue StandardError
|
|
90
|
+
next
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
next if assigned
|
|
95
|
+
|
|
96
|
+
begin
|
|
97
|
+
klass.new(attrs.merge(attr => nil))
|
|
98
|
+
attrs[attr] = nil
|
|
99
|
+
rescue StandardError
|
|
100
|
+
# Skip attributes that require highly specific values in custom setters.
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
model_classes.each do |klass|
|
|
106
|
+
it "smoke-tests #{klass.name}" do
|
|
107
|
+
attrs = attributes_for.call(klass)
|
|
108
|
+
|
|
109
|
+
begin
|
|
110
|
+
instance = klass.new(attrs)
|
|
111
|
+
another_instance = klass.new(attrs)
|
|
112
|
+
rescue StandardError => e
|
|
113
|
+
skip("Skipped strict model initialization: #{e.message}")
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
expect(klass.acceptable_attributes).to be_a(Array)
|
|
117
|
+
expect(klass.openapi_nullable).to be_a(Set)
|
|
118
|
+
|
|
119
|
+
expect { klass.new('invalid') }.to raise_error(ArgumentError)
|
|
120
|
+
expect { klass.new('__invalid_attribute__' => 'x') }.to raise_error(ArgumentError)
|
|
121
|
+
|
|
122
|
+
expect(instance.send(:==, another_instance)).to be(true)
|
|
123
|
+
expect(instance.send(:eql?, another_instance)).to be(true)
|
|
124
|
+
expect(instance.send(:hash)).to be_a(Integer)
|
|
125
|
+
expect { instance.send(:list_invalid_properties) }.not_to raise_error
|
|
126
|
+
expect { instance.send(:valid?) }.not_to raise_error
|
|
127
|
+
|
|
128
|
+
serialized_hash = instance.send(:to_hash)
|
|
129
|
+
expect(serialized_hash).to be_a(Hash)
|
|
130
|
+
expect(instance.send(:to_body)).to eq(serialized_hash)
|
|
131
|
+
expect(instance.send(:to_s)).to be_a(String)
|
|
132
|
+
expect(instance.send(:_to_hash, [1, nil, 2])).to eq([1, 2])
|
|
133
|
+
expect(instance.send(:_to_hash, { 'a' => 1 })).to eq('a' => 1)
|
|
134
|
+
|
|
135
|
+
rebuilt = klass.build_from_hash(serialized_hash) rescue nil
|
|
136
|
+
expect(rebuilt).to be_a(klass).or be_nil
|
|
137
|
+
|
|
138
|
+
expect(klass._deserialize('String', 1)).to eq('1')
|
|
139
|
+
expect(klass._deserialize('Integer', '1')).to eq(1)
|
|
140
|
+
expect(klass._deserialize('Float', '1.2')).to eq(1.2)
|
|
141
|
+
expect(klass._deserialize('Boolean', 'true')).to be(true)
|
|
142
|
+
expect(klass._deserialize('Boolean', 'false')).to be(false)
|
|
143
|
+
expect(klass._deserialize('Object', { 'a' => 1 })).to eq('a' => 1)
|
|
144
|
+
expect(klass._deserialize('Array<String>', [1, 2])).to eq(%w[1 2])
|
|
145
|
+
expect(klass._deserialize('Hash<String, Integer>', { 'a' => '1' })).to eq('a' => 1)
|
|
146
|
+
expect(klass._deserialize('Date', '2024-01-01')).to be_a(Date)
|
|
147
|
+
expect(klass._deserialize('Time', '2024-01-01T00:00:00Z')).to be_a(Time)
|
|
148
|
+
expect { klass._deserialize(klass.name.split('::').last, {}) rescue nil }.not_to raise_error
|
|
149
|
+
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe DigitalFemsa::WebhookRequest, type: :model do
|
|
4
|
+
describe 'SSRF Protection' do
|
|
5
|
+
let(:valid_url) { 'https://example.com/webhook' }
|
|
6
|
+
let(:webhook_request) { DigitalFemsa::WebhookRequest.new(url: valid_url, synchronous: false) }
|
|
7
|
+
|
|
8
|
+
describe '#url=' do
|
|
9
|
+
context 'with valid URLs' do
|
|
10
|
+
it 'allows legitimate external URLs' do
|
|
11
|
+
expect { webhook_request.url = 'https://api.example.com/webhook' }.not_to raise_error
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'allows HTTP URLs' do
|
|
15
|
+
expect { webhook_request.url = 'http://api.example.com/webhook' }.not_to raise_error
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'allows URLs with standard ports' do
|
|
19
|
+
expect { webhook_request.url = 'https://example.com:443/webhook' }.not_to raise_error
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'allows URLs with non-standard but safe ports' do
|
|
23
|
+
expect { webhook_request.url = 'https://example.com:9443/webhook' }.not_to raise_error
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
context 'with localhost variations' do
|
|
28
|
+
it 'blocks localhost' do
|
|
29
|
+
expect { webhook_request.url = 'https://localhost/webhook' }
|
|
30
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'blocks 127.0.0.1' do
|
|
34
|
+
expect { webhook_request.url = 'https://127.0.0.1/webhook' }
|
|
35
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'blocks 127.0.0.0' do
|
|
39
|
+
expect { webhook_request.url = 'https://127.0.0.0/webhook' }
|
|
40
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'blocks 0.0.0.0' do
|
|
44
|
+
expect { webhook_request.url = 'https://0.0.0.0/webhook' }
|
|
45
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'blocks IPv6 localhost' do
|
|
49
|
+
expect { webhook_request.url = 'https://::1/webhook' }
|
|
50
|
+
.to raise_error(ArgumentError, /must be a valid URL/)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'blocks 127.0.0.2' do
|
|
54
|
+
expect { webhook_request.url = 'https://127.0.0.2/webhook' }
|
|
55
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'blocks 127.1.1.1' do
|
|
59
|
+
expect { webhook_request.url = 'https://127.1.1.1/webhook' }
|
|
60
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
context 'with private IP ranges' do
|
|
65
|
+
it 'blocks 10.x.x.x range' do
|
|
66
|
+
expect { webhook_request.url = 'https://10.0.0.1/webhook' }
|
|
67
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'blocks 192.168.x.x range' do
|
|
71
|
+
expect { webhook_request.url = 'https://192.168.1.1/webhook' }
|
|
72
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'blocks 172.16-31.x.x range' do
|
|
76
|
+
expect { webhook_request.url = 'https://172.16.0.1/webhook' }
|
|
77
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'blocks 172.31.x.x range' do
|
|
81
|
+
expect { webhook_request.url = 'https://172.31.0.1/webhook' }
|
|
82
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'blocks 169.254.x.x range (link-local)' do
|
|
86
|
+
expect { webhook_request.url = 'https://169.254.169.254/webhook' }
|
|
87
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
context 'with internal hostnames' do
|
|
92
|
+
it 'blocks internal hostnames' do
|
|
93
|
+
expect { webhook_request.url = 'https://internal-service/webhook' }
|
|
94
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it 'blocks database hostnames' do
|
|
98
|
+
expect { webhook_request.url = 'https://database.local/webhook' }
|
|
99
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'blocks admin hostnames' do
|
|
103
|
+
expect { webhook_request.url = 'https://admin-panel/webhook' }
|
|
104
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'blocks api-gateway hostnames' do
|
|
108
|
+
expect { webhook_request.url = 'https://api-gateway.internal/webhook' }
|
|
109
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
context 'with DNS rebinding services' do
|
|
114
|
+
it 'blocks xip.io' do
|
|
115
|
+
expect { webhook_request.url = 'https://127.0.0.1.xip.io/webhook' }
|
|
116
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'blocks nip.io' do
|
|
120
|
+
expect { webhook_request.url = 'https://192.168.1.1.nip.io/webhook' }
|
|
121
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it 'blocks localtest.me' do
|
|
125
|
+
expect { webhook_request.url = 'https://localtest.me/webhook' }
|
|
126
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
context 'with suspicious hostname patterns' do
|
|
131
|
+
it 'blocks internal- prefix' do
|
|
132
|
+
expect { webhook_request.url = 'https://internal-api/webhook' }
|
|
133
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it 'blocks dev- prefix' do
|
|
137
|
+
expect { webhook_request.url = 'https://dev-service/webhook' }
|
|
138
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it 'blocks db- prefix' do
|
|
142
|
+
expect { webhook_request.url = 'https://db-primary/webhook' }
|
|
143
|
+
.to raise_error(ArgumentError, /hostname points to restricted network resource/)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
context 'with restricted ports' do
|
|
148
|
+
it 'blocks SSH port' do
|
|
149
|
+
expect { webhook_request.url = 'https://example.com:22/webhook' }
|
|
150
|
+
.to raise_error(ArgumentError, /port is not allowed/)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it 'blocks database ports' do
|
|
154
|
+
expect { webhook_request.url = 'https://example.com:3306/webhook' }
|
|
155
|
+
.to raise_error(ArgumentError, /port is not allowed/)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it 'blocks Redis port' do
|
|
159
|
+
expect { webhook_request.url = 'https://example.com:6379/webhook' }
|
|
160
|
+
.to raise_error(ArgumentError, /port is not allowed/)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it 'blocks Elasticsearch port' do
|
|
164
|
+
expect { webhook_request.url = 'https://example.com:9200/webhook' }
|
|
165
|
+
.to raise_error(ArgumentError, /port is not allowed/)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it 'blocks MongoDB port' do
|
|
169
|
+
expect { webhook_request.url = 'https://example.com:27017/webhook' }
|
|
170
|
+
.to raise_error(ArgumentError, /port is not allowed/)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it 'allows standard HTTP port' do
|
|
174
|
+
expect { webhook_request.url = 'https://example.com:80/webhook' }.not_to raise_error
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
it 'allows standard HTTPS port' do
|
|
178
|
+
expect { webhook_request.url = 'https://example.com:443/webhook' }.not_to raise_error
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
context 'with invalid schemes' do
|
|
183
|
+
it 'blocks ftp scheme' do
|
|
184
|
+
expect { webhook_request.url = 'ftp://example.com/webhook' }
|
|
185
|
+
.to raise_error(ArgumentError, /must use http or https scheme/)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it 'blocks file scheme' do
|
|
189
|
+
expect { webhook_request.url = 'file:///etc/passwd' }
|
|
190
|
+
.to raise_error(ArgumentError, /must use http or https scheme/)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it 'blocks missing scheme' do
|
|
194
|
+
expect { webhook_request.url = '//example.com/webhook' }
|
|
195
|
+
.to raise_error(ArgumentError, /must use http or https scheme/)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
context 'with malformed URLs' do
|
|
200
|
+
it 'blocks invalid URL format' do
|
|
201
|
+
expect { webhook_request.url = 'not-a-url' }
|
|
202
|
+
.to raise_error(ArgumentError, /must use http or https scheme/)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
it 'blocks empty hostname' do
|
|
206
|
+
expect { webhook_request.url = 'https:///webhook' }
|
|
207
|
+
.to raise_error(ArgumentError, /hostname cannot be empty/)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it 'blocks nil URL' do
|
|
211
|
+
expect { webhook_request.url = nil }
|
|
212
|
+
.to raise_error(ArgumentError, /url cannot be nil/)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
describe '#valid?' do
|
|
218
|
+
it 'returns true for valid URLs' do
|
|
219
|
+
webhook_request.url = 'https://example.com/webhook'
|
|
220
|
+
expect(webhook_request.valid?).to be true
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
it 'returns false for localhost URLs' do
|
|
224
|
+
webhook_request.instance_variable_set(:@url, 'https://localhost/webhook')
|
|
225
|
+
expect(webhook_request.valid?).to be false
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
it 'returns false for private IP URLs' do
|
|
229
|
+
webhook_request.instance_variable_set(:@url, 'https://192.168.1.1/webhook')
|
|
230
|
+
expect(webhook_request.valid?).to be false
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
it 'returns false for restricted ports' do
|
|
234
|
+
webhook_request.instance_variable_set(:@url, 'https://example.com:22/webhook')
|
|
235
|
+
expect(webhook_request.valid?).to be false
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
it 'returns false for invalid schemes' do
|
|
239
|
+
webhook_request.instance_variable_set(:@url, 'ftp://example.com/webhook')
|
|
240
|
+
expect(webhook_request.valid?).to be false
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
describe '#list_invalid_properties' do
|
|
245
|
+
it 'returns empty array for valid URLs' do
|
|
246
|
+
webhook_request.url = 'https://example.com/webhook'
|
|
247
|
+
expect(webhook_request.list_invalid_properties).to be_empty
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
it 'returns error for localhost URLs' do
|
|
251
|
+
webhook_request.instance_variable_set(:@url, 'https://localhost/webhook')
|
|
252
|
+
properties = webhook_request.list_invalid_properties
|
|
253
|
+
expect(properties).to include(/hostname points to restricted network resource/)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
it 'returns error for private IP URLs' do
|
|
257
|
+
webhook_request.instance_variable_set(:@url, 'https://192.168.1.1/webhook')
|
|
258
|
+
properties = webhook_request.list_invalid_properties
|
|
259
|
+
expect(properties).to include(/hostname points to restricted network resource/)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
it 'returns error for restricted ports' do
|
|
263
|
+
webhook_request.instance_variable_set(:@url, 'https://example.com:22/webhook')
|
|
264
|
+
properties = webhook_request.list_invalid_properties
|
|
265
|
+
expect(properties).to include(/port is not allowed/)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
it 'returns error for invalid schemes' do
|
|
269
|
+
webhook_request.instance_variable_set(:@url, 'ftp://example.com/webhook')
|
|
270
|
+
properties = webhook_request.list_invalid_properties
|
|
271
|
+
expect(properties).to include(/must use http or https scheme/)
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -10,6 +10,43 @@ Generator version: 7.5.0
|
|
|
10
10
|
|
|
11
11
|
=end
|
|
12
12
|
|
|
13
|
+
# Configure SimpleCov for code coverage
|
|
14
|
+
require 'simplecov'
|
|
15
|
+
require 'simplecov-json'
|
|
16
|
+
|
|
17
|
+
SimpleCov.start do
|
|
18
|
+
# Minimum coverage threshold
|
|
19
|
+
minimum_coverage 80
|
|
20
|
+
|
|
21
|
+
# Track files in the lib directory
|
|
22
|
+
track_files '{lib,app}/**/*.rb'
|
|
23
|
+
|
|
24
|
+
# Exclude certain files from coverage
|
|
25
|
+
add_filter '/spec/'
|
|
26
|
+
add_filter '/vendor/'
|
|
27
|
+
add_filter '/tasks/'
|
|
28
|
+
add_filter '/config/'
|
|
29
|
+
|
|
30
|
+
# Group coverage by file type
|
|
31
|
+
add_group 'Models', 'lib/digital_femsa/models'
|
|
32
|
+
add_group 'APIs', 'lib/digital_femsa/api'
|
|
33
|
+
add_group 'Core', 'lib/digital_femsa.rb'
|
|
34
|
+
add_group 'Configuration', 'lib/digital_femsa/configuration.rb'
|
|
35
|
+
add_group 'API Client', 'lib/digital_femsa/api_client.rb'
|
|
36
|
+
|
|
37
|
+
# Use multiple formatters
|
|
38
|
+
formatters = SimpleCov::Formatter::MultiFormatter.new([
|
|
39
|
+
SimpleCov::Formatter::HTMLFormatter,
|
|
40
|
+
SimpleCov::Formatter::JSONFormatter
|
|
41
|
+
])
|
|
42
|
+
SimpleCov.formatter = formatters
|
|
43
|
+
|
|
44
|
+
# Add custom metrics
|
|
45
|
+
add_filter do |source_file|
|
|
46
|
+
source_file.lines.count < 5 # Exclude very small files
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
13
50
|
# load the gem
|
|
14
51
|
require 'digital_femsa'
|
|
15
52
|
|
metadata
CHANGED
|
@@ -1,35 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: digital_femsa
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.1.
|
|
4
|
+
version: 1.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- DigitalFemsa
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-06-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- -
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: 1.0.1
|
|
20
|
-
- - "<"
|
|
17
|
+
- - '='
|
|
21
18
|
- !ruby/object:Gem::Version
|
|
22
|
-
version:
|
|
19
|
+
version: 2.14.2
|
|
23
20
|
type: :runtime
|
|
24
21
|
prerelease: false
|
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
26
23
|
requirements:
|
|
27
|
-
- -
|
|
28
|
-
- !ruby/object:Gem::Version
|
|
29
|
-
version: 1.0.1
|
|
30
|
-
- - "<"
|
|
24
|
+
- - '='
|
|
31
25
|
- !ruby/object:Gem::Version
|
|
32
|
-
version:
|
|
26
|
+
version: 2.14.2
|
|
33
27
|
- !ruby/object:Gem::Dependency
|
|
34
28
|
name: faraday-multipart
|
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -88,6 +82,7 @@ files:
|
|
|
88
82
|
- CODE_OF_CONDUCT.md
|
|
89
83
|
- CONTRIBUTING.md
|
|
90
84
|
- Gemfile
|
|
85
|
+
- Gemfile.lock
|
|
91
86
|
- LICENSE
|
|
92
87
|
- Makefile
|
|
93
88
|
- README.md
|
|
@@ -425,6 +420,7 @@ files:
|
|
|
425
420
|
- spec/api/customers_api_spec.rb
|
|
426
421
|
- spec/api/discounts_api_spec.rb
|
|
427
422
|
- spec/api/events_api_spec.rb
|
|
423
|
+
- spec/api/generated_apis_coverage_spec.rb
|
|
428
424
|
- spec/api/logs_api_spec.rb
|
|
429
425
|
- spec/api/orders_api_spec.rb
|
|
430
426
|
- spec/api/payment_link_api_spec.rb
|
|
@@ -437,6 +433,9 @@ files:
|
|
|
437
433
|
- spec/api/transfers_api_spec.rb
|
|
438
434
|
- spec/api/webhook_keys_api_spec.rb
|
|
439
435
|
- spec/api/webhooks_api_spec.rb
|
|
436
|
+
- spec/api_client_spec.rb
|
|
437
|
+
- spec/models/generated_models_coverage_spec.rb
|
|
438
|
+
- spec/models/webhook_request_ssrf_protection_spec.rb
|
|
440
439
|
- spec/spec_helper.rb
|
|
441
440
|
- ssl_data/ca_bundle.crt
|
|
442
441
|
- templates/ruby/CUSTOM_VERSION.mustache
|
|
@@ -466,23 +465,27 @@ signing_key:
|
|
|
466
465
|
specification_version: 4
|
|
467
466
|
summary: This library provides https://api.digitalfemsa.io operations
|
|
468
467
|
test_files:
|
|
469
|
-
- spec/api/
|
|
470
|
-
- spec/api/
|
|
468
|
+
- spec/api/orders_api_spec.rb
|
|
469
|
+
- spec/api/products_api_spec.rb
|
|
470
|
+
- spec/api/generated_apis_coverage_spec.rb
|
|
471
|
+
- spec/api/balances_api_spec.rb
|
|
472
|
+
- spec/api/taxes_api_spec.rb
|
|
473
|
+
- spec/api/webhooks_api_spec.rb
|
|
474
|
+
- spec/api/shipping_contacts_api_spec.rb
|
|
471
475
|
- spec/api/transfers_api_spec.rb
|
|
476
|
+
- spec/api/charges_api_spec.rb
|
|
472
477
|
- spec/api/companies_api_spec.rb
|
|
473
|
-
- spec/api/taxes_api_spec.rb
|
|
474
|
-
- spec/api/webhook_keys_api_spec.rb
|
|
475
478
|
- spec/api/shippings_api_spec.rb
|
|
476
|
-
- spec/api/
|
|
477
|
-
- spec/api/
|
|
478
|
-
- spec/api/charges_api_spec.rb
|
|
479
|
-
- spec/api/balances_api_spec.rb
|
|
479
|
+
- spec/api/customers_api_spec.rb
|
|
480
|
+
- spec/api/discounts_api_spec.rb
|
|
480
481
|
- spec/api/logs_api_spec.rb
|
|
481
|
-
- spec/api/webhooks_api_spec.rb
|
|
482
|
-
- spec/api/products_api_spec.rb
|
|
483
482
|
- spec/api/transactions_api_spec.rb
|
|
484
|
-
- spec/api/
|
|
485
|
-
- spec/api/
|
|
486
|
-
- spec/api/customers_api_spec.rb
|
|
483
|
+
- spec/api/webhook_keys_api_spec.rb
|
|
484
|
+
- spec/api/payment_link_api_spec.rb
|
|
487
485
|
- spec/api/events_api_spec.rb
|
|
486
|
+
- spec/api/payment_methods_api_spec.rb
|
|
487
|
+
- spec/api/api_keys_api_spec.rb
|
|
488
|
+
- spec/api_client_spec.rb
|
|
489
|
+
- spec/models/generated_models_coverage_spec.rb
|
|
490
|
+
- spec/models/webhook_request_ssrf_protection_spec.rb
|
|
488
491
|
- spec/spec_helper.rb
|