jsonapi-grader 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/jsonapi-grader +11 -12
- data/spec/content_negotiation_spec.rb +24 -0
- data/spec/fetch_single_resource_spec.rb +33 -0
- data/spec/fetching_resource_collection_spec.rb +42 -0
- data/spec/include_spec.rb +37 -0
- data/spec/jsonapi_object_spec.rb +7 -0
- data/spec/links_spec.rb +7 -0
- data/spec/meta_spec.rb +7 -0
- data/spec/relationships_spec.rb +30 -0
- data/spec/spec_helper.rb +105 -0
- data/spec/support/http_helpers.rb +52 -0
- metadata +55 -16
- data/lib/jsonapi/grader.rb +0 -2
- data/lib/jsonapi/grader/serialization_grader.rb +0 -64
- data/lib/jsonapi/grader/server/scenarii.rb +0 -1
- data/lib/jsonapi/grader/server/scenarii/empty_collection.rb +0 -45
- data/lib/jsonapi/grader/server/scenario.rb +0 -31
- data/lib/jsonapi/grader/server_grader.rb +0 -41
- data/scenarii/empty_collection.json +0 -3
- data/scenarii/null_data.json +0 -3
- data/scenarii/simple_resource.json +0 -6
- data/scenarii/simple_resource_attributes.json +0 -10
- data/scenarii/simple_resource_jsonapi_object.json +0 -12
- data/scenarii/simple_resource_meta.json +0 -9
- data/scenarii_serialization.json +0 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 56b6e8fd7709f1e2781f92cee90c07631c70a498
|
4
|
+
data.tar.gz: 27dfc9812dbd48b4d376be6cd9f7a4b4b753b826
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 050674d6228e3df1cdc4e51ebae9486ad5c3bf6481b81cb70af2c1e71144bbc43f2873b042db0cb7e6049f0ff9e93c4f9e241534f27ca856833698d81220e603
|
7
|
+
data.tar.gz: c1325c3d78e66fa8f668ec5fbd9feaa6608a37995d7373468711cf5570461c3921dcdb4f347cc981373c09a22436efd5a43a6abf2db104e18069f530cb8b5405
|
data/bin/jsonapi-grader
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'rspec'
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
implementation_dir: ARGV[1])
|
10
|
-
when 'server'
|
11
|
-
JSONAPI::Grader::ServerGrader.new(host: ARGV[1])
|
12
|
-
else
|
13
|
-
raise 'Unknown arguments'
|
14
|
-
end
|
6
|
+
if ARGV.size != 1
|
7
|
+
$stderr.puts 'Usage: jsonapi-grader http://api.example.com'
|
8
|
+
exit(-1)
|
9
|
+
end
|
15
10
|
|
16
|
-
|
11
|
+
ENV['HOST'] = ARGV[0]
|
12
|
+
|
13
|
+
dir = File.expand_path(File.dirname(__FILE__) + '/../spec')
|
14
|
+
|
15
|
+
RSpec::Core::Runner.run(['-I', dir, '-r', 'spec_helper', dir], $stderr, $stdout)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
RSpec.describe 'content negotiation' do
|
2
|
+
it 'responds with correct content type' do
|
3
|
+
get '/books/1'
|
4
|
+
|
5
|
+
expect(response_status).to be 200
|
6
|
+
expect(response['Content-Type']).to eq('application/vnd.api+json')
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'when requesting with media type parameters in content type' do
|
10
|
+
it 'responds with 415' do
|
11
|
+
get '/books/1', 'Content-Type' => 'application/vnd.api+json; foo=bar'
|
12
|
+
|
13
|
+
expect(response_status).to be 415
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'when requesting with media type parameters in accept' do
|
18
|
+
it 'responds with 406' do
|
19
|
+
get '/books/1', 'Accept' => 'application/vnd.api+json; foo=bar'
|
20
|
+
|
21
|
+
expect(response_status).to be 406
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
RSpec.describe 'fetching a single resource' do
|
2
|
+
it 'fetches a single resource' do
|
3
|
+
get '/books/1'
|
4
|
+
|
5
|
+
expect(response_status).to be 200
|
6
|
+
expect(response_body['data']).to have_type('books')
|
7
|
+
expect(response_body['data']).to have_id('1')
|
8
|
+
expect(response_body['data']).to have_attribute('title')
|
9
|
+
.with_value('The Fellowship of the Ring')
|
10
|
+
expect(response_body['data']).to have_relationship('author')
|
11
|
+
expect(response_body['data']).to have_relationship('series')
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'fetches a single resource with meta' do
|
15
|
+
get '/books/1'
|
16
|
+
|
17
|
+
expect(response_status).to be 200
|
18
|
+
expect(response_body['data']).to have_meta('foo' => 'bar')
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'fetches a single resource with links' do
|
22
|
+
get '/books/1'
|
23
|
+
|
24
|
+
expect(response_status).to be 200
|
25
|
+
expect(response_body['data']).to have_link('self')
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'fetches a non-existent resource' do
|
29
|
+
get '/books/foo'
|
30
|
+
|
31
|
+
expect(response_status).to be 404
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
RSpec.describe 'fetching a resource collection' do
|
2
|
+
it 'fetches a resource collection' do
|
3
|
+
get '/books'
|
4
|
+
|
5
|
+
expect(response_status).to be 200
|
6
|
+
expect(response_body['data']).to all(have_type('books'))
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'fetches a single resource with includes' do
|
10
|
+
get '/books/1?include=author'
|
11
|
+
|
12
|
+
expect(response_status).to be 200
|
13
|
+
expect(response_body['data']).to have_type('books')
|
14
|
+
expect(response_body['data']).to have_id('1')
|
15
|
+
expect(response_body['data']).to have_relationship('author')
|
16
|
+
.with_data('id' => '1', 'type' => 'authors')
|
17
|
+
expect(response_body['data']).to have_relationship('series')
|
18
|
+
author = response_body['data']['relationships']['author']['data']
|
19
|
+
expect(response_body['included'])
|
20
|
+
.to include(have_type('authors').and have_id(author['id']))
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'fetches a single resource with meta' do
|
24
|
+
get '/books/1'
|
25
|
+
|
26
|
+
expect(response_status).to be 200
|
27
|
+
expect(response_body['data']).to have_meta('foo' => 'bar')
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'fetches a single resource with links' do
|
31
|
+
get '/books/1'
|
32
|
+
|
33
|
+
expect(response_status).to be 200
|
34
|
+
expect(response_body['data']).to have_link('self')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'fetches a non-existent resource' do
|
38
|
+
get '/books/foo'
|
39
|
+
|
40
|
+
expect(response_status).to be 404
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
RSpec.describe 'include' do
|
2
|
+
it 'fetches a single resource with includes' do
|
3
|
+
get '/books/1?include=author'
|
4
|
+
|
5
|
+
expect(response_status).to be 200
|
6
|
+
expect(response_body['data']).to have_type('books')
|
7
|
+
expect(response_body['data']).to have_id('1')
|
8
|
+
expect(response_body['data'])
|
9
|
+
.to have_relationship('author').with_data('id' => '1', 'type' => 'authors')
|
10
|
+
expect(response_body['data']).to have_relationship('series')
|
11
|
+
author = response_body['data']['relationships']['author']['data']
|
12
|
+
expect(response_body['included'])
|
13
|
+
.to include(have_type('authors').and have_id(author['id']))
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'fetches a single resource with includes and stripped out relationship field' do
|
17
|
+
get '/books/1?include=author&fields[books]=title'
|
18
|
+
|
19
|
+
expect(response_status).to be 200
|
20
|
+
expect(response_body['data']).to have_type('books')
|
21
|
+
expect(response_body['data']).to have_id('1')
|
22
|
+
expect(response_body['data']).not_to have_relationship('author')
|
23
|
+
expect(response_body['data']).not_to have_relationship('series')
|
24
|
+
expect(response_body['included'])
|
25
|
+
.to include(have_type('authors'))
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'includes series relationship by default' do
|
29
|
+
get '/books/1'
|
30
|
+
|
31
|
+
expect(response_status).to be 200
|
32
|
+
expect(response_body['data']).to have_type('books')
|
33
|
+
expect(response_body['data']).to have_id('1')
|
34
|
+
expect(response_body['data']).to have_relationship('series')
|
35
|
+
expect(response_body['included']).to include(have_type('series'))
|
36
|
+
end
|
37
|
+
end
|
data/spec/links_spec.rb
ADDED
data/spec/meta_spec.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
RSpec.describe 'relationships' do
|
2
|
+
it 'has a relationship meta' do
|
3
|
+
get '/books/1'
|
4
|
+
|
5
|
+
expect(response_status).to be 200
|
6
|
+
|
7
|
+
expect(response_body['data']).to have_relationship('author')
|
8
|
+
expect(response_body['data']['relationships']['author'])
|
9
|
+
.to have_meta('foo' => 'bar')
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'has relationship links' do
|
13
|
+
get '/books/1'
|
14
|
+
|
15
|
+
expect(response_status).to be 200
|
16
|
+
|
17
|
+
expect(response_body['data']).to have_relationship('author')
|
18
|
+
expect(response_body['data']['relationships']['author'])
|
19
|
+
.to have_links(:related)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'does not have linkage data when relationship not included' do
|
23
|
+
get '/books/1'
|
24
|
+
|
25
|
+
expect(response_status).to be 200
|
26
|
+
|
27
|
+
expect(response_body['data']).to have_relationship('author')
|
28
|
+
expect(response_body['data']['relationships']['author']['data']).to be_nil
|
29
|
+
end
|
30
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
4
|
+
# this file to always be loaded, without a need to explicitly require it in any
|
5
|
+
# files.
|
6
|
+
#
|
7
|
+
# Given that it is always loaded, you are encouraged to keep this file as
|
8
|
+
# light-weight as possible. Requiring heavyweight dependencies from this file
|
9
|
+
# will add to the boot time of your test suite on EVERY test run, even for an
|
10
|
+
# individual file that may not need all of that loaded. Instead, consider making
|
11
|
+
# a separate helper file that requires the additional dependencies and performs
|
12
|
+
# the additional setup, and require it from the spec files that actually need
|
13
|
+
# it.
|
14
|
+
#
|
15
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
16
|
+
RSpec.configure do |config|
|
17
|
+
# rspec-expectations config goes here. You can use an alternate
|
18
|
+
# assertion/expectation library such as wrong or the stdlib/minitest
|
19
|
+
# assertions if you prefer.
|
20
|
+
config.expect_with :rspec do |expectations|
|
21
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
22
|
+
# and `failure_message` of custom matchers include text for helper methods
|
23
|
+
# defined using `chain`, e.g.:
|
24
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
25
|
+
# # => "be bigger than 2 and smaller than 4"
|
26
|
+
# ...rather than:
|
27
|
+
# # => "be bigger than 2"
|
28
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
29
|
+
end
|
30
|
+
|
31
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
32
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
33
|
+
config.mock_with :rspec do |mocks|
|
34
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
35
|
+
# a real object. This is generally recommended, and will default to
|
36
|
+
# `true` in RSpec 4.
|
37
|
+
mocks.verify_partial_doubles = true
|
38
|
+
end
|
39
|
+
|
40
|
+
# This option will default to `:apply_to_host_groups` in RSpec 4 (and will
|
41
|
+
# have no way to turn it off -- the option exists only for backwards
|
42
|
+
# compatibility in RSpec 3). It causes shared context metadata to be
|
43
|
+
# inherited by the metadata hash of host groups and examples, rather than
|
44
|
+
# triggering implicit auto-inclusion in groups with matching metadata.
|
45
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
46
|
+
|
47
|
+
# The settings below are suggested to provide a good initial experience
|
48
|
+
# with RSpec, but feel free to customize to your heart's content.
|
49
|
+
=begin
|
50
|
+
# This allows you to limit a spec run to individual examples or groups
|
51
|
+
# you care about by tagging them with `:focus` metadata. When nothing
|
52
|
+
# is tagged with `:focus`, all examples get run. RSpec also provides
|
53
|
+
# aliases for `it`, `describe`, and `context` that include `:focus`
|
54
|
+
# metadata: `fit`, `fdescribe` and `fcontext`, respectively.
|
55
|
+
config.filter_run_when_matching :focus
|
56
|
+
|
57
|
+
# Allows RSpec to persist some state between runs in order to support
|
58
|
+
# the `--only-failures` and `--next-failure` CLI options. We recommend
|
59
|
+
# you configure your source control system to ignore this file.
|
60
|
+
config.example_status_persistence_file_path = "spec/examples.txt"
|
61
|
+
|
62
|
+
# Limits the available syntax to the non-monkey patched syntax that is
|
63
|
+
# recommended. For more details, see:
|
64
|
+
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
65
|
+
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
66
|
+
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
67
|
+
config.disable_monkey_patching!
|
68
|
+
|
69
|
+
# This setting enables warnings. It's recommended, but in some cases may
|
70
|
+
# be too noisy due to issues in dependencies.
|
71
|
+
config.warnings = true
|
72
|
+
|
73
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
74
|
+
# file, and it's useful to allow more verbose output when running an
|
75
|
+
# individual spec file.
|
76
|
+
if config.files_to_run.one?
|
77
|
+
# Use the documentation formatter for detailed output,
|
78
|
+
# unless a formatter has already been configured
|
79
|
+
# (e.g. via a command-line flag).
|
80
|
+
config.default_formatter = "doc"
|
81
|
+
end
|
82
|
+
|
83
|
+
# Print the 10 slowest examples and example groups at the
|
84
|
+
# end of the spec run, to help surface which specs are running
|
85
|
+
# particularly slow.
|
86
|
+
config.profile_examples = 10
|
87
|
+
|
88
|
+
# Run specs in random order to surface order dependencies. If you find an
|
89
|
+
# order dependency and want to debug it, you can fix the order by providing
|
90
|
+
# the seed, which is printed after each run.
|
91
|
+
# --seed 1234
|
92
|
+
config.order = :random
|
93
|
+
|
94
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
95
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
96
|
+
# test failures related to randomization by passing the same `--seed` value
|
97
|
+
# as the one that triggered the failure.
|
98
|
+
Kernel.srand config.seed
|
99
|
+
=end
|
100
|
+
|
101
|
+
require 'support/http_helpers'
|
102
|
+
config.include HTTPHelpers
|
103
|
+
require 'jsonapi/rspec'
|
104
|
+
config.include JSONAPI::RSpec
|
105
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module HTTPHelpers
|
5
|
+
def request(klass, url, headers)
|
6
|
+
uri = URI(ENV['HOST'] + url)
|
7
|
+
req = klass.new(uri)
|
8
|
+
req['Accept'] = 'application/vnd.api+json'
|
9
|
+
|
10
|
+
yield(req) if block_given?
|
11
|
+
|
12
|
+
headers.each { |k, v| req[k.to_s] = v }
|
13
|
+
|
14
|
+
@_response = Net::HTTP.start(uri.hostname, uri.port) do |http|
|
15
|
+
http.request(req)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def get(url, headers = {})
|
20
|
+
request(Net::HTTP::Get, url, headers)
|
21
|
+
end
|
22
|
+
|
23
|
+
def post(url, payload, headers = {})
|
24
|
+
request(Net::HTTP::Post, url, headers) do |req|
|
25
|
+
req['Content-Type'] = 'application/vnd.api+json'
|
26
|
+
req.body = payload.to_json
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def patch(url, payload, headers = {})
|
31
|
+
request(Net::HTTP::Patch, url, headers) do |req|
|
32
|
+
req['Content-Type'] = 'application/vnd.api+json'
|
33
|
+
req.body = payload.to_json
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def delete(url, headers = {})
|
38
|
+
request(Net::HTTP::Delete, url, headers)
|
39
|
+
end
|
40
|
+
|
41
|
+
def response
|
42
|
+
@_response
|
43
|
+
end
|
44
|
+
|
45
|
+
def response_body
|
46
|
+
JSON.parse(response.body)
|
47
|
+
end
|
48
|
+
|
49
|
+
def response_status
|
50
|
+
response.code.to_i
|
51
|
+
end
|
52
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jsonapi-grader
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lucas Hosseini
|
@@ -9,7 +9,49 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
date: 2017-08-02 00:00:00.000000000 Z
|
12
|
-
dependencies:
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: jsonapi-rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
13
55
|
description: Ensure compliance of your JSON API library.
|
14
56
|
email: lucas.hosseini@gmail.com
|
15
57
|
executables:
|
@@ -17,20 +59,17 @@ executables:
|
|
17
59
|
extensions: []
|
18
60
|
extra_rdoc_files: []
|
19
61
|
files:
|
20
|
-
- "./
|
21
|
-
- "./
|
22
|
-
- "./
|
23
|
-
- "./
|
24
|
-
- "./
|
25
|
-
- "./
|
26
|
-
- "./
|
27
|
-
- "./
|
28
|
-
- "./
|
29
|
-
- "./
|
30
|
-
- "./scenarii/simple_resource_jsonapi_object.json"
|
31
|
-
- "./scenarii/simple_resource_meta.json"
|
62
|
+
- "./spec/content_negotiation_spec.rb"
|
63
|
+
- "./spec/fetch_single_resource_spec.rb"
|
64
|
+
- "./spec/fetching_resource_collection_spec.rb"
|
65
|
+
- "./spec/include_spec.rb"
|
66
|
+
- "./spec/jsonapi_object_spec.rb"
|
67
|
+
- "./spec/links_spec.rb"
|
68
|
+
- "./spec/meta_spec.rb"
|
69
|
+
- "./spec/relationships_spec.rb"
|
70
|
+
- "./spec/spec_helper.rb"
|
71
|
+
- "./spec/support/http_helpers.rb"
|
32
72
|
- bin/jsonapi-grader
|
33
|
-
- scenarii_serialization.json
|
34
73
|
homepage: https://github.com/beauby/jsonapi-grader
|
35
74
|
licenses:
|
36
75
|
- MIT
|
@@ -51,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
51
90
|
version: '0'
|
52
91
|
requirements: []
|
53
92
|
rubyforge_project:
|
54
|
-
rubygems_version: 2.6.
|
93
|
+
rubygems_version: 2.6.13
|
55
94
|
signing_key:
|
56
95
|
specification_version: 4
|
57
96
|
summary: Grade jsonapi.org implementations.
|
data/lib/jsonapi/grader.rb
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
require 'json'
|
2
|
-
|
3
|
-
module JSONAPI
|
4
|
-
module Grader
|
5
|
-
class SerializationGrader
|
6
|
-
def initialize(scenarii_file = 'scenarii_serialization.json', options = {})
|
7
|
-
scenarii_file = File.expand_path("../../../#{scenarii_file}", __dir__)
|
8
|
-
@scenarii = JSON.parse(File.read(scenarii_file))
|
9
|
-
@implementation_dir = options[:implementation_dir]
|
10
|
-
end
|
11
|
-
|
12
|
-
def grade
|
13
|
-
compliance = true
|
14
|
-
score = 0
|
15
|
-
max_score = 0
|
16
|
-
@scenarii.each do |scenario|
|
17
|
-
current_score = score_scenario(scenario)
|
18
|
-
compliance = false if scenario['required'] && current_score == 0
|
19
|
-
max_score += scenario['score']
|
20
|
-
score += current_score
|
21
|
-
end
|
22
|
-
|
23
|
-
STDERR.puts "Compliance: #{compliance}"
|
24
|
-
score_percent = (100.0 * score / max_score).round(2)
|
25
|
-
STDERR.puts "Score: #{score} / #{max_score} (#{score_percent}%)"
|
26
|
-
|
27
|
-
score
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def score_scenario(scenario)
|
33
|
-
document_file = File.expand_path("../../../#{scenario['file']}", __dir__)
|
34
|
-
reference_document = normalize(JSON.parse(File.read(document_file)))
|
35
|
-
implementation = "#{@implementation_dir}/#{scenario['name']}"
|
36
|
-
STDERR.print "Running scenario #{scenario['name']}... "
|
37
|
-
$stderr.flush
|
38
|
-
unless File.exists?(implementation)
|
39
|
-
STDERR.puts "not implemented"
|
40
|
-
return 0
|
41
|
-
end
|
42
|
-
|
43
|
-
actual_document = normalize(JSON.parse(`#{implementation}`))
|
44
|
-
if reference_document == actual_document
|
45
|
-
STDERR.puts "passed"
|
46
|
-
return scenario['score']
|
47
|
-
else
|
48
|
-
STDERR.puts "failed"
|
49
|
-
STDERR.puts "Expected: #{reference_document}"
|
50
|
-
STDERR.puts "Got: #{actual_document}"
|
51
|
-
return 0
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def normalize(document)
|
56
|
-
return document unless document.key?('included')
|
57
|
-
|
58
|
-
document['included'].sort do |a, b|
|
59
|
-
[a['type'], a['id']] <=> [b['type'], b['id']]
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
@@ -1 +0,0 @@
|
|
1
|
-
require 'jsonapi/grader/server/scenarii/empty_collection'
|
@@ -1,45 +0,0 @@
|
|
1
|
-
require 'net/http'
|
2
|
-
require 'jsonapi/grader/server/scenario'
|
3
|
-
|
4
|
-
module JSONAPI
|
5
|
-
module Grader
|
6
|
-
module Server
|
7
|
-
class EmptyCollectionScenario < Scenario
|
8
|
-
def name
|
9
|
-
'empty_collection'
|
10
|
-
end
|
11
|
-
|
12
|
-
def description
|
13
|
-
'The endpoint /empty_collection should return an empty collection.'
|
14
|
-
end
|
15
|
-
|
16
|
-
def score
|
17
|
-
100
|
18
|
-
end
|
19
|
-
|
20
|
-
def required
|
21
|
-
true
|
22
|
-
end
|
23
|
-
|
24
|
-
def call(host)
|
25
|
-
uri = URI("#{host}/empty_collection")
|
26
|
-
res = Net::HTTP.start(uri.hostname, uri.port) do |http|
|
27
|
-
req = Net::HTTP::Get.new(uri)
|
28
|
-
req['Accept'] = 'application/vnd.api+json'
|
29
|
-
|
30
|
-
http.request(req)
|
31
|
-
end
|
32
|
-
|
33
|
-
unless res['Content-Type'] == 'application/vnd.api+json'
|
34
|
-
fail 'Expected Content-Type header to equal application/vnd.api+json'
|
35
|
-
end
|
36
|
-
|
37
|
-
body = JSON.parse(res.body)
|
38
|
-
unless body['data'] == []
|
39
|
-
fail 'Expected empty collection'
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
module JSONAPI
|
2
|
-
module Grader
|
3
|
-
module Server
|
4
|
-
class Scenario
|
5
|
-
def call(host)
|
6
|
-
raise 'Not implemented'
|
7
|
-
end
|
8
|
-
|
9
|
-
def score
|
10
|
-
raise 'Not implemented'
|
11
|
-
end
|
12
|
-
|
13
|
-
def name
|
14
|
-
raise 'Not implemented'
|
15
|
-
end
|
16
|
-
|
17
|
-
def description
|
18
|
-
raise 'Not implemented'
|
19
|
-
end
|
20
|
-
|
21
|
-
def endpoint
|
22
|
-
raise 'Not implemented'
|
23
|
-
end
|
24
|
-
|
25
|
-
def required
|
26
|
-
raise 'Not implemented'
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
require 'jsonapi/grader/server/scenarii'
|
2
|
-
|
3
|
-
module JSONAPI
|
4
|
-
module Grader
|
5
|
-
class ServerGrader
|
6
|
-
SCENARII = [Server::EmptyCollectionScenario.new]
|
7
|
-
|
8
|
-
def initialize(options = {})
|
9
|
-
@host = options[:host]
|
10
|
-
end
|
11
|
-
|
12
|
-
def grade
|
13
|
-
compliance = true
|
14
|
-
score = 0
|
15
|
-
max_score = 0
|
16
|
-
SCENARII.each do |scenario|
|
17
|
-
STDERR.print "Running scenario #{scenario.name}... "
|
18
|
-
$stderr.flush
|
19
|
-
|
20
|
-
begin
|
21
|
-
scenario.call(@host)
|
22
|
-
STDERR.puts "passed"
|
23
|
-
score += scenario.score
|
24
|
-
rescue Exception => error
|
25
|
-
STDERR.puts "failed"
|
26
|
-
STDERR.puts scenario.description
|
27
|
-
STDERR.puts ">>> #{error}"
|
28
|
-
compliance = false if scenario.required
|
29
|
-
end
|
30
|
-
max_score += scenario.score
|
31
|
-
end
|
32
|
-
|
33
|
-
STDERR.puts "Compliance: #{compliance}"
|
34
|
-
score_percent = (100.0 * score / max_score).round(2)
|
35
|
-
STDERR.puts "Score: #{score} / #{max_score} (#{score_percent}%)"
|
36
|
-
|
37
|
-
score
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
data/scenarii/null_data.json
DELETED
data/scenarii_serialization.json
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
[
|
2
|
-
{
|
3
|
-
"name": "null_data",
|
4
|
-
"file": "scenarii/null_data.json",
|
5
|
-
"score": 100,
|
6
|
-
"required": true
|
7
|
-
},
|
8
|
-
{
|
9
|
-
"name": "empty_collection",
|
10
|
-
"file": "scenarii/empty_collection.json",
|
11
|
-
"score": 100,
|
12
|
-
"required": true
|
13
|
-
},
|
14
|
-
{
|
15
|
-
"name": "simple_resource",
|
16
|
-
"file": "scenarii/simple_resource.json",
|
17
|
-
"score": 100,
|
18
|
-
"required": true
|
19
|
-
},
|
20
|
-
{
|
21
|
-
"name": "simple_resource_attributes",
|
22
|
-
"file": "scenarii/simple_resource_attributes.json",
|
23
|
-
"score": 100,
|
24
|
-
"required": true
|
25
|
-
},
|
26
|
-
{
|
27
|
-
"name": "simple_resource_meta",
|
28
|
-
"file": "scenarii/simple_resource_meta.json",
|
29
|
-
"score": 10,
|
30
|
-
"required": false
|
31
|
-
},
|
32
|
-
{
|
33
|
-
"name": "simple_resource_jsonapi_object",
|
34
|
-
"file": "scenarii/simple_resource_jsonapi_object.json",
|
35
|
-
"score": 10,
|
36
|
-
"required": false
|
37
|
-
}
|
38
|
-
]
|