jpie 1.1.0 → 1.1.2
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 +44 -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 +165 -0
- data/lib/json_api/testing/sti_helpers.rb +141 -0
- data/lib/json_api/version.rb +1 -1
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8c205d5463d3fffd9ccf158d1ef8df04e34e50fd6a7385f9b727a46676181c00
|
|
4
|
+
data.tar.gz: 1c8e628e95a5296baa459c89cbc7d2bd119f4bb5e0456f5d9d3e4c8322b08a92
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 17bb979968aba9d606fdb537c5ebba08e8d29b39e9ffd2dbaefeaaeafc4a41b1ebb686429cd20ffd10db9276320d0e70e7cc94551a418863dbdd633542df67f6
|
|
7
|
+
data.tar.gz: 8fad54116b4eaa6c2e77f9e8034f477a3c9cb4911cc749ff00bf06cc30d801751ff0351771040dd94f6a639776aa0396775637e1d1fa4583a91a965e844ca8e8
|
data/Gemfile.lock
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
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. Load STI testing helpers for request specs
|
|
13
|
+
# 4. Make the modules available at the top level for convenient usage
|
|
14
|
+
#
|
|
15
|
+
# After requiring, you can use shared examples like:
|
|
16
|
+
#
|
|
17
|
+
# it_behaves_like [ Jpie, Resource, :has_attributes ].to_s, :name, :email
|
|
18
|
+
# it_behaves_like [ Jpie, Response, :data ].to_s, type: "users"
|
|
19
|
+
# it_behaves_like [ Jpie, Response, :sti_types ].to_s, expected_subtypes: ["user_messages"]
|
|
20
|
+
#
|
|
21
|
+
# And STI helpers in request specs:
|
|
22
|
+
#
|
|
23
|
+
# expect_sti_types_in_included(expected_subtypes: ["user_messages"])
|
|
24
|
+
# expect_sti_resources_included(sti_type: "user_messages", expected_ids: [msg.id])
|
|
25
|
+
|
|
26
|
+
require_relative "shared_examples/modules"
|
|
27
|
+
require_relative "sti_helpers"
|
|
28
|
+
|
|
29
|
+
# Load shared examples
|
|
30
|
+
Dir[File.join(__dir__, "shared_examples", "*.rb")].each do |file|
|
|
31
|
+
require file unless file.end_with?("modules.rb")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Make modules available at top level when included
|
|
35
|
+
RSpec.configure do |config|
|
|
36
|
+
config.include(Module.new do
|
|
37
|
+
%i[Jpie Resource Response].each do |const|
|
|
38
|
+
define_method(const) { Object.const_get(const) }
|
|
39
|
+
end
|
|
40
|
+
end)
|
|
41
|
+
|
|
42
|
+
# Include STI helpers for request specs
|
|
43
|
+
config.include JSONAPI::Testing::StiHelpers, type: :request
|
|
44
|
+
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,165 @@
|
|
|
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,
|
|
64
|
+
:included,].to_s do |type: nil, count: nil, sti_subtypes: nil, exclude_base_type: nil|
|
|
65
|
+
it "includes compound document data" do
|
|
66
|
+
included = response.parsed_body["included"]
|
|
67
|
+
expect(included).to be_an(Array)
|
|
68
|
+
expect(included).not_to be_empty
|
|
69
|
+
|
|
70
|
+
if type
|
|
71
|
+
matching = included.select { |item| item["type"] == type.to_s }
|
|
72
|
+
expect(matching).not_to be_empty, "Expected included to contain type '#{type}'"
|
|
73
|
+
expect(matching.size).to eq(count) if count
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
if sti_subtypes
|
|
77
|
+
included_types = included.pluck("type").uniq
|
|
78
|
+
sti_subtypes.each do |subtype|
|
|
79
|
+
expect(included_types).to include(subtype.to_s),
|
|
80
|
+
"Expected included to contain STI subtype '#{subtype}', got: #{included_types}"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
if exclude_base_type
|
|
85
|
+
included_types ||= included.pluck("type").uniq
|
|
86
|
+
expect(included_types).not_to include(exclude_base_type.to_s),
|
|
87
|
+
"Expected included NOT to contain base type '#{exclude_base_type}', but it did"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
RSpec.shared_examples [Jpie, Response, :links].to_s do |*link_keys|
|
|
93
|
+
it "includes links in response" do
|
|
94
|
+
links = response.parsed_body["links"]
|
|
95
|
+
expect(links).to be_a(Hash)
|
|
96
|
+
|
|
97
|
+
link_keys.each do |key|
|
|
98
|
+
msg = "Expected link '#{key}', got: #{links.keys}"
|
|
99
|
+
expect(links).to have_key(key.to_s), msg
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
RSpec.shared_examples [Jpie, Response, :meta].to_s do |*meta_keys|
|
|
105
|
+
it "includes meta in response" do
|
|
106
|
+
meta = response.parsed_body["meta"]
|
|
107
|
+
expect(meta).to be_a(Hash)
|
|
108
|
+
|
|
109
|
+
meta_keys.each do |key|
|
|
110
|
+
msg = "Expected meta '#{key}', got: #{meta.keys}"
|
|
111
|
+
expect(meta).to have_key(key.to_s), msg
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
RSpec.shared_examples [Jpie, Response, :errors].to_s do |count: nil|
|
|
117
|
+
it "returns errors array" do
|
|
118
|
+
errors = response.parsed_body["errors"]
|
|
119
|
+
expect(errors).to be_an(Array)
|
|
120
|
+
expect(errors).not_to be_empty
|
|
121
|
+
expect(errors.size).to eq(count) if count
|
|
122
|
+
|
|
123
|
+
errors.each do |error|
|
|
124
|
+
# JSON:API error objects should have at least one of these
|
|
125
|
+
expect(error.keys & %w[id status code title detail source meta]).not_to be_empty
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
RSpec.shared_examples [Jpie, Response, :sti_types].to_s do |expected_subtypes:, exclude_base_type: nil|
|
|
131
|
+
it "returns STI subtypes correctly" do
|
|
132
|
+
data = response.parsed_body["data"]
|
|
133
|
+
data = [data] unless data.is_a?(Array)
|
|
134
|
+
types = data.pluck("type").uniq
|
|
135
|
+
|
|
136
|
+
expected_subtypes.each do |subtype|
|
|
137
|
+
expect(types).to include(subtype.to_s),
|
|
138
|
+
"Expected data to contain STI subtype '#{subtype}', got: #{types}"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
if exclude_base_type
|
|
142
|
+
expect(types).not_to include(exclude_base_type.to_s),
|
|
143
|
+
"Expected data NOT to contain base type '#{exclude_base_type}', but it did"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
RSpec.shared_examples [Jpie, Response, :sti_included].to_s do |expected_subtypes:, exclude_base_type: nil|
|
|
149
|
+
it "includes STI subtypes correctly" do
|
|
150
|
+
included = response.parsed_body["included"] || []
|
|
151
|
+
expect(included).not_to be_empty, "Expected included to be present"
|
|
152
|
+
|
|
153
|
+
included_types = included.pluck("type").uniq
|
|
154
|
+
|
|
155
|
+
expected_subtypes.each do |subtype|
|
|
156
|
+
expect(included_types).to include(subtype.to_s),
|
|
157
|
+
"Expected included to contain STI subtype '#{subtype}', got: #{included_types}"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
if exclude_base_type
|
|
161
|
+
expect(included_types).not_to include(exclude_base_type.to_s),
|
|
162
|
+
"Expected included NOT to contain base type '#{exclude_base_type}', but it did"
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSONAPI
|
|
4
|
+
module Testing
|
|
5
|
+
# Helper methods for testing STI (Single Table Inheritance) resources in JSON:API responses.
|
|
6
|
+
#
|
|
7
|
+
# These helpers simplify testing that STI subtypes are correctly serialized
|
|
8
|
+
# and included in JSON:API responses.
|
|
9
|
+
#
|
|
10
|
+
# Usage in request specs:
|
|
11
|
+
#
|
|
12
|
+
# it "includes user messages", :aggregate_failures do
|
|
13
|
+
# get conversation_path(conversation), params: { include: "messages" }, as: :jsonapi
|
|
14
|
+
# expect_sti_types_in_included(expected_subtypes: ["user_messages"])
|
|
15
|
+
# expect_sti_resources_included(sti_type: "user_messages", expected_ids: [message.id])
|
|
16
|
+
# end
|
|
17
|
+
module StiHelpers
|
|
18
|
+
# Verify that included resources contain expected STI subtypes.
|
|
19
|
+
#
|
|
20
|
+
# @param expected_subtypes [Array<String, Symbol>] Expected STI subtype names
|
|
21
|
+
# @param exclude_base_type [String, Symbol, nil] Base type that should NOT be present (optional)
|
|
22
|
+
# @return [void]
|
|
23
|
+
#
|
|
24
|
+
# @example Verify subtypes are present
|
|
25
|
+
# expect_sti_types_in_included(expected_subtypes: ["user_messages", "ai_messages"])
|
|
26
|
+
#
|
|
27
|
+
# @example Verify subtypes and exclude base type
|
|
28
|
+
# expect_sti_types_in_included(
|
|
29
|
+
# expected_subtypes: ["user_messages"],
|
|
30
|
+
# exclude_base_type: "messages"
|
|
31
|
+
# )
|
|
32
|
+
def expect_sti_types_in_included(expected_subtypes:, exclude_base_type: nil)
|
|
33
|
+
included = response.parsed_body["included"] || []
|
|
34
|
+
included_types = included.pluck("type").uniq
|
|
35
|
+
|
|
36
|
+
expected_subtypes.each do |subtype|
|
|
37
|
+
expect(included_types).to include(subtype.to_s),
|
|
38
|
+
"Expected included to contain STI type '#{subtype}', got: #{included_types}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
return if exclude_base_type.blank?
|
|
42
|
+
|
|
43
|
+
expect(included_types).not_to include(exclude_base_type.to_s),
|
|
44
|
+
"Expected included NOT to contain base type '#{exclude_base_type}', but it did"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Find all resources of a specific STI type in the included array.
|
|
48
|
+
#
|
|
49
|
+
# @param sti_type [String, Symbol] The STI subtype to find
|
|
50
|
+
# @return [Array<Hash>] Array of resource objects matching the STI type
|
|
51
|
+
#
|
|
52
|
+
# @example Find all user messages in included
|
|
53
|
+
# messages = find_sti_resources_in_included("user_messages")
|
|
54
|
+
# expect(messages.first["attributes"]["content"]).to eq("Hello")
|
|
55
|
+
def find_sti_resources_in_included(sti_type)
|
|
56
|
+
included = response.parsed_body["included"] || []
|
|
57
|
+
included.select { |item| item["type"] == sti_type.to_s }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Verify that specific STI resources are included by ID.
|
|
61
|
+
#
|
|
62
|
+
# @param sti_type [String, Symbol] The STI subtype
|
|
63
|
+
# @param expected_ids [Array<Integer, String>] Expected resource IDs
|
|
64
|
+
# @return [void]
|
|
65
|
+
#
|
|
66
|
+
# @example Verify specific messages are included
|
|
67
|
+
# expect_sti_resources_included(
|
|
68
|
+
# sti_type: "user_messages",
|
|
69
|
+
# expected_ids: [message1.id, message2.id]
|
|
70
|
+
# )
|
|
71
|
+
def expect_sti_resources_included(sti_type:, expected_ids:)
|
|
72
|
+
resources = find_sti_resources_in_included(sti_type)
|
|
73
|
+
actual_ids = resources.map { |r| r["id"] }
|
|
74
|
+
expected_ids_str = expected_ids.map(&:to_s)
|
|
75
|
+
|
|
76
|
+
expect(actual_ids).to match_array(expected_ids_str),
|
|
77
|
+
"Expected #{sti_type} resources with IDs #{expected_ids_str}, got: #{actual_ids}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Verify STI types in data array (not included).
|
|
81
|
+
#
|
|
82
|
+
# @param expected_subtypes [Array<String, Symbol>] Expected STI subtype names
|
|
83
|
+
# @param exclude_base_type [String, Symbol, nil] Base type that should NOT be present (optional)
|
|
84
|
+
# @return [void]
|
|
85
|
+
#
|
|
86
|
+
# @example Verify data contains correct STI types
|
|
87
|
+
# expect_sti_types_in_data(
|
|
88
|
+
# expected_subtypes: ["user_messages", "ai_messages"],
|
|
89
|
+
# exclude_base_type: "messages"
|
|
90
|
+
# )
|
|
91
|
+
def expect_sti_types_in_data(expected_subtypes:, exclude_base_type: nil)
|
|
92
|
+
data = response.parsed_body["data"]
|
|
93
|
+
data = [data] unless data.is_a?(Array)
|
|
94
|
+
types = data.pluck("type").uniq
|
|
95
|
+
|
|
96
|
+
expected_subtypes.each do |subtype|
|
|
97
|
+
expect(types).to include(subtype.to_s),
|
|
98
|
+
"Expected data to contain STI subtype '#{subtype}', got: #{types}"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
return if exclude_base_type.blank?
|
|
102
|
+
|
|
103
|
+
expect(types).not_to include(exclude_base_type.to_s),
|
|
104
|
+
"Expected data NOT to contain base type '#{exclude_base_type}', but it did"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Find all resources of a specific STI type in the data array.
|
|
108
|
+
#
|
|
109
|
+
# @param sti_type [String, Symbol] The STI subtype to find
|
|
110
|
+
# @return [Array<Hash>] Array of resource objects matching the STI type
|
|
111
|
+
#
|
|
112
|
+
# @example Find all user messages in data
|
|
113
|
+
# messages = find_sti_resources_in_data("user_messages")
|
|
114
|
+
def find_sti_resources_in_data(sti_type)
|
|
115
|
+
data = response.parsed_body["data"]
|
|
116
|
+
data = [data] unless data.is_a?(Array)
|
|
117
|
+
data.select { |item| item["type"] == sti_type.to_s }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Verify that specific STI resources are present in data by ID.
|
|
121
|
+
#
|
|
122
|
+
# @param sti_type [String, Symbol] The STI subtype
|
|
123
|
+
# @param expected_ids [Array<Integer, String>] Expected resource IDs
|
|
124
|
+
# @return [void]
|
|
125
|
+
#
|
|
126
|
+
# @example Verify specific messages are in data
|
|
127
|
+
# expect_sti_resources_in_data(
|
|
128
|
+
# sti_type: "user_messages",
|
|
129
|
+
# expected_ids: [message1.id, message2.id]
|
|
130
|
+
# )
|
|
131
|
+
def expect_sti_resources_in_data(sti_type:, expected_ids:)
|
|
132
|
+
resources = find_sti_resources_in_data(sti_type)
|
|
133
|
+
actual_ids = resources.map { |r| r["id"] }
|
|
134
|
+
expected_ids_str = expected_ids.map(&:to_s)
|
|
135
|
+
|
|
136
|
+
expect(actual_ids).to match_array(expected_ids_str),
|
|
137
|
+
"Expected #{sti_type} resources with IDs #{expected_ids_str}, got: #{actual_ids}"
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
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.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Emil Kampp
|
|
@@ -143,6 +143,12 @@ 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
|
|
151
|
+
- lib/json_api/testing/sti_helpers.rb
|
|
146
152
|
- lib/json_api/testing/test_helper.rb
|
|
147
153
|
- lib/json_api/version.rb
|
|
148
154
|
- lib/rubocop/cop/custom/hash_value_omission.rb
|