scorpio 0.4.6 → 0.5.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,67 @@
1
+ discoveryVersion: v1
2
+ name: blog
3
+ title: "Scorpio Blog"
4
+ description: "REST service for the Scorpio Blog"
5
+ documentationLink: https://github.com/notEthan/scorpio
6
+ servicePath: /v1
7
+ resources:
8
+ articles:
9
+ methods:
10
+ index:
11
+ path: articles
12
+ httpMethod: GET
13
+ response:
14
+ type: array
15
+ items:
16
+ $ref: https://blog.example.com/schemas/articles/v1.0.0
17
+ index_with_root:
18
+ path: articles_with_root
19
+ httpMethod: GET
20
+ response:
21
+ type: object
22
+ properties:
23
+ articles:
24
+ type: array
25
+ items:
26
+ $ref: https://blog.example.com/schemas/articles/v1.0.0
27
+ best_article:
28
+ $ref: https://blog.example.com/schemas/articles/v1.0.0
29
+ version:
30
+ type: string
31
+ read:
32
+ path: articles/{id}
33
+ httpMethod: GET
34
+ response:
35
+ $ref: https://blog.example.com/schemas/articles/v1.0.0
36
+ post:
37
+ path: articles
38
+ httpMethod: POST
39
+ request:
40
+ $ref: https://blog.example.com/schemas/articles/v1.0.0
41
+ response:
42
+ $ref: https://blog.example.com/schemas/articles/v1.0.0
43
+ patch:
44
+ path: articles/{id}
45
+ httpMethod: PATCH
46
+ request:
47
+ $ref: https://blog.example.com/schemas/articles/v1.0.0
48
+ response:
49
+ $ref: https://blog.example.com/schemas/articles/v1.0.0
50
+ clean:
51
+ methods:
52
+ clean:
53
+ path: clean
54
+ httpMethod: POST
55
+ response:
56
+ {}
57
+ schemas:
58
+ articles:
59
+ id: https://blog.example.com/schemas/articles/v1.0.0
60
+ type: object
61
+ properties:
62
+ id:
63
+ type: integer
64
+ title:
65
+ type: string
66
+ author_id:
67
+ type: integer
@@ -0,0 +1,49 @@
1
+ require 'logger'
2
+ require 'api_hammer'
3
+
4
+ # this is a virtual model to parent models representing resources of the blog. it sets
5
+ # up connection information including base url, custom middleware or adapter for faraday.
6
+ # it describes the API by setting the API document, but this class itself represents no
7
+ # resources - it sets no resource_name and defines no schema_keys.
8
+ class BlogModel < Scorpio::ResourceBase
9
+ define_inheritable_accessor(:logger)
10
+ logpath = Pathname.new('log/test.log')
11
+ FileUtils.mkdir_p(logpath.dirname)
12
+ self.logger = ::Logger.new(logpath)
13
+
14
+ if ENV['SCORPIO_API_DESCRIPTION_FORMAT'] == 'rest_description'
15
+ self.openapi_document = Scorpio::Google::RestDescription.new_jsi(YAML.load_file('test/blog.rest_description.yml')).to_openapi_document
16
+ self.base_url = File.join("http://localhost:#{$blog_port || raise(Bug)}/", openapi_document.basePath)
17
+ elsif ENV['SCORPIO_API_DESCRIPTION_FORMAT'] == 'openapi2'
18
+ self.openapi_document = YAML.load_file('test/blog.openapi2.yml')
19
+ self.base_url = File.join("http://localhost:#{$blog_port || raise(Bug)}/", openapi_document.basePath)
20
+ elsif ENV['SCORPIO_API_DESCRIPTION_FORMAT'] == 'openapi3' || ENV['SCORPIO_API_DESCRIPTION_FORMAT'].nil?
21
+ self.openapi_document = YAML.load_file('test/blog.openapi3.yml')
22
+ self.server_variables = {
23
+ 'scheme' => 'http',
24
+ 'host' => 'localhost',
25
+ 'port' => $blog_port || raise(Bug, '$blog_port is nil'),
26
+ }
27
+ else
28
+ abort("bad SCORPIO_API_DESCRIPTION_FORMAT")
29
+ end
30
+ self.faraday_builder = -> (conn) {
31
+ conn.request(:api_hammer_request_logger, logger)
32
+ }
33
+ end
34
+
35
+ # this is a model of Article, a resource of the blog API. it sets the resource_name
36
+ # to the key of the 'resources' section of the API (described by the api document
37
+ # specified to BlogModel)
38
+ class Article < BlogModel
39
+ self.tag_name = 'articles'
40
+ if openapi_document.v2?
41
+ self.represented_schemas = [openapi_document.definitions['articles']]
42
+ else
43
+ self.represented_schemas = [openapi_document.components.schemas['articles']]
44
+ end
45
+ end
46
+
47
+ class BlogClean < BlogModel
48
+ self.tag_name = 'clean'
49
+ end
@@ -0,0 +1,105 @@
1
+ require_relative 'test_helper'
2
+
3
+ class ScorpioTest < Minitest::Test
4
+ def test_that_it_has_a_version_number
5
+ refute_nil ::Scorpio::VERSION
6
+ end
7
+ end
8
+
9
+ describe 'blog' do
10
+ let(:blog_article) { Article.post('title' => "sports!") }
11
+
12
+ it 'indexes articles' do
13
+ blog_article
14
+
15
+ articles = Article.index
16
+
17
+ assert_equal(1, articles.size)
18
+ article = articles[0]
19
+ assert_equal(1, article['id'])
20
+ assert(article.is_a?(Article))
21
+ assert_equal('sports!', article['title'])
22
+ assert_equal('sports!', article.title)
23
+ end
24
+ it 'indexes articles with root' do
25
+ blog_article
26
+
27
+ articles = Article.index_with_root
28
+ assert_respond_to(articles, :to_hash)
29
+ assert_equal('v1', articles['version'])
30
+ assert_equal('hi!', articles['note'])
31
+ assert_instance_of(Article, articles['best_article'])
32
+ assert_equal(articles['articles'].last, articles['best_article'])
33
+ assert_equal(1, articles['articles'].size)
34
+ article = articles['articles'][0]
35
+ assert_equal(1, article['id'])
36
+ assert(article.is_a?(Article))
37
+ assert_equal('sports!', article['title'])
38
+ assert_equal('sports!', article.title)
39
+ end
40
+ it 'reads an article' do
41
+ blog_article
42
+ article = Article.read(id: blog_article.id)
43
+ assert(article.is_a?(Article))
44
+ assert_equal('sports!', article['title'])
45
+ assert_equal('sports!', article.title)
46
+ end
47
+ it 'tries to read an article without a required path variable' do
48
+ blog_article
49
+ e = assert_raises(ArgumentError) do
50
+ Article.read({})
51
+ end
52
+ assert_equal('path /articles/{id} for operation articles.read requires path_params which were missing: ["id"]',
53
+ e.message)
54
+ e = assert_raises(ArgumentError) do
55
+ Article.read({id: ''})
56
+ end
57
+ assert_equal('path /articles/{id} for operation articles.read requires path_params which were empty: ["id"]',
58
+ e.message)
59
+ end
60
+ it 'tries to read a nonexistent article' do
61
+ err = assert_raises(Scorpio::NotFound404Error) do
62
+ Article.read(id: 99)
63
+ end
64
+ assert_equal({"article" => ["Unknown article! id: 99"]}, JSI::Typelike.as_json(err.response_object['errors']))
65
+ assert_match(/Unknown article! id: 99/, err.message)
66
+ end
67
+ it 'updates an article on the class' do
68
+ blog_article
69
+ Article.patch({id: blog_article.id, title: 'politics!'})
70
+ assert_equal('politics!', Article.read(id: blog_article.id).title)
71
+ end
72
+ it 'updates an article on the instance' do
73
+ blog_article
74
+ article = Article.read(id: blog_article.id)
75
+ article.title = 'politics!'
76
+ article.patch
77
+ assert_equal('politics!', Article.read(id: blog_article.id).title)
78
+ end
79
+ it 'updates an article with an unsuccessful response' do
80
+ blog_article
81
+ err = assert_raises(Scorpio::UnprocessableEntity422Error) do
82
+ Article.patch({id: blog_article.id, title: 'politics?'})
83
+ end
84
+ assert_equal({"title" => ["with gusto!"]}, JSI::Typelike.as_json(err.response_object['errors']))
85
+ assert_match(/with gusto!/, err.message)
86
+ assert_equal('sports!', Article.read(id: blog_article.id).title)
87
+ end
88
+ it 'instantiates an article with bad argument' do
89
+ assert_raises(ArgumentError) { Article.new("foo") }
90
+ end
91
+ it 'reports schema failure when the request does not match the request schema' do
92
+ # TODO handle blame
93
+ assert_raises(Scorpio::HTTPErrors::UnprocessableEntity422Error) do
94
+ # title is supposed to be a string
95
+ Article.post('title' => {'music' => '!'})
96
+ end
97
+ end
98
+ it 'checks equality' do
99
+ assert_equal(Article.read(id: blog_article.id), Article.read(id: blog_article.id))
100
+ end
101
+ it 'consistently keys a hash' do
102
+ hash = {Article.read(id: blog_article.id) => 0}
103
+ assert_equal(0, hash[Article.read(id: blog_article.id)])
104
+ end
105
+ end
@@ -0,0 +1,86 @@
1
+ require 'coveralls'
2
+ if Coveralls.will_run?
3
+ Coveralls.wear!
4
+ end
5
+
6
+ require 'simplecov'
7
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
8
+ require 'scorpio'
9
+
10
+ require 'bundler/setup'
11
+
12
+ # NO EXPECTATIONS
13
+ ENV["MT_NO_EXPECTATIONS"] = ''
14
+
15
+ require 'minitest/autorun'
16
+ require 'minitest/around/spec'
17
+ require 'minitest/reporters'
18
+
19
+ Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
20
+
21
+ require 'byebug'
22
+
23
+ class ScorpioSpec < Minitest::Spec
24
+ if ENV['SCORPIO_TEST_ALPHA']
25
+ # :nocov:
26
+ define_singleton_method(:test_order) { :alpha }
27
+ # :nocov:
28
+ end
29
+
30
+ around do |test|
31
+ test.call
32
+ BlogClean.clean
33
+ end
34
+
35
+ def assert_equal exp, act, msg = nil
36
+ msg = message(msg, E) { diff exp, act }
37
+ assert exp == act, msg
38
+ end
39
+ end
40
+
41
+ # register this to be the base class for specs instead of Minitest::Spec
42
+ Minitest::Spec.register_spec_type(//, ScorpioSpec)
43
+
44
+ # boot the blog application in a different process
45
+
46
+ # find a free port
47
+ server = TCPServer.new(0)
48
+ $blog_port = server.addr[1]
49
+ server.close
50
+
51
+ $blog_pid = fork do
52
+ require_relative 'blog'
53
+
54
+ STDOUT.reopen(Scorpio.root.join('log/blog_webrick_stdout.log').open('a'))
55
+ STDERR.reopen(Scorpio.root.join('log/blog_webrick_stderr.log').open('a'))
56
+
57
+ trap('INT') { ::Rack::Handler::WEBrick.shutdown }
58
+
59
+ ::Rack::Handler::WEBrick.run(::Blog, Port: $blog_port)
60
+ end
61
+
62
+ # wait for the server to become responsive
63
+ running = false
64
+ started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
65
+ timeout = 30
66
+ while !running
67
+ require 'socket'
68
+ begin
69
+ sock=TCPSocket.new('localhost', $blog_port)
70
+ running = true
71
+ sock.close
72
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPIPE
73
+ if Process.clock_gettime(Process::CLOCK_MONOTONIC) > started + timeout
74
+ raise $!.class, "Failed to connect to the server on port #{$blog_port} after #{timeout} seconds.\n\n#{$!.message}", $!.backtrace
75
+ end
76
+ sleep 2**-2
77
+ STDOUT.write('.')
78
+ end
79
+ end
80
+
81
+ Minitest.after_run do
82
+ Process.kill('INT', $blog_pid)
83
+ Process.waitpid
84
+ end
85
+
86
+ require_relative 'blog_scorpio_models'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scorpio
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.6
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ethan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-02-19 00:00:00.000000000 Z
11
+ date: 2020-07-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jsi
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.3.0
19
+ version: 0.4.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.3.0
26
+ version: 0.4.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: ur
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.1.0
33
+ version: 0.2.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.1.0
40
+ version: 0.2.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: faraday
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -56,16 +56,16 @@ dependencies:
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '10.0'
61
+ version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '10.0'
68
+ version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: minitest
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -283,6 +283,13 @@ files:
283
283
  - lib/scorpio/version.rb
284
284
  - resources/icons/AGPL-3.0.png
285
285
  - scorpio.gemspec
286
+ - test/blog.openapi2.yml
287
+ - test/blog.openapi3.yml
288
+ - test/blog.rb
289
+ - test/blog.rest_description.yml
290
+ - test/blog_scorpio_models.rb
291
+ - test/scorpio_test.rb
292
+ - test/test_helper.rb
286
293
  homepage: https://github.com/notEthan/scorpio
287
294
  licenses:
288
295
  - AGPL-3.0
@@ -306,4 +313,11 @@ rubygems_version: 3.0.6
306
313
  signing_key:
307
314
  specification_version: 4
308
315
  summary: Scorpio REST client
309
- test_files: []
316
+ test_files:
317
+ - test/blog.openapi2.yml
318
+ - test/blog.openapi3.yml
319
+ - test/blog.rb
320
+ - test/blog.rest_description.yml
321
+ - test/blog_scorpio_models.rb
322
+ - test/scorpio_test.rb
323
+ - test/test_helper.rb