wrappix 0.1.0

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.
@@ -0,0 +1,368 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wrappix
4
+ module Templates
5
+ class Tests
6
+ def self.render(_api_name, module_name, config)
7
+ resources = config["resources"] || {}
8
+
9
+ test_content = <<~RUBY
10
+ # frozen_string_literal: true
11
+
12
+ require "test_helper"
13
+
14
+ class #{module_name}Test < Minitest::Test
15
+ def setup
16
+ # Reset configuration before each test
17
+ #{module_name}.configuration = #{module_name}::Configuration.new
18
+
19
+ # Configure base settings
20
+ #{module_name}.configure do |config|
21
+ config.base_url = "#{config["base_url"] || "https://api.example.com"}"
22
+ end
23
+ end
24
+
25
+ def teardown
26
+ # No need to restore anything
27
+ end
28
+
29
+ #{config_tests(module_name, config)}
30
+
31
+ #{client_tests(module_name, resources.keys)}
32
+
33
+ #{error_tests(module_name)}
34
+ end
35
+ RUBY
36
+
37
+ # Generar tests de recursos en archivos separados
38
+ resources.each do |resource_name, resource_config|
39
+ resource_test(module_name, resource_name, resource_config, config)
40
+ # Aquí podrías devolver o guardar estos tests en archivos separados
41
+ end
42
+
43
+ test_content
44
+ end
45
+
46
+ def self.config_tests(module_name, config)
47
+ auth_config = case config["auth_type"]
48
+ when "oauth"
49
+ <<~RUBY.strip
50
+ config.client_id = "test_client_id"
51
+ config.client_secret = "test_client_secret"
52
+ config.token_url = "#{config["token_url"] || "https://api.example.com/oauth/token"}"
53
+ RUBY
54
+ when "basic"
55
+ <<~RUBY.strip
56
+ config.username = "test_username"
57
+ config.password = "test_password"
58
+ RUBY
59
+ when "api_key"
60
+ <<~RUBY.strip
61
+ config.api_key = "test_api_key"
62
+ config.api_key_header = "#{config["api_key_header"] || "X-Api-Key"}"
63
+ RUBY
64
+ else
65
+ ""
66
+ end
67
+
68
+ <<~RUBY
69
+ def test_configuration
70
+ #{module_name}.configure do |config|
71
+ config.base_url = "https://api.test.org"
72
+ config.timeout = 60
73
+ #{auth_config}
74
+ end
75
+
76
+ assert_equal "https://api.test.org", #{module_name}.configuration.base_url
77
+ assert_equal 60, #{module_name}.configuration.timeout
78
+ #{verify_auth_config(module_name, config)}
79
+ end
80
+ RUBY
81
+ end
82
+
83
+ def self.verify_auth_config(module_name, config)
84
+ case config["auth_type"]
85
+ when "oauth"
86
+ <<~RUBY
87
+ assert_equal "test_client_id", #{module_name}.configuration.client_id
88
+ assert_equal "test_client_secret", #{module_name}.configuration.client_secret
89
+ RUBY
90
+ when "basic"
91
+ <<~RUBY
92
+ assert_equal "test_username", #{module_name}.configuration.username
93
+ assert_equal "test_password", #{module_name}.configuration.password
94
+ RUBY
95
+ when "api_key"
96
+ <<~RUBY
97
+ assert_equal "test_api_key", #{module_name}.configuration.api_key
98
+ RUBY
99
+ else
100
+ ""
101
+ end
102
+ end
103
+
104
+ def self.client_tests(module_name, resource_names)
105
+ resource_assertions = resource_names.map do |name|
106
+ " assert_respond_to client, :#{name}"
107
+ end.join("\n")
108
+
109
+ <<~RUBY
110
+ def test_client_initialization
111
+ client = #{module_name}::Client.new
112
+
113
+ assert_instance_of #{module_name}::Client, client
114
+ #{resource_assertions}
115
+ end
116
+ RUBY
117
+ end
118
+
119
+ def self.resource_test(module_name, resource_name, resource_config, global_config)
120
+ endpoints = resource_config["endpoints"] || []
121
+
122
+ endpoint_tests = endpoints.map do |endpoint|
123
+ endpoint_test(module_name, resource_name, endpoint, resource_config, global_config)
124
+ end.join("\n\n")
125
+
126
+ <<~RUBY
127
+ # Tests for #{resource_name} resource
128
+ #{endpoint_tests}
129
+ RUBY
130
+ end
131
+
132
+ def self.endpoint_test(module_name, resource_name, endpoint, resource_config, global_config)
133
+ name = endpoint["name"]
134
+ method = endpoint["method"] || "get"
135
+ path = endpoint["path"] || name
136
+
137
+ # Obtener los casos de prueba definidos o crear uno predeterminado
138
+ test_cases = endpoint["tests"] || [create_default_test_case(endpoint)]
139
+
140
+ # Generar un test para cada caso
141
+ test_cases.map.with_index do |test_case, index|
142
+ generate_test_method(module_name, resource_name, name, method, path, test_case, index, resource_config,
143
+ global_config)
144
+ end.join("\n\n")
145
+ end
146
+
147
+ def self.create_default_test_case(endpoint)
148
+ name = endpoint["name"]
149
+ path = endpoint["path"] || name
150
+ is_collection = endpoint["collection"] || %w[all list index search].include?(name)
151
+
152
+ # Obtener los parámetros del path
153
+ path_params = path.scan(/\{([^}]+)\}/).flatten
154
+ path_params_values = path_params.map { |p| [p, "123"] }.to_h
155
+
156
+ # Crear un caso de prueba predeterminado
157
+ {
158
+ "description" => "funciona correctamente",
159
+ "request" => {
160
+ "path_params" => path_params_values,
161
+ "params" => endpoint["params"] ? { "page" => 1 } : {},
162
+ "body" => %w[post put patch].include?(endpoint["method"].to_s) ? { "name" => "Test" } : {}
163
+ },
164
+ "response" => {
165
+ "status" => 200,
166
+ "body" => if is_collection
167
+ { "data" => [{ "id" => 1, "name" => "Test" }], "next_href" => "/next" }
168
+ else
169
+ { "id" => 1, "name" => "Test" }
170
+ end
171
+ }
172
+ }
173
+ end
174
+
175
+ def self.generate_test_method(module_name, resource_name, endpoint_name, method, path, test_case, index, resource_config, global_config)
176
+ description = test_case["description"] || "case #{index + 1}"
177
+ # Normalizar la descripción para el nombre del método
178
+ method_name_desc = description.downcase.gsub(/[^a-z0-9]/, "_")
179
+
180
+ request = test_case["request"] || {}
181
+ response = test_case["response"] || {}
182
+
183
+ # Preparar los parámetros del path
184
+ path_params = request["path_params"] || {}
185
+ path_params.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")
186
+
187
+ # Preparar los parámetros de la consulta y el cuerpo
188
+ query_params = request["params"] || {}
189
+ body = request["body"] || {}
190
+
191
+ # Construir la URL con los parámetros del path reemplazados
192
+ test_url = path.dup
193
+ path_params.each do |key, value|
194
+ test_url.gsub!(/\{#{key}\}/, value.to_s)
195
+ end
196
+
197
+ # Si la URL comienza con http:// o https://, extraer solo el path
198
+ if test_url.start_with?("http://", "https://")
199
+ # Eliminar el protocolo y el dominio para quedarnos solo con el path
200
+ test_url = test_url.sub(%r{^https?://[^/]+}, "")
201
+ test_url = test_url[1..] if test_url.start_with?("/") # Quitar el slash inicial
202
+ end
203
+
204
+ # Construir los argumentos de la llamada
205
+ call_args = []
206
+ call_args.concat(path_params.values.map(&:inspect))
207
+ call_args << query_params.inspect if query_params.any?
208
+ call_args << body.inspect if body.any?
209
+
210
+ # Generar el código para verificar la respuesta
211
+ response_format = resource_config["response_format"] || global_config["response_format"] || {}
212
+ response_assertions = generate_response_assertions(module_name, endpoint_name, response, response_format)
213
+
214
+ <<~RUBY
215
+ def test_#{resource_name}_#{endpoint_name}_#{method_name_desc}
216
+ # Stub the HTTP request
217
+ status = #{response["status"] || 200}
218
+ response_body = #{response["body"].inspect}
219
+
220
+ @stubs.#{method}("#{test_url}") do |env|
221
+ # Verificar que los parámetros de consulta coincidan
222
+ if env.body.is_a?(String) && !env.body.empty?
223
+ request_body = JSON.parse(env.body)
224
+ # Aquí podrías agregar verificaciones de body si es necesario
225
+ end
226
+
227
+ [status, {'Content-Type' => 'application/json'}, response_body.is_a?(String) ? response_body : response_body.to_json]
228
+ end
229
+
230
+ client = #{module_name}::Client.new
231
+
232
+ #{handle_error_case(response)}
233
+ result = client.#{resource_name}.#{endpoint_name}(#{call_args.join(", ")})
234
+
235
+ #{response_assertions}
236
+ end
237
+ RUBY
238
+ end
239
+
240
+ def self.verify_request_env(query_params, body, method)
241
+ checks = []
242
+
243
+ if query_params.any?
244
+ params_checks = query_params.map do |key, value|
245
+ "assert_equal #{value.inspect}, Rack::Utils.parse_nested_query(env.url.query)[#{key.to_s.inspect}]"
246
+ end
247
+ checks.concat(params_checks)
248
+ end
249
+
250
+ if %w[post put patch].include?(method.to_s) && body.any?
251
+ checks << "request_body = JSON.parse(env.body)"
252
+
253
+ body_checks = body.map do |key, value|
254
+ "assert_equal #{value.inspect}, request_body[#{key.to_s.inspect}]"
255
+ end
256
+ checks.concat(body_checks)
257
+ end
258
+
259
+ checks.join("\n ")
260
+ end
261
+
262
+ def self.handle_error_case(response)
263
+ status = response["status"] || 200
264
+
265
+ if status >= 400
266
+ <<~RUBY
267
+ assert_raises(#{module_name}::Error) do
268
+ RUBY
269
+ else
270
+ ""
271
+ end
272
+ end
273
+
274
+ def self.generate_response_assertions(module_name, endpoint_name, response, response_format)
275
+ status = response["status"] || 200
276
+
277
+ if status >= 400
278
+ " end # assert_raises"
279
+ else
280
+ body = response["body"] || {}
281
+
282
+ # Verificar si el cuerpo es un hash antes de intentar usar key?
283
+ if body.is_a?(String)
284
+ # Si el cuerpo es una cadena (como en el caso de las imágenes binarias)
285
+ <<~RUBY
286
+ assert_instance_of #{module_name}::Object, result
287
+ # El resultado es probablemente una URL o datos binarios
288
+ RUBY
289
+ else
290
+ is_collection = %w[all list index search query].include?(endpoint_name) ||
291
+ (body.is_a?(Hash) && (body.key?("data") || body.key?(:data) ||
292
+ (response_format["collection_root"] &&
293
+ (body.key?(response_format["collection_root"]) ||
294
+ body.key?(response_format["collection_root"].to_sym)))))
295
+
296
+ if is_collection
297
+ collection_root = response_format["collection_root"] || "data"
298
+ items = body[collection_root] || body[collection_root.to_sym] || []
299
+
300
+ if items.empty?
301
+ <<~RUBY
302
+ assert_instance_of #{module_name}::Collection, result
303
+ assert_empty result.data
304
+ RUBY
305
+ else
306
+ <<~RUBY
307
+ assert_instance_of #{module_name}::Collection, result
308
+ assert_equal #{items.size}, result.data.size
309
+
310
+ # Verify first item properties
311
+ if result.data.any?
312
+ first_item = result.data.first
313
+ #{generate_item_assertions(items.first)}
314
+ end
315
+ RUBY
316
+ end
317
+ else
318
+ item_root = response_format["item_root"]
319
+ item = item_root ? (body[item_root] || body[item_root.to_sym] || {}) : body
320
+
321
+ <<~RUBY
322
+ assert_instance_of #{module_name}::Object, result
323
+ #{generate_item_assertions(item)}
324
+ RUBY
325
+ end
326
+ end
327
+ end
328
+ end
329
+
330
+ def self.generate_item_assertions(item)
331
+ return "# No item data to assert" if !item || !item.is_a?(Hash) || item.empty?
332
+
333
+ item.map do |key, value|
334
+ if value.is_a?(Hash) || value.is_a?(Array)
335
+ "assert_not_nil result.#{key}"
336
+ else
337
+ "assert_equal #{value.inspect}, result.#{key}"
338
+ end
339
+ end.join("\n ")
340
+ end
341
+
342
+ def self.error_tests(module_name)
343
+ <<~RUBY
344
+ def test_error_handling
345
+ error = #{module_name}::Error.new("Test error", {error: "details"}, 400)
346
+
347
+ assert_equal "Test error", error.message
348
+ assert_equal({error: "details"}, error.body)
349
+ assert_equal 400, error.status
350
+ end
351
+
352
+ def test_http_error_response
353
+ @stubs.get("error_test") do
354
+ [404, {'Content-Type' => 'application/json'}, {error: "Resource not found"}.to_json]
355
+ end
356
+
357
+ error = assert_raises(#{module_name}::Error) do
358
+ #{module_name}::Request.new("error_test").get
359
+ end
360
+
361
+ assert_equal 404, error.status
362
+ assert_includes error.message, "Resource not found"
363
+ end
364
+ RUBY
365
+ end
366
+ end
367
+ end
368
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wrappix
4
+ VERSION = "0.1.0"
5
+ end
data/lib/wrappix.rb ADDED
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "wrappix/version"
4
+ require_relative "wrappix/builder"
5
+ require_relative "wrappix/configuration"
6
+
7
+ module Wrappix
8
+ class Error < StandardError; end
9
+
10
+ class << self
11
+ attr_accessor :configuration
12
+
13
+ def configure
14
+ self.configuration ||= Configuration.new
15
+ yield(configuration) if block_given?
16
+ self
17
+ end
18
+
19
+ def build(config_file)
20
+ builder = Builder.new(config_file)
21
+ builder.build
22
+ rescue Errno::ENOENT => e
23
+ puts "Error: #{e.message}"
24
+ false
25
+ end
26
+ end
27
+
28
+ self.configuration = Configuration.new
29
+ end
data/sig/wrappix.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Wrappix
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wrappix
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bruno Costanzo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-03-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '1.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: faraday_middleware
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: thor
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: vcr
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '6.1'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '6.1'
75
+ - !ruby/object:Gem::Dependency
76
+ name: webmock
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.18'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.18'
89
+ description: A wrapper builder for any API
90
+ email:
91
+ - dev.bcostanzo@gmail.com
92
+ executables:
93
+ - wrappix
94
+ extensions: []
95
+ extra_rdoc_files: []
96
+ files:
97
+ - ".rubocop.yml"
98
+ - CHANGELOG.md
99
+ - CODE_OF_CONDUCT.md
100
+ - LICENSE.txt
101
+ - README.md
102
+ - Rakefile
103
+ - bin/wrappix
104
+ - lib/wrappix.rb
105
+ - lib/wrappix/builder.rb
106
+ - lib/wrappix/cli.rb
107
+ - lib/wrappix/client.rb
108
+ - lib/wrappix/configuration.rb
109
+ - lib/wrappix/request.rb
110
+ - lib/wrappix/templates/cache.rb
111
+ - lib/wrappix/templates/client.rb
112
+ - lib/wrappix/templates/collection.rb
113
+ - lib/wrappix/templates/configuration.rb
114
+ - lib/wrappix/templates/documentation.rb
115
+ - lib/wrappix/templates/error.rb
116
+ - lib/wrappix/templates/main.rb
117
+ - lib/wrappix/templates/object.rb
118
+ - lib/wrappix/templates/readme.rb
119
+ - lib/wrappix/templates/request.rb
120
+ - lib/wrappix/templates/resource.rb
121
+ - lib/wrappix/templates/tests.rb
122
+ - lib/wrappix/version.rb
123
+ - sig/wrappix.rbs
124
+ homepage: https://github.com/bruno-costanzo/wrappix
125
+ licenses:
126
+ - MIT
127
+ metadata:
128
+ homepage_uri: https://github.com/bruno-costanzo/wrappix
129
+ source_code_uri: https://github.com/bruno-costanzo/wrappix
130
+ changelog_uri: https://github.com/bruno-costanzo/wrappix/blob/main/CHANGELOG.md
131
+ post_install_message:
132
+ rdoc_options: []
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: 3.1.0
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ requirements: []
146
+ rubygems_version: 3.5.16
147
+ signing_key:
148
+ specification_version: 4
149
+ summary: A wrapper builder for any API
150
+ test_files: []