scorpio 0.5.0 → 0.6.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.
data/test/blog.rb DELETED
@@ -1,117 +0,0 @@
1
- require 'sinatra'
2
- require 'api_hammer'
3
- require 'rack/accept'
4
- require 'logger'
5
-
6
- # app
7
-
8
- class Blog < Sinatra::Base
9
- include ApiHammer::Sinatra
10
- self.supported_media_types = ['application/json']
11
- set :static, false
12
- disable :protection
13
- logpath = Pathname.new('log/test.log')
14
- FileUtils.mkdir_p(logpath.dirname)
15
- set :logger, ::Logger.new(logpath)
16
- logger.level = ::Logger::INFO
17
- define_method(:logger) { self.class.logger }
18
- use_with_lint ApiHammer::RequestLogger, logger
19
-
20
- # prevent sinatra from using Sinatra::ShowExceptions so we can use ShowTextExceptions instead
21
- set :show_exceptions, false
22
- # allow errors to bubble past sinatra up to ShowTextExceptions
23
- set :raise_errors, true
24
- # ShowTextExceptions rescues ruby exceptions and gives a response of 500 with text/plain
25
- use_with_lint ApiHammer::ShowTextExceptions, :full_error => true, :logger => logger
26
- end
27
-
28
- # models
29
-
30
- require 'active_record'
31
- ActiveRecord::Base.logger = Blog.logger
32
- dbpath = Pathname.new('tmp/blog.sqlite3')
33
- FileUtils.mkdir_p(dbpath.dirname)
34
- dbpath.unlink if dbpath.exist?
35
- ActiveRecord::Base.establish_connection(
36
- :adapter => "sqlite3",
37
- :database => dbpath
38
- )
39
-
40
- ActiveRecord::Schema.define do
41
- create_table :articles do |table|
42
- table.column :title, :string
43
- table.column :author_id, :integer
44
- end
45
-
46
- create_table :authors do |table|
47
- table.column :name, :string
48
- end
49
- end
50
-
51
- # we will namespace the models under Blog so that the top-level namespace
52
- # can be used by the scorpio model classes
53
- class Blog
54
- class Article < ActiveRecord::Base
55
- # validates_enthusiasm_of :title
56
- validate { errors.add(:title, "with gusto!") if title && !title[/!\z/] }
57
- end
58
- class Author < ActiveRecord::Base
59
- end
60
- end
61
-
62
- # controllers
63
-
64
- class Blog
65
- get '/v1/articles' do
66
- check_accept
67
-
68
- articles = Blog::Article.all
69
- format_response(200, articles.map(&:serializable_hash))
70
- end
71
- get '/v1/articles_with_root' do
72
- check_accept
73
-
74
- articles = Blog::Article.all
75
- body = {
76
- # this is on the response schema, an array with items whose id indicates they are articles
77
- 'articles' => articles.map(&:serializable_hash),
78
- # in the response schema, a single article
79
- 'best_article' => articles.last.serializable_hash,
80
- # this is on the response schema, not indicating it is an article
81
- 'version' => 'v1',
82
- # this is not in the response schema at all
83
- 'note' => 'hi!',
84
- }
85
- format_response(200, body)
86
- end
87
- get '/v1/articles/:id' do |id|
88
- article = find_or_halt(Blog::Article, id: id)
89
- format_response(200, article.serializable_hash)
90
- end
91
- post '/v1/articles' do
92
- article = Blog::Article.create(parsed_body)
93
- if article.persisted?
94
- format_response(200, article.serializable_hash)
95
- else
96
- halt_unprocessable_entity(article.errors.messages)
97
- end
98
- end
99
- patch '/v1/articles/:id' do |id|
100
- article_attrs = parsed_body
101
- article = find_or_halt(Blog::Article, id: id)
102
-
103
- article.assign_attributes(article_attrs)
104
- saved = article.save
105
- if saved
106
- format_response(200, article.serializable_hash)
107
- else
108
- halt_unprocessable_entity(article.errors.messages)
109
- end
110
- end
111
-
112
- require 'database_cleaner'
113
- post '/v1/clean' do
114
- DatabaseCleaner.clean_with(:truncation)
115
- format_response(200, nil)
116
- end
117
- end
@@ -1,67 +0,0 @@
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
@@ -1,49 +0,0 @@
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
data/test/scorpio_test.rb DELETED
@@ -1,105 +0,0 @@
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
data/test/test_helper.rb DELETED
@@ -1,86 +0,0 @@
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'