sinew 2.0.5 → 3.0.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 +4 -4
- data/.github/workflows/test.yml +26 -0
- data/.rubocop.yml +9 -6
- data/.vscode/settings.json +0 -10
- data/Gemfile +9 -0
- data/README.md +13 -17
- data/Rakefile +33 -18
- data/bin/sinew +2 -0
- data/lib/sinew.rb +0 -1
- data/lib/sinew/connection.rb +52 -0
- data/lib/sinew/connection/log_formatter.rb +22 -0
- data/lib/sinew/connection/rate_limit.rb +29 -0
- data/lib/sinew/core_ext.rb +1 -1
- data/lib/sinew/dsl.rb +2 -1
- data/lib/sinew/main.rb +7 -55
- data/lib/sinew/output.rb +7 -23
- data/lib/sinew/request.rb +20 -71
- data/lib/sinew/response.rb +8 -57
- data/lib/sinew/runtime_options.rb +4 -4
- data/lib/sinew/version.rb +1 -1
- data/sample.sinew +2 -2
- data/sinew.gemspec +16 -17
- metadata +41 -99
- data/.travis.yml +0 -4
- data/lib/sinew/cache.rb +0 -79
- data/test/legacy/eu.httpbin.org/head/redirect,3 +0 -51
- data/test/legacy/eu.httpbin.org/head/status,500 +0 -1
- data/test/legacy/eu.httpbin.org/redirect,3 +0 -11
- data/test/legacy/eu.httpbin.org/status,500 +0 -1
- data/test/legacy/legacy.sinew +0 -2
- data/test/recipes/array_header.sinew +0 -6
- data/test/recipes/basic.sinew +0 -8
- data/test/recipes/dups.sinew +0 -7
- data/test/recipes/implicit_header.sinew +0 -5
- data/test/recipes/limit.sinew +0 -11
- data/test/recipes/noko.sinew +0 -9
- data/test/recipes/uri.sinew +0 -11
- data/test/recipes/xml.sinew +0 -8
- data/test/test.html +0 -45
- data/test/test_cache.rb +0 -69
- data/test/test_helper.rb +0 -126
- data/test/test_legacy.rb +0 -23
- data/test/test_main.rb +0 -34
- data/test/test_nokogiri_ext.rb +0 -18
- data/test/test_output.rb +0 -56
- data/test/test_recipes.rb +0 -60
- data/test/test_requests.rb +0 -164
- data/test/test_utf8.rb +0 -39
data/test/test_legacy.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
require_relative 'test_helper'
|
2
|
-
|
3
|
-
class TestLegacy < MiniTest::Test
|
4
|
-
def setup
|
5
|
-
super
|
6
|
-
|
7
|
-
# These are legacy cache files, pulled from an older version of sinew. We
|
8
|
-
# use them to test our legacy head parsing.
|
9
|
-
src = 'legacy/eu.httpbin.org'
|
10
|
-
dst = "#{TMP}/eu.httpbin.org"
|
11
|
-
FileUtils.cp_r(File.expand_path(src, __dir__), dst)
|
12
|
-
end
|
13
|
-
|
14
|
-
def test_legacy
|
15
|
-
assert_output(/failed with 999/) do
|
16
|
-
sinew.dsl.get('http://eu.httpbin.org/status/500')
|
17
|
-
assert_equal "\n", sinew.dsl.raw
|
18
|
-
end
|
19
|
-
|
20
|
-
sinew.dsl.get('http://eu.httpbin.org/redirect/3')
|
21
|
-
assert_equal 'http://eu.httpbin.org/get', sinew.dsl.url
|
22
|
-
end
|
23
|
-
end
|
data/test/test_main.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
require_relative 'test_helper'
|
2
|
-
|
3
|
-
require 'base64'
|
4
|
-
|
5
|
-
class TestMain < MiniTest::Test
|
6
|
-
def test_rate_limit
|
7
|
-
# true network requests call sleep for timeouts, which interferes with our
|
8
|
-
# instrumentation of Kernel#sleep
|
9
|
-
skip if test_network?
|
10
|
-
|
11
|
-
slept = false
|
12
|
-
|
13
|
-
# change Kernel#sleep to not really sleep!
|
14
|
-
Kernel.send(:alias_method, :old_sleep, :sleep)
|
15
|
-
Kernel.send(:define_method, :sleep) do |_duration|
|
16
|
-
slept = true
|
17
|
-
end
|
18
|
-
|
19
|
-
sinew.runtime_options.rate_limit = 1
|
20
|
-
sinew.dsl.get('http://httpbin.org/html')
|
21
|
-
sinew.dsl.get('http://httpbin.org/get')
|
22
|
-
assert(slept)
|
23
|
-
|
24
|
-
# restore old Kernel#sleep
|
25
|
-
Kernel.send(:alias_method, :sleep, :old_sleep)
|
26
|
-
Kernel.send(:undef_method, :old_sleep)
|
27
|
-
end
|
28
|
-
|
29
|
-
def test_gunzip
|
30
|
-
body = Base64.decode64('H4sICBRI61oAA2d1Yi50eHQASy9N4gIAJlqRYgQAAAA=')
|
31
|
-
body = Sinew::Response.process_body(OpenStruct.new(body: body))
|
32
|
-
assert_equal 'gub', body.strip
|
33
|
-
end
|
34
|
-
end
|
data/test/test_nokogiri_ext.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
require_relative 'test_helper'
|
2
|
-
|
3
|
-
class TestNokogiriExt < MiniTest::Test
|
4
|
-
def test_inner_text
|
5
|
-
assert_equal('hello world', noko.css('li').inner_text)
|
6
|
-
assert_equal('<li>hello</li> <li>world</li>', noko.css('ul').inner_html.squish)
|
7
|
-
end
|
8
|
-
|
9
|
-
def test_just_me
|
10
|
-
assert_equal('a', noko.css('div').text_just_me.squish)
|
11
|
-
assert_equal('b b', noko.css('p').text_just_me.squish)
|
12
|
-
end
|
13
|
-
|
14
|
-
def noko
|
15
|
-
@noko ||= Nokogiri::HTML(HTML).css('#nokogiri_ext')
|
16
|
-
end
|
17
|
-
protected :noko
|
18
|
-
end
|
data/test/test_output.rb
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
require_relative 'test_helper'
|
2
|
-
|
3
|
-
class TestOutput < MiniTest::Test
|
4
|
-
def test_filenames
|
5
|
-
sinew = Sinew::Main.new(recipe: 'gub.sinew')
|
6
|
-
assert_equal 'gub.csv', sinew.output.filename
|
7
|
-
sinew = Sinew::Main.new(recipe: 'gub')
|
8
|
-
assert_equal 'gub.csv', sinew.output.filename
|
9
|
-
sinew = Sinew::Main.new(recipe: '/somewhere/gub.sinew')
|
10
|
-
assert_equal '/somewhere/gub.csv', sinew.output.filename
|
11
|
-
end
|
12
|
-
|
13
|
-
def test_normalization
|
14
|
-
output = Sinew::Output.new(nil)
|
15
|
-
|
16
|
-
#
|
17
|
-
# simple types
|
18
|
-
#
|
19
|
-
|
20
|
-
assert_equal '', output.send(:normalize, nil)
|
21
|
-
assert_equal '', output.send(:normalize, '')
|
22
|
-
assert_equal 'text', output.send(:normalize, 'text')
|
23
|
-
assert_equal '123', output.send(:normalize, 123)
|
24
|
-
assert_equal('1|2', output.send(:normalize, [ 1, 2 ]))
|
25
|
-
|
26
|
-
#
|
27
|
-
# nokogiri
|
28
|
-
#
|
29
|
-
|
30
|
-
noko = Nokogiri::HTML(HTML)
|
31
|
-
|
32
|
-
# node => text
|
33
|
-
assert_equal('text', output.send(:normalize, noko.css('#element')))
|
34
|
-
# nodes => text joined with space
|
35
|
-
assert_equal('text1 text2', output.send(:normalize, noko.css('.e')))
|
36
|
-
|
37
|
-
#
|
38
|
-
# string cleanups
|
39
|
-
#
|
40
|
-
|
41
|
-
# strip_html_tags
|
42
|
-
assert_equal('gub', output.send(:normalize, '<tag>gub</tag>'))
|
43
|
-
# strip_html_tags and replace with spaces
|
44
|
-
assert_equal('hello world', output.send(:normalize, '<tag>hello<br>world</tag>'))
|
45
|
-
# convert_smart_punctuation
|
46
|
-
assert_equal('"gub"', output.send(:normalize, "\302\223gub\302\224"))
|
47
|
-
# convert_accented_html_entities
|
48
|
-
assert_equal('a', output.send(:normalize, 'á'))
|
49
|
-
# convert_miscellaneous_html_entities
|
50
|
-
assert_equal('<&>', output.send(:normalize, '<&>'))
|
51
|
-
# to_ascii
|
52
|
-
assert_equal('cafe', output.send(:normalize, "caf\xc3\xa9"))
|
53
|
-
# squish
|
54
|
-
assert_equal('hello world', output.send(:normalize, "\nhello \t \rworld"))
|
55
|
-
end
|
56
|
-
end
|
data/test/test_recipes.rb
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
require_relative 'test_helper'
|
2
|
-
|
3
|
-
class TestRecipe < MiniTest::Test
|
4
|
-
DIR = File.expand_path('recipes', __dir__)
|
5
|
-
TEST_SINEW = "#{TMP}/test.sinew".freeze
|
6
|
-
TEST_CSV = "#{TMP}/test.csv".freeze
|
7
|
-
|
8
|
-
def test_recipes
|
9
|
-
Dir.chdir(DIR) do
|
10
|
-
Dir['*.sinew'].sort.each do |filename|
|
11
|
-
recipe = IO.read(filename)
|
12
|
-
|
13
|
-
# get ready
|
14
|
-
IO.write(TEST_SINEW, recipe)
|
15
|
-
sinew = Sinew::Main.new(cache: TMP, quiet: true, recipe: TEST_SINEW)
|
16
|
-
|
17
|
-
# read OPTIONS
|
18
|
-
if options = options_from(recipe)
|
19
|
-
options.each do |key, value|
|
20
|
-
sinew.options[key] = value
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
# read OUTPUT
|
25
|
-
output = output_from(recipe, filename)
|
26
|
-
|
27
|
-
# run
|
28
|
-
sinew.run
|
29
|
-
|
30
|
-
# assert
|
31
|
-
csv = IO.read(TEST_CSV)
|
32
|
-
assert_equal(output, csv, "Output didn't match for recipes/#{filename}")
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def options_from(recipe)
|
38
|
-
if options = recipe[/^#\s*OPTIONS\s*(\{.*\})/, 1]
|
39
|
-
# rubocop:disable Security/Eval
|
40
|
-
eval(options)
|
41
|
-
# rubocop:enable Security/Eval
|
42
|
-
end
|
43
|
-
end
|
44
|
-
protected :options_from
|
45
|
-
|
46
|
-
def output_from(recipe, filename)
|
47
|
-
lines = recipe.split("\n")
|
48
|
-
first_line = lines.index { |i| i =~ /^# OUTPUT/ }
|
49
|
-
if !first_line
|
50
|
-
raise "# OUTPUT not found in recipes/#{filename}"
|
51
|
-
end
|
52
|
-
|
53
|
-
output = lines[first_line + 1..-1]
|
54
|
-
output = output.map { |i| i.gsub(/^# /, '') }
|
55
|
-
output = output.join("\n")
|
56
|
-
output += "\n"
|
57
|
-
output
|
58
|
-
end
|
59
|
-
protected :output_from
|
60
|
-
end
|
data/test/test_requests.rb
DELETED
@@ -1,164 +0,0 @@
|
|
1
|
-
require_relative 'test_helper'
|
2
|
-
|
3
|
-
class TestRequests < MiniTest::Test
|
4
|
-
def test_user_agent
|
5
|
-
sinew.dsl.get('http://httpbin.org/get', a: 1, b: 2)
|
6
|
-
assert_match(/sinew/, sinew.dsl.json[:headers][:'User-Agent'])
|
7
|
-
end
|
8
|
-
|
9
|
-
def test_basic_methods
|
10
|
-
sinew.dsl.get('http://httpbin.org/get', a: 1, b: 2)
|
11
|
-
assert_equal(200, sinew.dsl.code)
|
12
|
-
assert_equal({ a: '1', b: '2' }, sinew.dsl.json[:args])
|
13
|
-
|
14
|
-
sinew.dsl.post('http://httpbin.org/post', a: 1, b: 2)
|
15
|
-
assert_equal({ a: '1', b: '2' }, sinew.dsl.json[:form])
|
16
|
-
|
17
|
-
sinew.dsl.post_json('http://httpbin.org/post', a: 1, b: 2)
|
18
|
-
assert_equal({ a: 1, b: 2 }, sinew.dsl.json[:json])
|
19
|
-
end
|
20
|
-
|
21
|
-
def test_custom_headers
|
22
|
-
sinew.dsl.http('get', 'http://httpbin.org/get', headers: { "User-Agent": '007' })
|
23
|
-
assert_match(/007/, sinew.dsl.json[:headers][:'User-Agent'])
|
24
|
-
end
|
25
|
-
|
26
|
-
def test_redirects
|
27
|
-
# absolute redirect
|
28
|
-
sinew.dsl.get('http://httpbin.org/redirect/2')
|
29
|
-
assert_equal 'http://httpbin.org/get', sinew.dsl.url
|
30
|
-
|
31
|
-
# and relative redirect
|
32
|
-
sinew.dsl.get('http://httpbin.org/relative-redirect/2')
|
33
|
-
assert_equal 'http://httpbin.org/get', sinew.dsl.url
|
34
|
-
end
|
35
|
-
|
36
|
-
def test_errors
|
37
|
-
skip if test_network?
|
38
|
-
|
39
|
-
# 500
|
40
|
-
assert_output(/failed with 500/) do
|
41
|
-
sinew.dsl.get('http://httpbin.org/status/500')
|
42
|
-
assert_equal 500, sinew.dsl.code
|
43
|
-
assert_equal '500', sinew.dsl.raw
|
44
|
-
end
|
45
|
-
|
46
|
-
# timeout
|
47
|
-
assert_output(/failed with 999/) do
|
48
|
-
sinew.dsl.get('http://httpbin.org/delay/1')
|
49
|
-
assert_equal 999, sinew.dsl.code
|
50
|
-
end
|
51
|
-
|
52
|
-
# uncommon errors
|
53
|
-
errors = [
|
54
|
-
Errno::ECONNREFUSED,
|
55
|
-
HTTParty::RedirectionTooDeep.new(nil),
|
56
|
-
OpenSSL::SSL::SSLError.new,
|
57
|
-
SocketError.new,
|
58
|
-
]
|
59
|
-
errors.each_with_index do |error, index|
|
60
|
-
stub_request(:get, %r{http://[^/]+/error#{index}}).to_return { raise error }
|
61
|
-
assert_output(/failed with 999/) do
|
62
|
-
sinew.dsl.get("http://httpbin.org/error#{index}")
|
63
|
-
assert_equal 999, sinew.dsl.code
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def test_retry_timeout
|
69
|
-
skip if test_network?
|
70
|
-
|
71
|
-
errors = 2
|
72
|
-
stub_request(:get, %r{http://[^/]+/error}).to_return do
|
73
|
-
if errors > 0
|
74
|
-
errors -= 1
|
75
|
-
raise Timeout::Error
|
76
|
-
end
|
77
|
-
{ body: 'done', status: 200 }
|
78
|
-
end
|
79
|
-
sinew.dsl.get('http://httpbin.org/error')
|
80
|
-
assert_equal 0, errors
|
81
|
-
assert_equal 'done', sinew.dsl.raw
|
82
|
-
end
|
83
|
-
|
84
|
-
def test_retry_500
|
85
|
-
skip if test_network?
|
86
|
-
|
87
|
-
errors = 2
|
88
|
-
stub_request(:get, %r{http://[^/]+/error}).to_return do
|
89
|
-
if errors > 0
|
90
|
-
errors -= 1
|
91
|
-
return { status: 500 }
|
92
|
-
end
|
93
|
-
{ body: 'done', status: 200 }
|
94
|
-
end
|
95
|
-
sinew.dsl.get('http://httpbin.org/error')
|
96
|
-
assert_equal 0, errors
|
97
|
-
assert_equal 'done', sinew.dsl.raw
|
98
|
-
end
|
99
|
-
|
100
|
-
def test_cache_key
|
101
|
-
# empty
|
102
|
-
req = Sinew::Request.new(sinew, 'get', 'http://host')
|
103
|
-
assert_equal req.cache_key, 'host/_root_'
|
104
|
-
|
105
|
-
# path
|
106
|
-
req = Sinew::Request.new(sinew, 'get', 'http://host/path')
|
107
|
-
assert_equal req.cache_key, 'host/path'
|
108
|
-
|
109
|
-
# query
|
110
|
-
req = Sinew::Request.new(sinew, 'get', 'http://host/path', query: { a: 'b' })
|
111
|
-
assert_equal req.cache_key, 'host/path,a=b'
|
112
|
-
|
113
|
-
# post with body
|
114
|
-
req = Sinew::Request.new(sinew, 'post', 'http://host/path', body: { c: 'd' })
|
115
|
-
assert_equal req.cache_key, 'host/post,path,c=d'
|
116
|
-
|
117
|
-
# too long should turn into digest
|
118
|
-
path = 'xyz' * 123
|
119
|
-
req = Sinew::Request.new(sinew, 'get', "http://host/#{path}")
|
120
|
-
assert_equal "host/#{Digest::MD5.hexdigest(path)}", req.cache_key
|
121
|
-
end
|
122
|
-
|
123
|
-
def test_before_generate_cache_key
|
124
|
-
# arity 1
|
125
|
-
sinew.runtime_options.before_generate_cache_key = method(:redact_cache_key)
|
126
|
-
req = Sinew::Request.new(sinew, 'get', 'http://host', query: { secret: 'xyz' })
|
127
|
-
assert_equal 'host/secret=redacted', req.cache_key
|
128
|
-
|
129
|
-
# arity 2
|
130
|
-
sinew.runtime_options.before_generate_cache_key = method(:add_scheme)
|
131
|
-
req = Sinew::Request.new(sinew, 'get', 'https://host/gub')
|
132
|
-
assert_equal 'host/https,gub', req.cache_key
|
133
|
-
end
|
134
|
-
|
135
|
-
def test_urls
|
136
|
-
# simple
|
137
|
-
req = Sinew::Request.new(sinew, 'get', 'https://host')
|
138
|
-
assert_equal 'https://host', req.uri.to_s
|
139
|
-
|
140
|
-
# with query
|
141
|
-
req = Sinew::Request.new(sinew, 'get', 'https://host', query: { a: 1 })
|
142
|
-
assert_equal 'https://host?a=1', req.uri.to_s
|
143
|
-
|
144
|
-
# entity decoding
|
145
|
-
req = Sinew::Request.new(sinew, 'get', 'https://host?a=<5')
|
146
|
-
assert_equal 'https://host?a=%3C5', req.uri.to_s
|
147
|
-
|
148
|
-
# sloppy urls
|
149
|
-
req = Sinew::Request.new(sinew, 'get', 'https://host?a=b c&d=f\'g')
|
150
|
-
assert_equal 'https://host?a=b%20c&d=f%27g', req.uri.to_s
|
151
|
-
end
|
152
|
-
|
153
|
-
def redact_cache_key(key)
|
154
|
-
key[:query].gsub!(/secret=[^&]+/, 'secret=redacted')
|
155
|
-
key
|
156
|
-
end
|
157
|
-
protected :redact_cache_key
|
158
|
-
|
159
|
-
def add_scheme(key, uri)
|
160
|
-
key[:scheme] = uri.scheme
|
161
|
-
key
|
162
|
-
end
|
163
|
-
protected :add_scheme
|
164
|
-
end
|
data/test/test_utf8.rb
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
require_relative 'test_helper'
|
2
|
-
|
3
|
-
class TestRequests < MiniTest::Test
|
4
|
-
def test_get
|
5
|
-
# network (or stub)
|
6
|
-
sinew.dsl.get('http://httpbin.org/get')
|
7
|
-
assert_equal 'UTF-8', sinew.dsl.raw.encoding.name
|
8
|
-
|
9
|
-
# disk
|
10
|
-
sinew.dsl.get('http://httpbin.org/get')
|
11
|
-
assert_equal 'UTF-8', sinew.dsl.raw.encoding.name
|
12
|
-
end
|
13
|
-
|
14
|
-
def test_utf8
|
15
|
-
skip if !test_network?
|
16
|
-
|
17
|
-
# network
|
18
|
-
sinew.dsl.get('http://httpbin.org/encoding/utf8')
|
19
|
-
assert_equal 'UTF-8', sinew.dsl.raw.encoding.name
|
20
|
-
assert_match(/∑/, sinew.dsl.raw)
|
21
|
-
|
22
|
-
# disk
|
23
|
-
sinew.dsl.get('http://httpbin.org/encoding/utf8')
|
24
|
-
assert_equal 'UTF-8', sinew.dsl.raw.encoding.name
|
25
|
-
assert_match(/∑/, sinew.dsl.raw)
|
26
|
-
end
|
27
|
-
|
28
|
-
def test_encode
|
29
|
-
skip if !test_network?
|
30
|
-
|
31
|
-
# network
|
32
|
-
sinew.dsl.get('https://www.google.co.jp')
|
33
|
-
assert_equal 'UTF-8', sinew.dsl.raw.encoding.name
|
34
|
-
|
35
|
-
# disk
|
36
|
-
sinew.dsl.get('https://www.google.co.jp')
|
37
|
-
assert_equal 'UTF-8', sinew.dsl.raw.encoding.name
|
38
|
-
end
|
39
|
-
end
|