yobi-http 0.6.0 → 0.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8300927c161636f736c477436c29cc441b79847df717a899a465fd0f129cec13
4
- data.tar.gz: d6d693cabeea337129547854608fa394a23b870d89f4f60e3d975960a7bcf1cb
3
+ metadata.gz: 2cd8930bcfe4c6abc945f290f82f4dbca27cbbbe7fb18592e76b0a78f7541325
4
+ data.tar.gz: dddad8c7efc3aac7d4aaa5a95a67585ee62896f2721bd017fc4f15f8f348761a
5
5
  SHA512:
6
- metadata.gz: df1eda58dbc36deb0719785872684adc9fbfa43f033e3ded3ff737792e4bf763a3dd98ab0452db1da4f96ce3fdf4450f73d865e13a061b67eef907c4510d2700
7
- data.tar.gz: 16d5746464e6619d3dee0725b04b0c0cfd723860f2bd952ba0500f4bc1c238e262aaf521f75d2f92310ef0440bdeadc10ee1f704fc1581f00c06780acd99d054
6
+ metadata.gz: 32b78e79f26f6e9a1c58165fc0cf50c1d299505a35135cb592c5a4c8e1a1355a16c483cb4486cada273623b1d7f56821a719326d33e536216cd73310abce8057
7
+ data.tar.gz: eded11c1ac57fc5cd10c029e294465891ec352fbfa332c7de84cef47a1e3a9024742deb60a52ffbcd6f705699c3ea552ad668de40f7ffb1c230d12fcd2d82b65
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require ./spec/spec_helper
data/exe/yobi CHANGED
@@ -3,7 +3,6 @@
3
3
 
4
4
  $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
5
5
 
6
- require "base64"
7
6
  require "erb"
8
7
  require "json"
9
8
  require "net/http"
@@ -77,106 +76,74 @@ end
77
76
  parser.parse!(ARGV)
78
77
 
79
78
  # resolve the HTTP method
80
- method = Yobi::Http::METHODS.include?(ARGV[0]) ? ARGV.shift : "GET"
79
+ method = Yobi.args.http_method?(ARGV[0]) ? ARGV.shift : "GET"
81
80
 
82
81
  # resolve the URL
83
- url = case ARGV[0]
84
- when %r{\Ahttps?://}
85
- ARGV.shift
86
- when /\A:\d+/
87
- "http://localhost#{ARGV.shift}"
88
- else
89
- "http://#{ARGV.shift}"
90
- end
91
-
92
- data =
93
- ARGV.select { |arg| arg.include?("=") }.map.to_h { |arg| arg.split("=", 2).map(&:strip) }
94
-
95
- headers =
96
- { "Content-Type" => "application/json", "User-Agent" => "#{Yobi.name}/#{Yobi::VERSION}" }
97
- .merge(ARGV.select { |arg| arg.include?(":") }.map.to_h { |arg| arg.split(":", 2).map(&:strip) })
82
+ url = Yobi.args.url(ARGV.shift)
83
+
84
+ data = Yobi.args.parse_data(ARGV)
85
+ headers = Yobi.args.parse_headers(ARGV)
98
86
 
99
87
  # prepare authentication header if auth is provided
100
- if @options[:auth]
101
- auth_type = @options[:auth_type] || "basic"
102
- case auth_type.downcase
103
- when "basic"
104
- headers["Authorization"] = "Basic #{Base64.strict_encode64(@options[:auth])}"
105
- when "bearer"
106
- headers["Authorization"] = "Bearer #{@options[:auth]}"
107
- else
108
- warn "Unsupported authentication type: #{auth_type}"
109
- end
110
- end
88
+ Yobi.args.auth_header(headers, @options) if @options[:auth]
111
89
 
112
90
  pp [method, url, data, headers, @options, ARGV] if ENV["YOBI_DEBUG"]
113
91
 
114
- if @options[:offline]
92
+ # Extend Net::HTTPResponse to add util behaviors
93
+ def offline_mode(request, options)
115
94
  Net::HTTP.class_eval do
116
95
  def connect; end
117
96
  end
97
+
98
+ options[:verbose] = true
99
+
100
+ response = Net::HTTPResponse.new("1.1", "200", "OK")
101
+ response["Content-Type"] = "application/json"
102
+ response["Access-Control-Allow-Credentials"] = true
103
+ response["Access-Control-Allow-Origin"] = "*"
104
+ response["Connection"] = "close"
105
+ response["Date"] = Time.now.httpdate
106
+ response["Server"] = "yobi-offline/#{Yobi::VERSION}"
107
+ response["X-Powered-By"] = "Yobi/#{Yobi::VERSION}"
108
+ response.body = body = JSON.pretty_generate({ message: "Offline mode enabled" })
109
+
110
+ view = Yobi.view(:output)
111
+ puts TTY::Markdown.parse(view.result(binding), color: :always)
112
+
113
+ exit 0
118
114
  end
119
115
 
120
- Net::HTTP.start(URI(url).host, URI(url).port, use_ssl: URI(url).scheme == "https") do |http|
121
- options = @options
122
- request_class = Net::HTTP.const_get(method.capitalize)
123
- request = request_class.new(URI(url))
124
-
125
- headers.each { |key, value| request[key] = value }
126
- request["Content-Type"] ||= "application/json"
127
-
128
- request.body = data.to_json unless data.empty?
129
-
130
- if options[:offline]
131
- options[:verbose] = true
132
- # In offline mode, we prepare the request but do not send it. Instead, we print the request details for debugging
133
- # purposes.
134
- response = Net::HTTPResponse.new("1.1", "200", "OK")
135
- response["Content-Type"] = "application/json"
136
- response["Access-Control-Allow-Credentials"] = true
137
- response["Access-Control-Allow-Origin"] = "*"
138
- response["Connection"] = "close"
139
- response["Date"] = Time.now.httpdate
140
- response["Server"] = "yobi-offline/#{Yobi::VERSION}"
141
- response["X-Powered-By"] = "Yobi/#{Yobi::VERSION}"
142
- response.body = body = JSON.pretty_generate({ message: "Offline mode enabled" })
143
-
144
- view = Yobi.view(:output)
145
- puts TTY::Markdown.parse(ERB.new(view).result(binding), color: :always)
146
- break
116
+ def raw_mode(_request, response, options)
117
+ if options[:print].include?("H")
118
+ puts "HTTP/#{response.http_version} #{response.code} #{response.message}"
119
+ response.each_header { |key, value| puts "#{key}: #{value}" }
120
+ puts
147
121
  end
148
122
 
149
- response = http.request(request)
123
+ body = response.parsed_body
124
+ puts body if options[:print].include?("B") && body
150
125
 
151
- body = nil
152
- if response["Content-Type"]&.include?("application/json") && response.body
153
- begin
154
- body ||= JSON.pretty_generate(JSON.parse(response.body))
155
- rescue StandardError
156
- body ||= response.body
157
- end
158
- else
159
- body ||= response.body
160
- end
126
+ exit 0
127
+ end
161
128
 
162
- if options[:raw]
163
- if options[:print].include?("H")
164
- puts "HTTP/#{response.http_version} #{response.code} #{response.message}"
165
- response.each_header { |key, value| puts "#{key}: #{value}" }
166
- puts
167
- end
129
+ Yobi.request(method, url, data: data, headers: headers, options: @options) do |http, request|
130
+ options = @options
168
131
 
169
- puts body if options[:print].include?("B") && body
132
+ offline_mode(request, options) if options[:offline]
170
133
 
171
- break
172
- end
134
+ response = http.request(request)
135
+
136
+ raw_mode(request, response, options) if options[:raw]
137
+
138
+ body = JSON.pretty_generate(response.parsed_body)
173
139
 
174
140
  view = Yobi.view(:output)
141
+ output_result = view.result(binding)
175
142
 
176
143
  if options[:output]
177
144
  file = File.expand_path(options[:output], Dir.pwd)
178
- File.write(file, ERB.new(view).result(binding), mode: "w")
145
+ File.write(file, output_result, mode: "w")
179
146
  else
180
- puts TTY::Markdown.parse(ERB.new(view).result(binding), color: :always)
147
+ puts TTY::Markdown.parse(output_result, color: :always)
181
148
  end
182
149
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+
5
+ module Yobi
6
+ module CLI
7
+ # Command-line argument utilities
8
+ module Arguments
9
+ class << self
10
+ def http_method?(value)
11
+ Yobi::Http::METHODS.include? value.upcase
12
+ end
13
+
14
+ def url(value)
15
+ case value
16
+ when %r{\Ahttps?://}
17
+ value
18
+ when /\A:\d+/
19
+ "http://localhost#{value}"
20
+ else
21
+ "http://#{value}"
22
+ end
23
+ end
24
+
25
+ def parse_data(args)
26
+ args.select { |arg| arg.include?("=") }.map.to_h { |arg| arg.split("=", 2).map(&:strip) }
27
+ end
28
+
29
+ def parse_headers(args)
30
+ { "Content-Type" => "application/json", "User-Agent" => "#{Yobi.name}/#{Yobi::VERSION}" }
31
+ .merge(args.select { |arg| arg.include?(":") }.map.to_h { |arg| arg.split(":", 2).map(&:strip) })
32
+ end
33
+
34
+ def auth_header(headers, options)
35
+ raise ArgumentError, "Authentication credentials must be provided with --auth" unless options[:auth]
36
+
37
+ auth_type = options.fetch(:auth_type, "basic")
38
+
39
+ case auth_type.downcase
40
+ when "basic"
41
+ headers["Authorization"] = "Basic #{Base64.strict_encode64(options[:auth])}"
42
+ when "bearer"
43
+ headers["Authorization"] = "Bearer #{options[:auth]}"
44
+ else
45
+ warn "Unsupported authentication type: #{auth_type}"
46
+ exit 1
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
data/lib/yobi/cli.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "cli/arguments"
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ # Extend the Net::HTTPResponse class to add parsing methods
5
+ class HTTPResponse
6
+ # Parses the response body as JSON if the Content-Type is application/json, otherwise returns the raw body.
7
+ #
8
+ # @return [Object, String, nil] The parsed JSON object, raw body string, or nil if there is no body.
9
+ def parsed_body
10
+ return @parsed_body if instance_variable_defined?(:@parsed_body)
11
+
12
+ if body && content_type&.include?("application/json")
13
+ begin
14
+ @parsed_body = JSON.parse(body)
15
+ rescue JSON::ParserError
16
+ @parsed_body = body
17
+ end
18
+ else
19
+ @parsed_body = body
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "net_http/parsing"
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir.glob(File.expand_path("extensions/*.rb", __dir__)).sort.each do |path|
4
+ require path
5
+ end
data/lib/yobi/http.rb CHANGED
@@ -1,8 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "net/http"
4
+
3
5
  module Yobi
4
6
  # Yobi Http behaviors and constants
5
7
  module Http
6
8
  METHODS = %w[GET POST PUT DELETE PATCH HEAD OPTIONS].freeze
9
+
10
+ class << self
11
+ # rubocop:disable Metrics/AbcSize
12
+ def request(method, url, data: {}, headers: {}, options: {})
13
+ @uri = URI(url)
14
+ @options = options
15
+ @method = method.capitalize
16
+
17
+ Net::HTTP.start(@uri.host, @uri.port, use_ssl: @uri.scheme == "https") do |http|
18
+ request_class = Net::HTTP.const_get(@method)
19
+ request = request_class.new(@uri)
20
+
21
+ headers.each { |key, value| request[key] = value }
22
+
23
+ request.body = data.to_json unless data.empty?
24
+
25
+ yield(http, request) if block_given?
26
+ end
27
+ end
28
+ # rubocop:enable Metrics/AbcSize
29
+ end
7
30
  end
8
31
  end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yobi
4
+ # Yobi gem version
5
+ VERSION = "0.8.0"
6
+ end
data/lib/yobi.rb CHANGED
@@ -1,26 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "erb"
4
+
5
+ require "yobi/cli"
6
+ require "yobi/extensions"
3
7
  require "yobi/http"
8
+ require "yobi/version"
4
9
 
5
10
  # Yobi Http CLI client namespace
6
11
  module Yobi
7
12
  # Standard Yobi error class
8
13
  class Error < StandardError; end
9
14
 
10
- # Yobi gem version
11
- VERSION = "0.6.0"
15
+ class << self
16
+ def name
17
+ "yobi"
18
+ end
12
19
 
20
+ def description
21
+ "A simple HTTP client for testing APIs from the command line."
22
+ end
13
23
 
14
- def self.name
15
- "yobi"
16
- end
24
+ # Load project templates
25
+ def view(name)
26
+ ERB.new(File.read(File.join(__dir__, "views/#{name}.md.erb")))
27
+ end
17
28
 
18
- def self.description
19
- "A simple HTTP client for testing APIs from the command line."
20
- end
29
+ def request(...)
30
+ Yobi::Http.request(...)
31
+ end
21
32
 
22
- # Load project templates
23
- def self.view(name)
24
- File.read(File.join(__dir__, "views/#{name}.md.erb"))
33
+ def args
34
+ Yobi::CLI::Arguments
35
+ end
25
36
  end
26
37
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe Net::HTTPResponse do
6
+ describe "#parsed_body" do
7
+ subject(:response) do
8
+ Yobi::Http.request(:get, "http://example.com") do |http, request|
9
+ http.request(request)
10
+ end
11
+ end
12
+
13
+ before do
14
+ stub_request(:get, "http://example.com")
15
+ .to_return(status: 200, body: '{"name": "John", "age": 30}', headers: { "Content-Type" => "application/json" })
16
+ end
17
+
18
+ it "parses JSON response bodies into Ruby objects" do
19
+ expect(response.parsed_body).to eq({ "name" => "John", "age" => 30 })
20
+ end
21
+
22
+ context "when the response body is not valid JSON" do
23
+ before do
24
+ stub_request(:get, "http://example.com")
25
+ .to_return(status: 200, body: "Not a JSON string", headers: { "Content-Type" => "application/json" })
26
+ end
27
+
28
+ it "returns the raw body string if JSON parsing fails" do
29
+ expect(response.parsed_body).to eq("Not a JSON string")
30
+ end
31
+ end
32
+
33
+ context "when the Content-Type is not application/json" do
34
+ before do
35
+ stub_request(:get, "http://example.com")
36
+ .to_return(status: 200, body: "Just a plain text response", headers: { "Content-Type" => "text/plain" })
37
+ end
38
+
39
+ it "returns the raw body string if Content-Type is not application/json" do
40
+ expect(response.parsed_body).to eq("Just a plain text response")
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,60 @@
1
+ if ENV["COVERAGE"]
2
+ require "simplecov"
3
+ require "simplecov-console"
4
+
5
+ SimpleCov.root(File.expand_path("..", __dir__))
6
+
7
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
8
+ [SimpleCov::Formatter::HTMLFormatter,
9
+ SimpleCov::Formatter::Console]
10
+ )
11
+ SimpleCov.coverage_dir("coverage")
12
+
13
+ SimpleCov.start do
14
+ add_filter "/spec/"
15
+ add_group "Lib", "lib"
16
+
17
+ track_files "lib/**/*.rb"
18
+ end
19
+
20
+ SimpleCov.minimum_coverage 90
21
+ end
22
+
23
+ # puts "ROOT: #{SimpleCov.root}"
24
+ # puts "TRACKED: #{SimpleCov.tracked_files.inspect}"
25
+ # puts "LOADED BEFORE START: #{$LOADED_FEATURES.grep(/lib/).size}"
26
+
27
+ Dir[File.expand_path("../lib/**/*.rb", __dir__)].sort.each { |f| require f }
28
+
29
+ RSpec.configure do |config|
30
+ config.expect_with :rspec do |expectations|
31
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
32
+ end
33
+
34
+ config.mock_with :rspec do |mocks|
35
+ mocks.verify_partial_doubles = true
36
+ end
37
+
38
+ config.shared_context_metadata_behavior = :apply_to_host_groups
39
+ config.filter_run_when_matching :focus unless ENV["CI"]
40
+
41
+ if ENV["META"]
42
+ config.example_status_persistence_file_path = "spec/examples.txt"
43
+ config.profile_examples = 10
44
+ end
45
+ # config.warnings = true
46
+
47
+ # if config.files_to_run.one?
48
+ # config.default_formatter = "doc"
49
+ # end
50
+
51
+ # config.order = :random
52
+ # Kernel.srand config.seed
53
+
54
+ config.after(:suite) do
55
+ SimpleCov.result.format! if ENV["COVERAGE"]
56
+ end
57
+ end
58
+
59
+ # load support configuration files
60
+ Dir[File.expand_path("support/**/*.rb", __dir__)].sort.each { |f| require f }
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webmock/rspec"
4
+
5
+ RSpec.configure do |config|
6
+ # Enable WebMock
7
+ config.before(:each) do
8
+ WebMock.enable!
9
+ end
10
+
11
+ # Disable WebMock after each test to allow real HTTP connections if needed
12
+ config.after(:each) do
13
+ WebMock.disable!
14
+ end
15
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe Yobi::CLI::Arguments do
6
+ describe ".http_method?" do
7
+ context "when given valid HTTP methods" do
8
+ Yobi::Http::METHODS.each do |method|
9
+ it "recognizes #{method} as a valid HTTP method" do
10
+ expect(described_class.http_method?(method)).to be_truthy
11
+ end
12
+
13
+ it "is case-insensitive for #{method}" do
14
+ expect(described_class.http_method?(method.downcase)).to be_truthy
15
+ end
16
+ end
17
+ end
18
+
19
+ context "when given invalid HTTP methods" do
20
+ it "does not recognize FOO as a valid HTTP method" do
21
+ expect(described_class.http_method?("Foo")).to be_falsey
22
+ end
23
+ end
24
+ end
25
+
26
+ describe ".url" do
27
+ context "when given a URL that starts with http://" do
28
+ it "returns the http URL unchanged if it looks completed and valid" do
29
+ expect(described_class.url("http://example.com")).to eq("http://example.com")
30
+ end
31
+
32
+ it "returns the https URL unchanged if it looks completed and valid" do
33
+ expect(described_class.url("https://example.com")).to eq("https://example.com")
34
+ end
35
+ end
36
+
37
+ context "when given a URL that does not start with http://" do
38
+ it "prepends http:// to URLs" do
39
+ expect(described_class.url("example.com")).to eq("http://example.com")
40
+ end
41
+ end
42
+
43
+ context "when given a URL that starts with :port" do
44
+ it "returns a URL with http://localhost prepended" do
45
+ expect(described_class.url(":3000")).to eq("http://localhost:3000")
46
+ end
47
+ end
48
+ end
49
+
50
+ describe ".parse_data" do
51
+ it "parses key=value pairs into a hash" do
52
+ args = ["name=John", "age=30", "city=New York"]
53
+
54
+ expect(described_class.parse_data(args)).to eq({ "name" => "John", "age" => "30", "city" => "New York" })
55
+ end
56
+
57
+ it "ignores arguments that do not contain an equals sign" do
58
+ args = ["name=John", "invalid_arg", "age=30"]
59
+
60
+ expect(described_class.parse_data(args)).to eq({ "name" => "John", "age" => "30" })
61
+ end
62
+ end
63
+
64
+ describe ".parse_headers" do
65
+ it "parses key:value pairs into a headers hash" do
66
+ args = ["Content-Type: text/http", "User-Agent: Yobi/1.0"]
67
+
68
+ expect(described_class.parse_headers(args))
69
+ .to eq({ "Content-Type" => "text/http", "User-Agent" => "Yobi/1.0" })
70
+ end
71
+
72
+ it "includes default headers" do
73
+ args = []
74
+
75
+ expect(described_class.parse_headers(args))
76
+ .to eq({ "Content-Type" => "application/json", "User-Agent" => "#{Yobi.name}/#{Yobi::VERSION}" })
77
+ end
78
+ end
79
+
80
+ describe ".auth_header" do
81
+ it "raises an error if auth credentials are missing" do
82
+ expect { described_class.auth_header({}, {}) }.to raise_error(ArgumentError)
83
+ end
84
+
85
+ it "adds a Basic Authorization header when auth_type is basic" do
86
+ headers = {}
87
+ options = { auth: "user:pass", auth_type: "basic" }
88
+
89
+ described_class.auth_header(headers, options)
90
+ expect(headers["Authorization"]).to eq("Basic #{Base64.strict_encode64("user:pass")}")
91
+ end
92
+
93
+ it "adds a Bearer Authorization header when auth_type is bearer" do
94
+ headers = {}
95
+ options = { auth: "mytoken", auth_type: "bearer" }
96
+
97
+ described_class.auth_header(headers, options)
98
+ expect(headers["Authorization"]).to eq("Bearer mytoken")
99
+ end
100
+
101
+ it "exits with an error for unsupported auth types" do
102
+ headers = {}
103
+ options = { auth: "mytoken", auth_type: "unsupported" }
104
+
105
+ expect { described_class.auth_header(headers, options) }.to raise_error(SystemExit)
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe Yobi::Http do
6
+ describe "METHODS" do
7
+ it "includes standard HTTP methods" do
8
+ expect(Yobi::Http::METHODS).to include("GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS")
9
+ end
10
+ end
11
+
12
+ describe ".request" do
13
+ subject(:response) { described_class.request(*meta) { |http, request| http.request(request) } }
14
+
15
+ let(:meta) { [:get, "http://example.com"] }
16
+ let(:result) { { status: 201, body: "Created" } }
17
+ let(:arguments) { { body: {} } }
18
+
19
+ before { stub_request(*meta).to_return(**result) }
20
+
21
+ it { expect(Yobi::Http).to respond_to(:request) }
22
+
23
+ context "when making a GET request" do
24
+ let(:result) { { status: 200, body: "OK" } }
25
+ let(:arguments) { { body: nil } }
26
+
27
+ it { expect(response.code).to eq("200") }
28
+ it { expect(response.body).to eq("OK") }
29
+ end
30
+
31
+ context "when making a POST request with data" do
32
+ let(:meta) { [:post, "http://example.com"] }
33
+ let(:result) { { status: 201, body: "Created" } }
34
+ let(:arguments) { { body: { "name" => "John" }.to_json } }
35
+
36
+ it { expect(response.code).to eq("201") }
37
+ it { expect(response.body).to eq("Created") }
38
+ end
39
+ end
40
+ end
data/spec/yobi_spec.rb ADDED
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe Yobi do
6
+ describe "VERSION" do
7
+ it { expect(Yobi.constants).to include(:VERSION) }
8
+
9
+ it "follows semantic versioning format" do
10
+ expect(Yobi::VERSION).to match(/^\d+\.\d+\.\d+$/)
11
+ end
12
+ end
13
+
14
+ describe ".name" do
15
+ it { expect(Yobi).to respond_to(:name) }
16
+ it { expect(Yobi.name).to eq("yobi") }
17
+ end
18
+
19
+ describe ".description" do
20
+ it { expect(Yobi).to respond_to(:description) }
21
+ it { expect(Yobi.description).to eq("A simple HTTP client for testing APIs from the command line.") }
22
+ end
23
+
24
+ describe ".view" do
25
+ it { expect(Yobi).to respond_to(:view) }
26
+
27
+ it "loads a template by name" do
28
+ expect(Yobi.view(:output).src).to include("HTTP/1.1")
29
+ end
30
+
31
+ it "raises an error if the template does not exist" do
32
+ expect { Yobi.view("nonexistent") }.to raise_error(Errno::ENOENT)
33
+ end
34
+ end
35
+
36
+ describe ".args" do
37
+ it { expect(Yobi).to respond_to(:args) }
38
+ it { expect(Yobi.args).to be(Yobi::CLI::Arguments) }
39
+ end
40
+ end
metadata CHANGED
@@ -1,14 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yobi-http
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Vinciguerra
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-02-16 00:00:00.000000000 Z
10
+ date: 2026-02-20 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: base64
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.3'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.3'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: tty-markdown
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -32,6 +46,7 @@ executables:
32
46
  extensions: []
33
47
  extra_rdoc_files: []
34
48
  files:
49
+ - ".rspec"
35
50
  - Agents.md
36
51
  - CHANGELOG.md
37
52
  - CODE_OF_CONDUCT.md
@@ -40,7 +55,19 @@ files:
40
55
  - exe/yobi
41
56
  - lib/views/output.md.erb
42
57
  - lib/yobi.rb
58
+ - lib/yobi/cli.rb
59
+ - lib/yobi/cli/arguments.rb
60
+ - lib/yobi/extensions.rb
61
+ - lib/yobi/extensions/net_http.rb
62
+ - lib/yobi/extensions/net_http/parsing.rb
43
63
  - lib/yobi/http.rb
64
+ - lib/yobi/version.rb
65
+ - spec/extensions/net_http/parsing_spec.rb
66
+ - spec/spec_helper.rb
67
+ - spec/support/webmock.rb
68
+ - spec/yobi/cli/arguments_spec.rb
69
+ - spec/yobi/http_spec.rb
70
+ - spec/yobi_spec.rb
44
71
  homepage: https://github.com/dvinciguerra/yobi-http
45
72
  licenses: []
46
73
  metadata: