Skip to content

Commit 2e0e4a1

Browse files
committed
Add coverage for JSON schema and protobuf extensions
1 parent 68167ad commit 2e0e4a1

5 files changed

Lines changed: 694 additions & 0 deletions

File tree

elasticgraph-json_schema/spec/unit/elastic_graph/json_schema/schema_definition/api_extension_spec.rb

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,82 @@
1111
module ElasticGraph
1212
module JsonSchema
1313
module SchemaDefinition
14+
RSpec.describe APIExtension do
15+
def build_api_with_extension
16+
state = ::Struct.new(
17+
:json_schema_version,
18+
:json_schema_version_setter_location,
19+
:allow_omitted_json_schema_fields,
20+
:allow_extra_json_schema_fields,
21+
keyword_init: true
22+
).new
23+
24+
factory = ::Object.new
25+
26+
api = ::Object.new
27+
api.instance_variable_set(:@state, state)
28+
api.define_singleton_method(:factory) { factory }
29+
api.extend(APIExtension)
30+
31+
[api, state, factory]
32+
end
33+
34+
it "extends the api factory with JSON schema factory behavior" do
35+
_api, _state, factory = build_api_with_extension
36+
37+
expect(factory).to be_a(FactoryExtension)
38+
end
39+
40+
it "stores the JSON schema version and its setter location" do
41+
api, state, = build_api_with_extension
42+
43+
expect(api.json_schema_version(3)).to eq(nil)
44+
expect(state.json_schema_version).to eq(3)
45+
expect(state.json_schema_version_setter_location).to be_a(::Thread::Backtrace::Location)
46+
end
47+
48+
it "rejects invalid JSON schema versions" do
49+
api, = build_api_with_extension
50+
51+
expect {
52+
api.json_schema_version(0)
53+
}.to raise_error(Errors::SchemaError, /must be a positive integer/)
54+
55+
expect {
56+
api.json_schema_version("3")
57+
}.to raise_error(Errors::SchemaError, /must be a positive integer/)
58+
end
59+
60+
it "rejects setting the JSON schema version more than once" do
61+
api, = build_api_with_extension
62+
api.json_schema_version(1)
63+
64+
expect {
65+
api.json_schema_version(2)
66+
}.to raise_error(Errors::SchemaError, /can only be set once/)
67+
end
68+
69+
it "stores JSON schema strictness settings" do
70+
api, state, = build_api_with_extension
71+
72+
expect(api.json_schema_strictness(allow_omitted_fields: true, allow_extra_fields: false)).to eq(nil)
73+
expect(state.allow_omitted_json_schema_fields).to eq(true)
74+
expect(state.allow_extra_json_schema_fields).to eq(false)
75+
end
76+
77+
it "validates JSON schema strictness arguments" do
78+
api, = build_api_with_extension
79+
80+
expect {
81+
api.json_schema_strictness(allow_omitted_fields: :sometimes)
82+
}.to raise_error(Errors::SchemaError, /allow_omitted_fields/)
83+
84+
expect {
85+
api.json_schema_strictness(allow_extra_fields: :sometimes)
86+
}.to raise_error(Errors::SchemaError, /allow_extra_fields/)
87+
end
88+
end
89+
1490
RSpec.describe APIExtension, :json_schema_schema do
1591
it "adds JSON schema generation and artifact dumping through schema definition extension hooks" do
1692
results = define_json_schema_schema(reload_schema_artifacts: true, json_schema_version: nil) do |schema|
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Copyright 2024 - 2026 Block, Inc.
2+
#
3+
# Use of this source code is governed by an MIT-style
4+
# license that can be found in the LICENSE file or at
5+
# https://opensource.org/licenses/MIT.
6+
#
7+
# frozen_string_literal: true
8+
9+
require "elastic_graph/json_schema/schema_definition/factory_extension"
10+
11+
module ElasticGraph
12+
module JsonSchema
13+
module SchemaDefinition
14+
RSpec.describe FactoryExtension do
15+
let(:factory_class) do
16+
base_class = ::Class.new do
17+
def new_results
18+
::Object.new
19+
end
20+
21+
def new_schema_artifact_manager(*args, **kwargs)
22+
@last_schema_artifact_manager_args = args
23+
@last_schema_artifact_manager_kwargs = kwargs
24+
::Object.new
25+
end
26+
27+
attr_reader :last_schema_artifact_manager_args, :last_schema_artifact_manager_kwargs
28+
end
29+
30+
::Class.new(base_class) do
31+
prepend FactoryExtension
32+
end
33+
end
34+
35+
it "extends results and schema artifact managers with JSON schema behavior" do
36+
factory = factory_class.new
37+
38+
expect(factory.new_results).to be_a(ResultsExtension)
39+
40+
manager = factory.new_schema_artifact_manager(:positional, key: "value")
41+
expect(manager).to be_a(SchemaArtifactManagerExtension)
42+
expect(factory.last_schema_artifact_manager_args).to eq([:positional])
43+
expect(factory.last_schema_artifact_manager_kwargs).to eq({key: "value"})
44+
end
45+
end
46+
end
47+
end
48+
end
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
# Copyright 2024 - 2026 Block, Inc.
2+
#
3+
# Use of this source code is governed by an MIT-style
4+
# license that can be found in the LICENSE file or at
5+
# https://opensource.org/licenses/MIT.
6+
#
7+
# frozen_string_literal: true
8+
9+
require "elastic_graph/json_schema/schema_definition/results_extension"
10+
11+
module ElasticGraph
12+
module JsonSchema
13+
module SchemaDefinition
14+
FakeType = ::Struct.new(
15+
:name,
16+
:indexing_field_type,
17+
:root_document_type_value,
18+
:abstract_value,
19+
:graphql_only_value,
20+
keyword_init: true
21+
) do
22+
def root_document_type?
23+
root_document_type_value
24+
end
25+
26+
def abstract?
27+
abstract_value
28+
end
29+
30+
def graphql_only?
31+
graphql_only_value
32+
end
33+
34+
def to_indexing_field_type
35+
indexing_field_type
36+
end
37+
end
38+
39+
FakeIndexingFieldType = ::Struct.new(:json_schema, :field_metadata, keyword_init: true) do
40+
def to_json_schema
41+
json_schema
42+
end
43+
44+
def json_schema_field_metadata_by_field_name
45+
field_metadata
46+
end
47+
end
48+
49+
FakeMergedSchema = ::Struct.new(:json_schema_version, :json_schema, keyword_init: true)
50+
51+
RSpec.describe ResultsExtension do
52+
def build_results(state:, derived_indexing_type_names: Set[])
53+
::Object.new.tap do |results|
54+
results.extend(described_class)
55+
results.define_singleton_method(:state) { state }
56+
results.define_singleton_method(:derived_indexing_type_names) { derived_indexing_type_names }
57+
end
58+
end
59+
60+
it "builds the current public JSON schema, exposes metadata helpers, and memoizes merged schemas" do
61+
widget_indexing_type = FakeIndexingFieldType.new(
62+
json_schema: {"type" => "object"},
63+
field_metadata: {"id" => {"name_in_index" => "id"}}
64+
)
65+
66+
widget_type = FakeType.new(
67+
name: "Widget",
68+
indexing_field_type: widget_indexing_type,
69+
root_document_type_value: true,
70+
abstract_value: false,
71+
graphql_only_value: false
72+
)
73+
derived_type = FakeType.new(
74+
name: "DerivedWidget",
75+
indexing_field_type: widget_indexing_type,
76+
root_document_type_value: true,
77+
abstract_value: false,
78+
graphql_only_value: false
79+
)
80+
graphql_only_type = FakeType.new(
81+
name: "GraphqlOnly",
82+
indexing_field_type: widget_indexing_type,
83+
root_document_type_value: false,
84+
abstract_value: false,
85+
graphql_only_value: true
86+
)
87+
query_type = FakeType.new(
88+
name: "Query",
89+
indexing_field_type: widget_indexing_type,
90+
root_document_type_value: false,
91+
abstract_value: false,
92+
graphql_only_value: false
93+
)
94+
95+
setter_location = instance_double(::Thread::Backtrace::Location)
96+
state = ::Struct.new(
97+
:json_schema_version,
98+
:json_schema_version_setter_location,
99+
:object_types_by_name,
100+
:types_by_name,
101+
keyword_init: true
102+
).new(
103+
json_schema_version: 3,
104+
json_schema_version_setter_location: setter_location,
105+
object_types_by_name: {
106+
"Widget" => widget_type,
107+
"DerivedWidget" => derived_type
108+
},
109+
types_by_name: {
110+
"Query" => query_type,
111+
"Widget" => widget_type,
112+
"DerivedWidget" => derived_type,
113+
"GraphqlOnly" => graphql_only_type
114+
}
115+
)
116+
117+
results = build_results(state: state, derived_indexing_type_names: Set["DerivedWidget"])
118+
119+
merged_schema = FakeMergedSchema.new(
120+
json_schema_version: 3,
121+
json_schema: {"json_schema_version" => 3, "$defs" => {"Widget" => {"with_metadata" => true}}}
122+
)
123+
merger = instance_double(
124+
ElasticGraph::SchemaDefinition::Indexing::JSONSchemaWithMetadata::Merger,
125+
merge_metadata_into: merged_schema,
126+
unused_deprecated_elements: [:unused]
127+
)
128+
129+
expect(ElasticGraph::SchemaDefinition::Indexing::JSONSchemaWithMetadata::Merger).to receive(:new).with(results).once.and_return(merger)
130+
expect(ElasticGraph::SchemaDefinition::Indexing::EventEnvelope).to receive(:json_schema).with(["Widget"], 3).once.and_return({"type" => "object"})
131+
132+
expect(results.available_json_schema_versions).to eq(Set[3])
133+
expect(results.latest_json_schema_version).to eq(3)
134+
expect(results.json_schema_version_setter_location).to be(setter_location)
135+
expect(results.json_schema_field_metadata_by_type_and_field_name).to eq({"Widget" => {"id" => {"name_in_index" => "id"}}})
136+
expect(results.current_public_json_schema).to include(
137+
"$schema" => JSON_META_SCHEMA,
138+
JSON_SCHEMA_VERSION_KEY => 3
139+
)
140+
expect(results.current_public_json_schema.fetch("$defs")).to include(
141+
"ElasticGraphEventEnvelope" => {"type" => "object"},
142+
"Widget" => {"type" => "object"}
143+
)
144+
expect(results.unused_deprecated_elements).to eq([:unused])
145+
expect(results.json_schemas_for(3)).to eq({"json_schema_version" => 3, "$defs" => {"Widget" => {"with_metadata" => true}}})
146+
expect(results.json_schemas_for(3)).to eq({"json_schema_version" => 3, "$defs" => {"Widget" => {"with_metadata" => true}}})
147+
end
148+
149+
it "raises when the requested JSON schema version is unavailable" do
150+
state = ::Struct.new(
151+
:json_schema_version,
152+
:json_schema_version_setter_location,
153+
:object_types_by_name,
154+
:types_by_name,
155+
keyword_init: true
156+
).new(
157+
json_schema_version: 3,
158+
json_schema_version_setter_location: nil,
159+
object_types_by_name: {},
160+
types_by_name: {}
161+
)
162+
163+
results = build_results(state: state)
164+
165+
expect {
166+
results.json_schemas_for(2)
167+
}.to raise_error(Errors::NotFoundError, /requested json schema version \(2\) is not available/)
168+
end
169+
170+
it "raises when json_schema_version has not been configured" do
171+
state = ::Struct.new(
172+
:json_schema_version,
173+
:json_schema_version_setter_location,
174+
:object_types_by_name,
175+
:types_by_name,
176+
keyword_init: true
177+
).new(
178+
json_schema_version: nil,
179+
json_schema_version_setter_location: nil,
180+
object_types_by_name: {},
181+
types_by_name: {}
182+
)
183+
184+
results = build_results(state: state)
185+
186+
expect {
187+
results.current_public_json_schema
188+
}.to raise_error(Errors::SchemaError, /`json_schema_version` must be specified/)
189+
end
190+
end
191+
end
192+
end
193+
end

0 commit comments

Comments
 (0)