jpie 1.1.0 → 1.1.1
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/Gemfile.lock +1 -1
- data/lib/json_api/testing/rspec.rb +33 -0
- data/lib/json_api/testing/shared_examples/active_storage.rb +95 -0
- data/lib/json_api/testing/shared_examples/modules.rb +24 -0
- data/lib/json_api/testing/shared_examples/resources.rb +54 -0
- data/lib/json_api/testing/shared_examples/responses.rb +113 -0
- data/lib/json_api/version.rb +1 -1
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a9afc70de5e508e561e451595fc67fdad82446fe7b79c4b565d53c8a26719c13
|
|
4
|
+
data.tar.gz: fca030ebc361a511587a1a3b152243629ec6fec26f65c929408325cedba2ca95
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6136bb0f0829358a96d5fc1daace4794ad1f3e559701d5ed6b03c63189f8925fa3e6f645e6b447809f3cdbd44596e3defc6ffda2d0f87b62d36f2c3f17ba9cc8
|
|
7
|
+
data.tar.gz: 9af6902e625ab5fa282065849bdaa89977389a4c2c86db06805de6c05b3c6325bb2a8dd4253f93bcf8a27016e77ca9b3b3adc9a805058baac1c7b403439a104f
|
data/Gemfile.lock
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Main RSpec integration for JSON:API testing support.
|
|
4
|
+
#
|
|
5
|
+
# Usage in your rails_helper.rb or spec_helper.rb:
|
|
6
|
+
#
|
|
7
|
+
# require "json_api/testing/rspec"
|
|
8
|
+
#
|
|
9
|
+
# This will:
|
|
10
|
+
# 1. Load all shared example module definitions
|
|
11
|
+
# 2. Load all shared examples for resources and responses
|
|
12
|
+
# 3. Make the modules available at the top level for convenient usage
|
|
13
|
+
#
|
|
14
|
+
# After requiring, you can use shared examples like:
|
|
15
|
+
#
|
|
16
|
+
# it_behaves_like [ Jpie, Resource, :has_attributes ].to_s, :name, :email
|
|
17
|
+
# it_behaves_like [ Jpie, Response, :data ].to_s, type: "users"
|
|
18
|
+
|
|
19
|
+
require_relative "shared_examples/modules"
|
|
20
|
+
|
|
21
|
+
# Load shared examples
|
|
22
|
+
Dir[File.join(__dir__, "shared_examples", "*.rb")].each do |file|
|
|
23
|
+
require file unless file.end_with?("modules.rb")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Make modules available at top level when included
|
|
27
|
+
RSpec.configure do |config|
|
|
28
|
+
config.include(Module.new do
|
|
29
|
+
%i[Jpie Resource Response].each do |const|
|
|
30
|
+
define_method(const) { Object.const_get(const) }
|
|
31
|
+
end
|
|
32
|
+
end)
|
|
33
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Shared examples for testing ActiveStorage attachment serialization in JSON:API resources.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# RSpec.describe UserResource, type: :resource do
|
|
7
|
+
# let(:model) { users(:one) }
|
|
8
|
+
#
|
|
9
|
+
# it_behaves_like [ Jpie, Resource, :has_one_attached ].to_s, :avatar, :model
|
|
10
|
+
# it_behaves_like [ Jpie, Resource, :has_many_attached ].to_s, :documents, :model
|
|
11
|
+
# end
|
|
12
|
+
|
|
13
|
+
RSpec.shared_examples [Jpie, Resource, :has_one_attached].to_s do |rel_name, fixture_method| # rubocop:disable Metrics/BlockLength
|
|
14
|
+
describe "#{rel_name} ActiveStorage serialization" do # rubocop:disable Metrics/BlockLength
|
|
15
|
+
let(:file_fixture) { fixture_file_upload("avatar.jpg") }
|
|
16
|
+
let(:fixture) { send(fixture_method) }
|
|
17
|
+
let(:attachment) { fixture.public_send(rel_name) }
|
|
18
|
+
let(:serializer) { JSONAPI::Serializer.new(fixture) }
|
|
19
|
+
let(:serialized) { serializer.serialize_record }
|
|
20
|
+
|
|
21
|
+
context "without attachment" do
|
|
22
|
+
before do
|
|
23
|
+
attachment.purge if attachment.attached?
|
|
24
|
+
fixture.reload
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it { expect(serialized.dig(:relationships, rel_name, :data)).to be_nil }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
context "with attachment" do
|
|
31
|
+
before do
|
|
32
|
+
attachment.attach(file_fixture)
|
|
33
|
+
fixture.reload
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "serializes as active_storage_blobs", :aggregate_failures do
|
|
37
|
+
data = serialized.dig(:relationships, rel_name, :data)
|
|
38
|
+
expect(data).to include(type: "active_storage_blobs", id: attachment.blob.id.to_s)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
context "when included" do
|
|
42
|
+
let(:serialized) { serializer.to_hash(include: [rel_name.to_s]) }
|
|
43
|
+
|
|
44
|
+
it "includes blob details", :aggregate_failures do
|
|
45
|
+
blob = serialized[:included].find { |r| r[:type] == "active_storage_blobs" }
|
|
46
|
+
expect(blob[:attributes]).to include(filename: "avatar.jpg", content_type: "image/jpeg")
|
|
47
|
+
expect(blob[:links][:download]).to be_present
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
RSpec.shared_examples [Jpie, Resource, :has_many_attached].to_s do |rel_name, fixture_method| # rubocop:disable Metrics/BlockLength
|
|
55
|
+
describe "#{rel_name} ActiveStorage serialization" do # rubocop:disable Metrics/BlockLength
|
|
56
|
+
let(:file_fixture) { fixture_file_upload("avatar.jpg") }
|
|
57
|
+
let(:fixture) { send(fixture_method) }
|
|
58
|
+
let(:attachments) { fixture.public_send(rel_name) }
|
|
59
|
+
let(:serializer) { JSONAPI::Serializer.new(fixture) }
|
|
60
|
+
let(:serialized) { serializer.serialize_record }
|
|
61
|
+
|
|
62
|
+
context "without attachments" do
|
|
63
|
+
before do
|
|
64
|
+
attachments.each(&:purge) if attachments.attached?
|
|
65
|
+
fixture.reload
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it { expect(serialized.dig(:relationships, rel_name, :data)).to eq([]) }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
context "with attachments" do
|
|
72
|
+
before do
|
|
73
|
+
attachments.each(&:purge) if attachments.attached?
|
|
74
|
+
attachments.attach(file_fixture)
|
|
75
|
+
fixture.reload
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "serializes as active_storage_blobs array", :aggregate_failures do
|
|
79
|
+
data = serialized.dig(:relationships, rel_name, :data)
|
|
80
|
+
expect(data).to be_an(Array)
|
|
81
|
+
expect(data.first).to include(type: "active_storage_blobs", id: attachments.first.blob.id.to_s)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
context "when included" do
|
|
85
|
+
let(:serialized) { serializer.to_hash(include: [rel_name.to_s]) }
|
|
86
|
+
|
|
87
|
+
it "includes blob details", :aggregate_failures do
|
|
88
|
+
blob = serialized[:included].find { |r| r[:type] == "active_storage_blobs" }
|
|
89
|
+
expect(blob[:attributes]).to include(filename: "avatar.jpg", content_type: "image/jpeg")
|
|
90
|
+
expect(blob[:links][:download]).to be_present
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Module definitions for shared example naming in JSON:API specs.
|
|
4
|
+
# This follows the pattern: [ Jpie, Category, :symbol ].to_s
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# RSpec.shared_examples [ Jpie, Resource, :has_attributes ].to_s do
|
|
8
|
+
# ...
|
|
9
|
+
# end
|
|
10
|
+
#
|
|
11
|
+
# it_behaves_like [ Jpie, Resource, :has_attributes ].to_s, :name, :email
|
|
12
|
+
# it_behaves_like [ Jpie, Response, :data ].to_s, type: "users"
|
|
13
|
+
#
|
|
14
|
+
# These modules are made available at the top level when you require
|
|
15
|
+
# "json_api/testing/rspec" in your RSpec configuration.
|
|
16
|
+
|
|
17
|
+
# Gem namespace - prefix for all jpie shared examples
|
|
18
|
+
module Jpie; end
|
|
19
|
+
|
|
20
|
+
# Resource-level shared examples (for testing resource class definitions)
|
|
21
|
+
module Resource; end
|
|
22
|
+
|
|
23
|
+
# Response-level shared examples (for testing JSON:API response structure)
|
|
24
|
+
module Response; end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Shared examples for testing JSON:API Resource class definitions.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# RSpec.describe UserResource do
|
|
7
|
+
# it_behaves_like [ Jpie, Resource, :has_attributes ].to_s, :name, :email
|
|
8
|
+
# it_behaves_like [ Jpie, Resource, :has_one ].to_s, :account
|
|
9
|
+
# it_behaves_like [ Jpie, Resource, :has_many ].to_s, :posts
|
|
10
|
+
# it_behaves_like [ Jpie, Resource, :has_filter ].to_s, :name
|
|
11
|
+
# end
|
|
12
|
+
|
|
13
|
+
RSpec.shared_examples [Jpie, Resource, :has_attributes].to_s do |*attributes|
|
|
14
|
+
it "exposes exactly the expected attributes" do
|
|
15
|
+
expect(described_class.permitted_attributes).to match_array(attributes.map(&:to_sym))
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
RSpec.shared_examples [Jpie, Resource, :has_one].to_s do |name, **options|
|
|
20
|
+
describe "##{name} relationship" do
|
|
21
|
+
let(:relationship_def) { described_class.relationship_definitions.find { |r| r[:name] == name } }
|
|
22
|
+
|
|
23
|
+
it { expect(relationship_def).to be_present }
|
|
24
|
+
it { expect(relationship_def[:type]).to be_in(%i[has_one belongs_to]) }
|
|
25
|
+
|
|
26
|
+
class_name = options.fetch(:class_name, name.to_s.classify.to_s)
|
|
27
|
+
it { expect(relationship_def[:options][:class_name] || name.to_s.classify.to_s).to eql class_name }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
RSpec.shared_examples [Jpie, Resource, :has_many].to_s do |name, **options|
|
|
32
|
+
describe "##{name} relationship" do
|
|
33
|
+
let(:relationship_def) { described_class.relationship_definitions.find { |r| r[:name] == name } }
|
|
34
|
+
|
|
35
|
+
it { expect(relationship_def).to be_present }
|
|
36
|
+
it { expect(relationship_def[:type]).to eq :has_many }
|
|
37
|
+
|
|
38
|
+
if options.key?(:class_name)
|
|
39
|
+
it "has the correct class_name" do
|
|
40
|
+
actual_class_name = relationship_def[:options][:class_name] || name.to_s.classify
|
|
41
|
+
expect(actual_class_name).to eql options[:class_name]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
RSpec.shared_examples [Jpie, Resource, :has_filter].to_s do |name|
|
|
48
|
+
describe "##{name} filter" do
|
|
49
|
+
it "exposes the expected filter (base or comparator)" do
|
|
50
|
+
expected = [name.to_sym, :"#{name}_eq"]
|
|
51
|
+
expect(described_class.permitted_filters & expected).to be_present
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Shared examples for testing JSON:API response structure.
|
|
4
|
+
#
|
|
5
|
+
# These shared examples verify that API responses conform to JSON:API specification.
|
|
6
|
+
# They assume the response is available via `response` and use `response.parsed_body`.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# RSpec.describe "Users API", type: :request do
|
|
10
|
+
# describe "GET /users/:id" do
|
|
11
|
+
# before { get user_path(user), headers: headers, as: :jsonapi }
|
|
12
|
+
#
|
|
13
|
+
# it_behaves_like [ Jpie, Response, :data ].to_s, type: "users"
|
|
14
|
+
# it_behaves_like [ Jpie, Response, :attributes ].to_s, :name, :email
|
|
15
|
+
# end
|
|
16
|
+
# end
|
|
17
|
+
|
|
18
|
+
RSpec.shared_examples [Jpie, Response, :data].to_s do |type: nil|
|
|
19
|
+
it "returns data with correct structure" do
|
|
20
|
+
data = response.parsed_body["data"]
|
|
21
|
+
expect(data).to be_present
|
|
22
|
+
|
|
23
|
+
if data.is_a?(Array)
|
|
24
|
+
data.each do |item|
|
|
25
|
+
expect(item).to have_key("type")
|
|
26
|
+
expect(item).to have_key("id")
|
|
27
|
+
expect(item["type"]).to eq(type.to_s) if type
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
expect(data).to have_key("type")
|
|
31
|
+
expect(data).to have_key("id")
|
|
32
|
+
expect(data["type"]).to eq(type.to_s) if type
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
RSpec.shared_examples [Jpie, Response, :attributes].to_s do |*attributes|
|
|
38
|
+
it "includes expected attributes in response" do
|
|
39
|
+
data = response.parsed_body["data"]
|
|
40
|
+
data = data.first if data.is_a?(Array)
|
|
41
|
+
|
|
42
|
+
expect(data).to have_key("attributes")
|
|
43
|
+
attributes.each do |attr|
|
|
44
|
+
msg = "Expected attribute '#{attr}', got: #{data["attributes"].keys}"
|
|
45
|
+
expect(data["attributes"]).to have_key(attr.to_s), msg
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
RSpec.shared_examples [Jpie, Response, :relationships].to_s do |*relationships|
|
|
51
|
+
it "includes expected relationships in response" do
|
|
52
|
+
data = response.parsed_body["data"]
|
|
53
|
+
data = data.first if data.is_a?(Array)
|
|
54
|
+
|
|
55
|
+
expect(data).to have_key("relationships")
|
|
56
|
+
relationships.each do |rel|
|
|
57
|
+
msg = "Expected relationship '#{rel}', got: #{data["relationships"].keys}"
|
|
58
|
+
expect(data["relationships"]).to have_key(rel.to_s), msg
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
RSpec.shared_examples [Jpie, Response, :included].to_s do |type: nil, count: nil|
|
|
64
|
+
it "includes compound document data" do
|
|
65
|
+
included = response.parsed_body["included"]
|
|
66
|
+
expect(included).to be_an(Array)
|
|
67
|
+
expect(included).not_to be_empty
|
|
68
|
+
|
|
69
|
+
if type
|
|
70
|
+
matching = included.select { |item| item["type"] == type.to_s }
|
|
71
|
+
expect(matching).not_to be_empty, "Expected included to contain type '#{type}'"
|
|
72
|
+
expect(matching.size).to eq(count) if count
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
RSpec.shared_examples [Jpie, Response, :links].to_s do |*link_keys|
|
|
78
|
+
it "includes links in response" do
|
|
79
|
+
links = response.parsed_body["links"]
|
|
80
|
+
expect(links).to be_a(Hash)
|
|
81
|
+
|
|
82
|
+
link_keys.each do |key|
|
|
83
|
+
msg = "Expected link '#{key}', got: #{links.keys}"
|
|
84
|
+
expect(links).to have_key(key.to_s), msg
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
RSpec.shared_examples [Jpie, Response, :meta].to_s do |*meta_keys|
|
|
90
|
+
it "includes meta in response" do
|
|
91
|
+
meta = response.parsed_body["meta"]
|
|
92
|
+
expect(meta).to be_a(Hash)
|
|
93
|
+
|
|
94
|
+
meta_keys.each do |key|
|
|
95
|
+
msg = "Expected meta '#{key}', got: #{meta.keys}"
|
|
96
|
+
expect(meta).to have_key(key.to_s), msg
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
RSpec.shared_examples [Jpie, Response, :errors].to_s do |count: nil|
|
|
102
|
+
it "returns errors array" do
|
|
103
|
+
errors = response.parsed_body["errors"]
|
|
104
|
+
expect(errors).to be_an(Array)
|
|
105
|
+
expect(errors).not_to be_empty
|
|
106
|
+
expect(errors.size).to eq(count) if count
|
|
107
|
+
|
|
108
|
+
errors.each do |error|
|
|
109
|
+
# JSON:API error objects should have at least one of these
|
|
110
|
+
expect(error.keys & %w[id status code title detail source meta]).not_to be_empty
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
data/lib/json_api/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jpie
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.1.
|
|
4
|
+
version: 1.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Emil Kampp
|
|
@@ -143,6 +143,11 @@ files:
|
|
|
143
143
|
- lib/json_api/support/sort_parsing.rb
|
|
144
144
|
- lib/json_api/support/type_conversion.rb
|
|
145
145
|
- lib/json_api/testing.rb
|
|
146
|
+
- lib/json_api/testing/rspec.rb
|
|
147
|
+
- lib/json_api/testing/shared_examples/active_storage.rb
|
|
148
|
+
- lib/json_api/testing/shared_examples/modules.rb
|
|
149
|
+
- lib/json_api/testing/shared_examples/resources.rb
|
|
150
|
+
- lib/json_api/testing/shared_examples/responses.rb
|
|
146
151
|
- lib/json_api/testing/test_helper.rb
|
|
147
152
|
- lib/json_api/version.rb
|
|
148
153
|
- lib/rubocop/cop/custom/hash_value_omission.rb
|