scorpio 0.4.6 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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