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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e425e58d806e51b4cb6ed58345acdc3169079011dca02fa8dfae18b57bbe70c
4
- data.tar.gz: e0f9316d7a675048c5c63442e9f897452ffa62358d3ca73d8e8aa288122a623c
3
+ metadata.gz: a9afc70de5e508e561e451595fc67fdad82446fe7b79c4b565d53c8a26719c13
4
+ data.tar.gz: fca030ebc361a511587a1a3b152243629ec6fec26f65c929408325cedba2ca95
5
5
  SHA512:
6
- metadata.gz: 36c41c6de0027699b5534abe379930f67fc67698aa8016635a294b8ba5624ace0bedaeaebddfbf5708c3ad58696527b1e15f177ac84c36c272581cff123a52ec
7
- data.tar.gz: c948938233596ef3345bc7186e8f78575f37c1057d9675850634ddb53c36506de7b26b89f14555fdca8659124ae8ec0b9908da39324f87cff90dd82aa2c8cd8d
6
+ metadata.gz: 6136bb0f0829358a96d5fc1daace4794ad1f3e559701d5ed6b03c63189f8925fa3e6f645e6b447809f3cdbd44596e3defc6ffda2d0f87b62d36f2c3f17ba9cc8
7
+ data.tar.gz: 9af6902e625ab5fa282065849bdaa89977389a4c2c86db06805de6c05b3c6325bb2a8dd4253f93bcf8a27016e77ca9b3b3adc9a805058baac1c7b403439a104f
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- jpie (1.1.0)
4
+ jpie (1.1.1)
5
5
  actionpack (~> 8.0, >= 8.0.0)
6
6
  rails (~> 8.0, >= 8.0.0)
7
7
 
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSONAPI
4
- VERSION = "1.1.0"
4
+ VERSION = "1.1.1"
5
5
  end
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.0
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