avro_turf 1.20.0 → 1.20.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/CHANGELOG.md +6 -0
- data/avro_turf.gemspec +0 -1
- data/lib/avro_turf/messaging.rb +4 -2
- data/lib/avro_turf/test/fake_confluent_schema_registry_server.rb +16 -15
- data/lib/avro_turf/test/fake_prefixed_confluent_schema_registry_server.rb +1 -1
- data/lib/avro_turf/test/fake_server.rb +186 -0
- data/lib/avro_turf/version.rb +1 -1
- data/spec/messaging_spec.rb +21 -0
- data/spec/test/fake_confluent_schema_registry_server_http_contract_spec.rb +722 -0
- metadata +3 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 80b65f5f08285ac85c04012f6b9f1fa9483095013a36fbaa5f93fa5b6d379e33
|
|
4
|
+
data.tar.gz: 6cfc6bdc7b87a0c2784bed5d6451c4803466fdd0d2fc0c8fa3d8bd61546ddbce
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 921c736f0889497e0db8327b96797a34f69d64410b1edcd05d3e961116eebc1476ecc039e78c4a06b49e8b03bfc8706153e446ac07831fef20c49002333f6c5d
|
|
7
|
+
data.tar.gz: 74d4ca51d66e3f5aa4f232cec3e3f52b86467d20c04145d51bb4e71c861f3120897eb38934e52ca35756b2fe69f0a1c6d22dae027ea923c258a7b12faf382cec
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Memoize parsed schemas to improve encoding performance when using `AvroTurf::Messaging` (#206)
|
|
6
|
+
|
|
7
|
+
## v1.20.1
|
|
8
|
+
|
|
9
|
+
- Remove `sinatra` as a development dependency (#237)
|
|
10
|
+
|
|
5
11
|
## v1.20.0
|
|
6
12
|
|
|
7
13
|
- Add support for client certificate chains via `client_chain` and `client_chain_data` parameters (#233)
|
data/avro_turf.gemspec
CHANGED
|
@@ -27,7 +27,6 @@ Gem::Specification.new do |spec|
|
|
|
27
27
|
spec.add_development_dependency "rspec", "~> 3.2"
|
|
28
28
|
spec.add_development_dependency "fakefs", "~> 3"
|
|
29
29
|
spec.add_development_dependency "webmock"
|
|
30
|
-
spec.add_development_dependency "sinatra"
|
|
31
30
|
spec.add_development_dependency "json_spec"
|
|
32
31
|
spec.add_development_dependency "rack-test"
|
|
33
32
|
spec.add_development_dependency "resolv"
|
data/lib/avro_turf/messaging.rb
CHANGED
|
@@ -236,13 +236,15 @@ class AvroTurf
|
|
|
236
236
|
if schema_type && schema_type != "AVRO"
|
|
237
237
|
raise IncompatibleSchemaError, "The #{schema_type} schema for #{subject} is incompatible."
|
|
238
238
|
end
|
|
239
|
-
|
|
239
|
+
|
|
240
|
+
schema = @schemas_by_id[schema_id] ||= Avro::Schema.parse(schema_data.fetch("schema"))
|
|
241
|
+
|
|
240
242
|
[schema, schema_id]
|
|
241
243
|
end
|
|
242
244
|
|
|
243
245
|
# Fetch the schema from registry with the provided schema_id.
|
|
244
246
|
def fetch_schema_by_id(schema_id)
|
|
245
|
-
schema = @schemas_by_id
|
|
247
|
+
schema = @schemas_by_id[schema_id] ||= begin
|
|
246
248
|
schema_json = @registry.fetch(schema_id)
|
|
247
249
|
Avro::Schema.parse(schema_json)
|
|
248
250
|
end
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative "fake_server"
|
|
4
|
+
|
|
5
|
+
class FakeConfluentSchemaRegistryServer
|
|
6
|
+
include AvroTurf::Test::FakeServer
|
|
4
7
|
|
|
5
|
-
class FakeConfluentSchemaRegistryServer < Sinatra::Base
|
|
6
8
|
QUALIFIED_SUBJECT = /
|
|
7
9
|
:(?<context>\.[^:]*)
|
|
8
10
|
:(?<subject>.*)
|
|
@@ -22,22 +24,21 @@ class FakeConfluentSchemaRegistryServer < Sinatra::Base
|
|
|
22
24
|
attr_reader :global_config
|
|
23
25
|
end
|
|
24
26
|
|
|
25
|
-
helpers
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
end
|
|
27
|
+
# Helper methods (previously in Sinatra helpers block)
|
|
28
|
+
def parse_schema
|
|
29
|
+
request.body.rewind
|
|
30
|
+
JSON.parse(request.body.read).fetch("schema").tap do |schema|
|
|
31
|
+
Avro::Schema.parse(schema)
|
|
31
32
|
end
|
|
33
|
+
end
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
def parse_config
|
|
36
|
+
request.body.rewind
|
|
37
|
+
JSON.parse(request.body.read)
|
|
38
|
+
end
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
end
|
|
40
|
+
def global_config
|
|
41
|
+
self.class.global_config
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
post "/subjects/:qualified_subject/versions" do
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "rack"
|
|
5
|
+
|
|
6
|
+
# Ensure AvroTurf class exists so we can add modules to it
|
|
7
|
+
# This is defined as a class (not module) in lib/avro_turf/version.rb
|
|
8
|
+
class AvroTurf
|
|
9
|
+
module Test
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# A lightweight Rack-based router module that provides Sinatra-like DSL.
|
|
14
|
+
# This module is designed to replace Sinatra::Base for the fake schema registry servers
|
|
15
|
+
# used in testing, eliminating the sinatra dependency.
|
|
16
|
+
#
|
|
17
|
+
# Usage:
|
|
18
|
+
# class MyServer
|
|
19
|
+
# include AvroTurf::Test::FakeServer
|
|
20
|
+
#
|
|
21
|
+
# get "/path/:param" do
|
|
22
|
+
# { result: params[:param] }.to_json
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# post "/other" do
|
|
26
|
+
# halt(404, '{"error": "not found"}') if some_condition
|
|
27
|
+
# '{"ok": true}'
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
module AvroTurf::Test::FakeServer
|
|
32
|
+
def self.included(base)
|
|
33
|
+
base.extend(ClassMethods)
|
|
34
|
+
base.include(InstanceMethods)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
module ClassMethods
|
|
38
|
+
# Storage for routes defined in this class
|
|
39
|
+
def routes
|
|
40
|
+
@routes ||= {"GET" => [], "POST" => [], "PUT" => [], "DELETE" => []}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# When a class inherits from another that includes FakeServer,
|
|
44
|
+
# ensure it gets its own routes hash
|
|
45
|
+
def inherited(subclass)
|
|
46
|
+
super
|
|
47
|
+
subclass.instance_variable_set(:@routes, nil)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Define a GET route
|
|
51
|
+
def get(pattern, &block)
|
|
52
|
+
add_route("GET", pattern, block)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Define a POST route
|
|
56
|
+
def post(pattern, &block)
|
|
57
|
+
add_route("POST", pattern, block)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Define a PUT route
|
|
61
|
+
def put(pattern, &block)
|
|
62
|
+
add_route("PUT", pattern, block)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Define a DELETE route
|
|
66
|
+
def delete(pattern, &block)
|
|
67
|
+
add_route("DELETE", pattern, block)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Sinatra-compatible `set` method for configuration
|
|
71
|
+
def set(key, value)
|
|
72
|
+
case key
|
|
73
|
+
when :host_authorization
|
|
74
|
+
@host_authorization = value
|
|
75
|
+
else
|
|
76
|
+
instance_variable_set(:"@#{key}", value)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Access host authorization settings
|
|
81
|
+
def host_authorization
|
|
82
|
+
@host_authorization
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Rack interface - creates a new instance and calls it
|
|
86
|
+
def call(env)
|
|
87
|
+
new.call(env)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def add_route(method, pattern, block)
|
|
93
|
+
routes[method] << [compile_pattern(pattern), pattern, block]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Convert a route pattern like "/subjects/:subject/versions" to a regex
|
|
97
|
+
# with named capture groups: /^\/subjects\/(?<subject>[^\/]+)\/versions$/
|
|
98
|
+
def compile_pattern(pattern)
|
|
99
|
+
regex_str = Regexp.escape(pattern).gsub(/:(\w+)/) { "(?<#{$1}>[^/]+)" }
|
|
100
|
+
Regexp.new("^#{regex_str}$")
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
module InstanceMethods
|
|
105
|
+
attr_reader :request, :params
|
|
106
|
+
|
|
107
|
+
def call(env)
|
|
108
|
+
@request = Rack::Request.new(env)
|
|
109
|
+
@params = {}
|
|
110
|
+
|
|
111
|
+
# Check host authorization if configured
|
|
112
|
+
if (auth = self.class.host_authorization)
|
|
113
|
+
permitted = auth[:permitted_hosts] || []
|
|
114
|
+
unless permitted.include?(@request.host)
|
|
115
|
+
return [403, {"Content-Type" => "text/plain"}, ["Forbidden"]]
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Use catch/throw for halt mechanism (like Sinatra)
|
|
120
|
+
catch(:halt) do
|
|
121
|
+
route_and_dispatch(env)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Early return from a route handler with a specific status and body
|
|
126
|
+
def halt(status, body)
|
|
127
|
+
throw :halt, [status, {"Content-Type" => "application/json"}, [body]]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
def route_and_dispatch(env)
|
|
133
|
+
method = env["REQUEST_METHOD"]
|
|
134
|
+
path = env["PATH_INFO"]
|
|
135
|
+
|
|
136
|
+
# Parse query string into params (with both string and symbol keys for compatibility)
|
|
137
|
+
query_params = Rack::Utils.parse_query(env["QUERY_STRING"] || "")
|
|
138
|
+
@params = {}
|
|
139
|
+
query_params.each do |key, value|
|
|
140
|
+
@params[key] = value
|
|
141
|
+
@params[key.to_sym] = value
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Find matching route (check own class first, then ancestors)
|
|
145
|
+
matched = find_route(method, path)
|
|
146
|
+
|
|
147
|
+
if matched
|
|
148
|
+
regex, _pattern, block = matched
|
|
149
|
+
|
|
150
|
+
# Extract path parameters from the match
|
|
151
|
+
if (match = regex.match(path))
|
|
152
|
+
match.names.each do |name|
|
|
153
|
+
# Store with both symbol and string keys for compatibility
|
|
154
|
+
@params[name.to_sym] = match[name]
|
|
155
|
+
@params[name] = match[name]
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Execute the route block in the context of this instance
|
|
160
|
+
body = instance_exec(&block)
|
|
161
|
+
[200, {"Content-Type" => "text/html;charset=utf-8"}, [body]]
|
|
162
|
+
else
|
|
163
|
+
[404, {"Content-Type" => "text/plain"}, ["Not Found"]]
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Find a matching route by searching this class's routes first,
|
|
168
|
+
# then parent classes (to support inheritance)
|
|
169
|
+
def find_route(method, path)
|
|
170
|
+
klass = self.class
|
|
171
|
+
while klass
|
|
172
|
+
if klass.respond_to?(:routes, true) && klass.routes[method]
|
|
173
|
+
klass.routes[method].each do |route|
|
|
174
|
+
regex, _, _ = route
|
|
175
|
+
return route if regex.match(path)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
# Move up the inheritance chain
|
|
179
|
+
klass = klass.superclass
|
|
180
|
+
# Stop if we've gone past classes that include FakeServer
|
|
181
|
+
break unless klass.respond_to?(:routes, true)
|
|
182
|
+
end
|
|
183
|
+
nil
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
data/lib/avro_turf/version.rb
CHANGED
data/spec/messaging_spec.rb
CHANGED
|
@@ -380,6 +380,17 @@ describe AvroTurf::Messaging do
|
|
|
380
380
|
expect(subject).to eq([schema, schema_id])
|
|
381
381
|
end
|
|
382
382
|
|
|
383
|
+
it "memoizes parsed schema" do
|
|
384
|
+
expect(registry).to receive(:subject_version).with(subj, version).twice.and_return(response)
|
|
385
|
+
expect(Avro::Schema).to receive(:parse).with(schema_json).once.and_return(schema)
|
|
386
|
+
|
|
387
|
+
first_result = avro.fetch_schema(subject: subj, version: version)
|
|
388
|
+
second_result = avro.fetch_schema(subject: subj, version: version)
|
|
389
|
+
|
|
390
|
+
expect(first_result).to eq([schema, schema_id])
|
|
391
|
+
expect(second_result).to eq([schema, schema_id])
|
|
392
|
+
end
|
|
393
|
+
|
|
383
394
|
context "with an incompatible schema type" do
|
|
384
395
|
let(:response) { {"id" => schema_id, "schema" => "blah", "schemaType" => schema_type} }
|
|
385
396
|
let(:schema_type) { "PROTOBUF" }
|
|
@@ -403,6 +414,16 @@ describe AvroTurf::Messaging do
|
|
|
403
414
|
it "gets schema from registry" do
|
|
404
415
|
expect(subject).to eq([schema, schema_id])
|
|
405
416
|
end
|
|
417
|
+
|
|
418
|
+
it "memoizes schema in @schemas_by_id after first call" do
|
|
419
|
+
expect(registry).to receive(:fetch).with(schema_id).once.and_return(schema_json)
|
|
420
|
+
|
|
421
|
+
first_result = avro.fetch_schema_by_id(schema_id)
|
|
422
|
+
second_result = avro.fetch_schema_by_id(schema_id)
|
|
423
|
+
|
|
424
|
+
expect(first_result).to eq([schema, schema_id])
|
|
425
|
+
expect(second_result).to eq([schema, schema_id])
|
|
426
|
+
end
|
|
406
427
|
end
|
|
407
428
|
|
|
408
429
|
context "using fetch_schema_by_body" do
|
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# HTTP Contract Tests for FakeConfluentSchemaRegistryServer
|
|
4
|
+
#
|
|
5
|
+
# These tests verify the exact HTTP behavior of the fake schema registry server,
|
|
6
|
+
# including status codes, headers, and response body structure.
|
|
7
|
+
# They serve as a specification for the Rack-based replacement of Sinatra.
|
|
8
|
+
|
|
9
|
+
require "rack/test"
|
|
10
|
+
|
|
11
|
+
RSpec.describe "FakeConfluentSchemaRegistryServer HTTP Contract" do
|
|
12
|
+
include Rack::Test::Methods
|
|
13
|
+
|
|
14
|
+
def app
|
|
15
|
+
AuthorizedFakeConfluentSchemaRegistryServer
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
before do
|
|
19
|
+
# Must call clear on the actual app class to reset the global_config class instance variable
|
|
20
|
+
AuthorizedFakeConfluentSchemaRegistryServer.clear
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def schema(name: "test_schema")
|
|
24
|
+
{
|
|
25
|
+
type: "record",
|
|
26
|
+
name: name,
|
|
27
|
+
fields: [
|
|
28
|
+
{name: "name", type: "string"}
|
|
29
|
+
]
|
|
30
|
+
}.to_json
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def json_content_type
|
|
34
|
+
"application/vnd.schemaregistry+json"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def post_json(path, body)
|
|
38
|
+
post path, body.to_json, "CONTENT_TYPE" => json_content_type
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def put_json(path, body)
|
|
42
|
+
put path, body.to_json, "CONTENT_TYPE" => json_content_type
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe "Response Headers" do
|
|
46
|
+
# Note: Sinatra defaults to text/html, we'll preserve this behavior
|
|
47
|
+
# but our Rack replacement could potentially improve this
|
|
48
|
+
it "returns a content type for successful requests" do
|
|
49
|
+
post_json "/subjects/test/versions", {schema: schema}
|
|
50
|
+
|
|
51
|
+
# Sinatra defaults to text/html;charset=utf-8
|
|
52
|
+
expect(last_response.content_type).to be_truthy
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "returns a content type for error responses" do
|
|
56
|
+
get "/schemas/ids/999"
|
|
57
|
+
|
|
58
|
+
expect(last_response.content_type).to be_truthy
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe "POST /subjects/:subject/versions" do
|
|
63
|
+
it "returns 200 status for successful registration" do
|
|
64
|
+
post_json "/subjects/test-subject/versions", {schema: schema}
|
|
65
|
+
|
|
66
|
+
expect(last_response.status).to eq(200)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "returns JSON with 'id' key" do
|
|
70
|
+
post_json "/subjects/test-subject/versions", {schema: schema}
|
|
71
|
+
|
|
72
|
+
body = JSON.parse(last_response.body)
|
|
73
|
+
expect(body).to have_key("id")
|
|
74
|
+
expect(body["id"]).to be_a(Integer)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "returns same id for same schema in same subject" do
|
|
78
|
+
post_json "/subjects/test-subject/versions", {schema: schema}
|
|
79
|
+
first_id = JSON.parse(last_response.body)["id"]
|
|
80
|
+
|
|
81
|
+
post_json "/subjects/test-subject/versions", {schema: schema}
|
|
82
|
+
second_id = JSON.parse(last_response.body)["id"]
|
|
83
|
+
|
|
84
|
+
expect(second_id).to eq(first_id)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "returns same id for same schema in different subject" do
|
|
88
|
+
post_json "/subjects/subject1/versions", {schema: schema}
|
|
89
|
+
first_id = JSON.parse(last_response.body)["id"]
|
|
90
|
+
|
|
91
|
+
post_json "/subjects/subject2/versions", {schema: schema}
|
|
92
|
+
second_id = JSON.parse(last_response.body)["id"]
|
|
93
|
+
|
|
94
|
+
expect(second_id).to eq(first_id)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it "returns different id for different schema" do
|
|
98
|
+
post_json "/subjects/test-subject/versions", {schema: schema(name: "schema1")}
|
|
99
|
+
first_id = JSON.parse(last_response.body)["id"]
|
|
100
|
+
|
|
101
|
+
post_json "/subjects/test-subject/versions", {schema: schema(name: "schema2")}
|
|
102
|
+
second_id = JSON.parse(last_response.body)["id"]
|
|
103
|
+
|
|
104
|
+
expect(second_id).not_to eq(first_id)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
context "with schema context" do
|
|
108
|
+
it "supports qualified subject names" do
|
|
109
|
+
post_json "/subjects/:.context1:test/versions", {schema: schema}
|
|
110
|
+
|
|
111
|
+
expect(last_response.status).to eq(200)
|
|
112
|
+
body = JSON.parse(last_response.body)
|
|
113
|
+
expect(body).to have_key("id")
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "isolates schemas by context" do
|
|
117
|
+
post_json "/subjects/:.ctx1:test/versions", {schema: schema(name: "s1")}
|
|
118
|
+
id1 = JSON.parse(last_response.body)["id"]
|
|
119
|
+
|
|
120
|
+
post_json "/subjects/:.ctx2:test/versions", {schema: schema(name: "s2")}
|
|
121
|
+
id2 = JSON.parse(last_response.body)["id"]
|
|
122
|
+
|
|
123
|
+
# Different contexts start from 0
|
|
124
|
+
expect(id1).to eq(id2)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
describe "GET /schemas/ids/:schema_id" do
|
|
130
|
+
it "returns 200 status for existing schema" do
|
|
131
|
+
post_json "/subjects/test/versions", {schema: schema}
|
|
132
|
+
schema_id = JSON.parse(last_response.body)["id"]
|
|
133
|
+
|
|
134
|
+
get "/schemas/ids/#{schema_id}"
|
|
135
|
+
|
|
136
|
+
expect(last_response.status).to eq(200)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it "returns JSON with 'schema' key containing the schema JSON" do
|
|
140
|
+
test_schema = schema(name: "my_schema")
|
|
141
|
+
post_json "/subjects/test/versions", {schema: test_schema}
|
|
142
|
+
schema_id = JSON.parse(last_response.body)["id"]
|
|
143
|
+
|
|
144
|
+
get "/schemas/ids/#{schema_id}"
|
|
145
|
+
|
|
146
|
+
body = JSON.parse(last_response.body)
|
|
147
|
+
expect(body).to have_key("schema")
|
|
148
|
+
expect(body["schema"]).to eq(test_schema)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it "returns 404 status for non-existent schema" do
|
|
152
|
+
get "/schemas/ids/999"
|
|
153
|
+
|
|
154
|
+
expect(last_response.status).to eq(404)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it "returns error JSON for non-existent schema" do
|
|
158
|
+
get "/schemas/ids/999"
|
|
159
|
+
|
|
160
|
+
body = JSON.parse(last_response.body)
|
|
161
|
+
expect(body["error_code"]).to eq(40403)
|
|
162
|
+
expect(body["message"]).to eq("Schema not found")
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
context "with schema context" do
|
|
166
|
+
it "fetches schema from specified context via query param" do
|
|
167
|
+
test_schema = schema(name: "ctx_schema")
|
|
168
|
+
post_json "/subjects/:.myctx:test/versions", {schema: test_schema}
|
|
169
|
+
schema_id = JSON.parse(last_response.body)["id"]
|
|
170
|
+
|
|
171
|
+
get "/schemas/ids/#{schema_id}?subject=:.myctx:"
|
|
172
|
+
|
|
173
|
+
expect(last_response.status).to eq(200)
|
|
174
|
+
body = JSON.parse(last_response.body)
|
|
175
|
+
expect(body["schema"]).to eq(test_schema)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
describe "GET /schemas/ids/:schema_id/versions" do
|
|
181
|
+
it "returns 200 status for existing schema" do
|
|
182
|
+
post_json "/subjects/test/versions", {schema: schema}
|
|
183
|
+
schema_id = JSON.parse(last_response.body)["id"]
|
|
184
|
+
|
|
185
|
+
get "/schemas/ids/#{schema_id}/versions"
|
|
186
|
+
|
|
187
|
+
expect(last_response.status).to eq(200)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it "returns array of subject/version objects" do
|
|
191
|
+
post_json "/subjects/test-subject/versions", {schema: schema}
|
|
192
|
+
schema_id = JSON.parse(last_response.body)["id"]
|
|
193
|
+
|
|
194
|
+
get "/schemas/ids/#{schema_id}/versions"
|
|
195
|
+
|
|
196
|
+
body = JSON.parse(last_response.body)
|
|
197
|
+
expect(body).to be_an(Array)
|
|
198
|
+
expect(body.first).to have_key("subject")
|
|
199
|
+
expect(body.first).to have_key("version")
|
|
200
|
+
expect(body.first["subject"]).to eq("test-subject")
|
|
201
|
+
expect(body.first["version"]).to eq(1)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it "returns all subjects using the schema" do
|
|
205
|
+
test_schema = schema(name: "shared")
|
|
206
|
+
post_json "/subjects/subject1/versions", {schema: test_schema}
|
|
207
|
+
post_json "/subjects/subject2/versions", {schema: test_schema}
|
|
208
|
+
schema_id = JSON.parse(last_response.body)["id"]
|
|
209
|
+
|
|
210
|
+
get "/schemas/ids/#{schema_id}/versions"
|
|
211
|
+
|
|
212
|
+
body = JSON.parse(last_response.body)
|
|
213
|
+
subjects = body.map { |v| v["subject"] }
|
|
214
|
+
expect(subjects).to include("subject1", "subject2")
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it "returns 404 for non-existent schema" do
|
|
218
|
+
get "/schemas/ids/999/versions"
|
|
219
|
+
|
|
220
|
+
expect(last_response.status).to eq(404)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
describe "GET /subjects" do
|
|
225
|
+
it "returns 200 status" do
|
|
226
|
+
get "/subjects"
|
|
227
|
+
|
|
228
|
+
expect(last_response.status).to eq(200)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
it "returns empty array when no subjects" do
|
|
232
|
+
get "/subjects"
|
|
233
|
+
|
|
234
|
+
body = JSON.parse(last_response.body)
|
|
235
|
+
expect(body).to eq([])
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
it "returns array of subject names" do
|
|
239
|
+
post_json "/subjects/subject1/versions", {schema: schema(name: "s1")}
|
|
240
|
+
post_json "/subjects/subject2/versions", {schema: schema(name: "s2")}
|
|
241
|
+
|
|
242
|
+
get "/subjects"
|
|
243
|
+
|
|
244
|
+
body = JSON.parse(last_response.body)
|
|
245
|
+
expect(body).to include("subject1", "subject2")
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
it "includes subjects from all contexts" do
|
|
249
|
+
post_json "/subjects/plain-subject/versions", {schema: schema(name: "s1")}
|
|
250
|
+
post_json "/subjects/:.ctx:context-subject/versions", {schema: schema(name: "s2")}
|
|
251
|
+
|
|
252
|
+
get "/subjects"
|
|
253
|
+
|
|
254
|
+
body = JSON.parse(last_response.body)
|
|
255
|
+
expect(body).to include("plain-subject")
|
|
256
|
+
expect(body).to include(":.ctx:context-subject")
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
describe "GET /subjects/:subject/versions" do
|
|
261
|
+
it "returns 200 status for existing subject" do
|
|
262
|
+
post_json "/subjects/test/versions", {schema: schema}
|
|
263
|
+
|
|
264
|
+
get "/subjects/test/versions"
|
|
265
|
+
|
|
266
|
+
expect(last_response.status).to eq(200)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
it "returns array of version numbers" do
|
|
270
|
+
post_json "/subjects/test/versions", {schema: schema(name: "v1")}
|
|
271
|
+
post_json "/subjects/test/versions", {schema: schema(name: "v2")}
|
|
272
|
+
|
|
273
|
+
get "/subjects/test/versions"
|
|
274
|
+
|
|
275
|
+
body = JSON.parse(last_response.body)
|
|
276
|
+
expect(body).to eq([1, 2])
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
it "returns 404 for non-existent subject" do
|
|
280
|
+
get "/subjects/nonexistent/versions"
|
|
281
|
+
|
|
282
|
+
expect(last_response.status).to eq(404)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
it "returns error JSON for non-existent subject" do
|
|
286
|
+
get "/subjects/nonexistent/versions"
|
|
287
|
+
|
|
288
|
+
body = JSON.parse(last_response.body)
|
|
289
|
+
expect(body["error_code"]).to eq(40401)
|
|
290
|
+
expect(body["message"]).to eq("Subject not found")
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
describe "GET /subjects/:subject/versions/:version" do
|
|
295
|
+
before do
|
|
296
|
+
post_json "/subjects/test/versions", {schema: schema(name: "version1")}
|
|
297
|
+
@schema1 = schema(name: "version1")
|
|
298
|
+
@id1 = JSON.parse(last_response.body)["id"]
|
|
299
|
+
|
|
300
|
+
post_json "/subjects/test/versions", {schema: schema(name: "version2")}
|
|
301
|
+
@schema2 = schema(name: "version2")
|
|
302
|
+
@id2 = JSON.parse(last_response.body)["id"]
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
it "returns 200 status for existing version" do
|
|
306
|
+
get "/subjects/test/versions/1"
|
|
307
|
+
|
|
308
|
+
expect(last_response.status).to eq(200)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
it "returns full schema details" do
|
|
312
|
+
get "/subjects/test/versions/1"
|
|
313
|
+
|
|
314
|
+
body = JSON.parse(last_response.body)
|
|
315
|
+
expect(body["subject"]).to eq("test")
|
|
316
|
+
expect(body["version"]).to eq(1)
|
|
317
|
+
expect(body["id"]).to eq(@id1)
|
|
318
|
+
expect(body["schema"]).to eq(@schema1)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
it "returns correct version when version number specified" do
|
|
322
|
+
get "/subjects/test/versions/2"
|
|
323
|
+
|
|
324
|
+
body = JSON.parse(last_response.body)
|
|
325
|
+
expect(body["version"]).to eq(2)
|
|
326
|
+
expect(body["id"]).to eq(@id2)
|
|
327
|
+
expect(body["schema"]).to eq(@schema2)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
it "supports 'latest' as version" do
|
|
331
|
+
get "/subjects/test/versions/latest"
|
|
332
|
+
|
|
333
|
+
body = JSON.parse(last_response.body)
|
|
334
|
+
expect(body["version"]).to eq(2)
|
|
335
|
+
expect(body["schema"]).to eq(@schema2)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
it "returns 404 for non-existent subject" do
|
|
339
|
+
get "/subjects/nonexistent/versions/1"
|
|
340
|
+
|
|
341
|
+
expect(last_response.status).to eq(404)
|
|
342
|
+
body = JSON.parse(last_response.body)
|
|
343
|
+
expect(body["error_code"]).to eq(40401)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
it "returns 404 for non-existent version" do
|
|
347
|
+
get "/subjects/test/versions/99"
|
|
348
|
+
|
|
349
|
+
expect(last_response.status).to eq(404)
|
|
350
|
+
body = JSON.parse(last_response.body)
|
|
351
|
+
expect(body["error_code"]).to eq(40402)
|
|
352
|
+
expect(body["message"]).to eq("Version not found")
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
describe "POST /subjects/:subject (check schema)" do
|
|
357
|
+
before do
|
|
358
|
+
@test_schema = schema(name: "registered")
|
|
359
|
+
post_json "/subjects/test/versions", {schema: @test_schema}
|
|
360
|
+
@schema_id = JSON.parse(last_response.body)["id"]
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
it "returns 200 status for registered schema" do
|
|
364
|
+
post_json "/subjects/test", {schema: @test_schema}
|
|
365
|
+
|
|
366
|
+
expect(last_response.status).to eq(200)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
it "returns schema details for registered schema" do
|
|
370
|
+
post_json "/subjects/test", {schema: @test_schema}
|
|
371
|
+
|
|
372
|
+
body = JSON.parse(last_response.body)
|
|
373
|
+
expect(body["subject"]).to eq("test")
|
|
374
|
+
expect(body["id"]).to eq(@schema_id)
|
|
375
|
+
expect(body["version"]).to eq(1)
|
|
376
|
+
expect(body["schema"]).to eq(@test_schema)
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
it "returns 404 for unregistered schema" do
|
|
380
|
+
post_json "/subjects/test", {schema: schema(name: "unregistered")}
|
|
381
|
+
|
|
382
|
+
expect(last_response.status).to eq(404)
|
|
383
|
+
body = JSON.parse(last_response.body)
|
|
384
|
+
expect(body["error_code"]).to eq(40403)
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
describe "GET /config" do
|
|
389
|
+
it "returns 200 status" do
|
|
390
|
+
get "/config"
|
|
391
|
+
|
|
392
|
+
expect(last_response.status).to eq(200)
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
it "returns default global config" do
|
|
396
|
+
get "/config"
|
|
397
|
+
|
|
398
|
+
body = JSON.parse(last_response.body)
|
|
399
|
+
expect(body["compatibility"]).to eq("BACKWARD")
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
describe "PUT /config" do
|
|
404
|
+
it "returns 200 status" do
|
|
405
|
+
put_json "/config", {compatibility: "FULL"}
|
|
406
|
+
|
|
407
|
+
expect(last_response.status).to eq(200)
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
it "updates and returns the new config" do
|
|
411
|
+
put_json "/config", {compatibility: "FULL"}
|
|
412
|
+
|
|
413
|
+
body = JSON.parse(last_response.body)
|
|
414
|
+
expect(body["compatibility"]).to eq("FULL")
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
it "persists the updated config" do
|
|
418
|
+
put_json "/config", {compatibility: "NONE"}
|
|
419
|
+
|
|
420
|
+
get "/config"
|
|
421
|
+
body = JSON.parse(last_response.body)
|
|
422
|
+
expect(body["compatibility"]).to eq("NONE")
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
describe "GET /config/:subject" do
|
|
427
|
+
it "returns 200 status" do
|
|
428
|
+
get "/config/test-subject"
|
|
429
|
+
|
|
430
|
+
expect(last_response.status).to eq(200)
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
it "returns global config when subject config not set" do
|
|
434
|
+
get "/config/test-subject"
|
|
435
|
+
|
|
436
|
+
body = JSON.parse(last_response.body)
|
|
437
|
+
expect(body["compatibility"]).to eq("BACKWARD")
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
it "returns subject-specific config when set" do
|
|
441
|
+
put_json "/config/test-subject", {compatibility: "FORWARD"}
|
|
442
|
+
|
|
443
|
+
get "/config/test-subject"
|
|
444
|
+
|
|
445
|
+
body = JSON.parse(last_response.body)
|
|
446
|
+
expect(body["compatibility"]).to eq("FORWARD")
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
describe "PUT /config/:subject" do
|
|
451
|
+
it "returns 200 status" do
|
|
452
|
+
put_json "/config/test-subject", {compatibility: "FORWARD"}
|
|
453
|
+
|
|
454
|
+
expect(last_response.status).to eq(200)
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
it "updates and returns the subject config" do
|
|
458
|
+
put_json "/config/test-subject", {compatibility: "FORWARD"}
|
|
459
|
+
|
|
460
|
+
body = JSON.parse(last_response.body)
|
|
461
|
+
expect(body["compatibility"]).to eq("FORWARD")
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
it "does not affect global config" do
|
|
465
|
+
put_json "/config/test-subject", {compatibility: "NONE"}
|
|
466
|
+
|
|
467
|
+
get "/config"
|
|
468
|
+
body = JSON.parse(last_response.body)
|
|
469
|
+
expect(body["compatibility"]).to eq("BACKWARD")
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
it "does not affect other subjects" do
|
|
473
|
+
put_json "/config/subject1", {compatibility: "NONE"}
|
|
474
|
+
|
|
475
|
+
get "/config/subject2"
|
|
476
|
+
body = JSON.parse(last_response.body)
|
|
477
|
+
expect(body["compatibility"]).to eq("BACKWARD")
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
describe "clear class method" do
|
|
482
|
+
it "resets all state" do
|
|
483
|
+
post_json "/subjects/test/versions", {schema: schema}
|
|
484
|
+
put_json "/config", {compatibility: "NONE"}
|
|
485
|
+
|
|
486
|
+
# Must call clear on the same class used as app
|
|
487
|
+
AuthorizedFakeConfluentSchemaRegistryServer.clear
|
|
488
|
+
|
|
489
|
+
get "/subjects"
|
|
490
|
+
expect(JSON.parse(last_response.body)).to eq([])
|
|
491
|
+
|
|
492
|
+
get "/config"
|
|
493
|
+
expect(JSON.parse(last_response.body)["compatibility"]).to eq("BACKWARD")
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
RSpec.describe "FakePrefixedConfluentSchemaRegistryServer HTTP Contract" do
|
|
499
|
+
include Rack::Test::Methods
|
|
500
|
+
|
|
501
|
+
def app
|
|
502
|
+
AuthorizedFakePrefixedConfluentSchemaRegistryServer
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
before do
|
|
506
|
+
# Must call clear on the actual app class to reset the global_config class instance variable
|
|
507
|
+
AuthorizedFakePrefixedConfluentSchemaRegistryServer.clear
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
def schema(name: "test_schema")
|
|
511
|
+
{
|
|
512
|
+
type: "record",
|
|
513
|
+
name: name,
|
|
514
|
+
fields: [
|
|
515
|
+
{name: "name", type: "string"}
|
|
516
|
+
]
|
|
517
|
+
}.to_json
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
def json_content_type
|
|
521
|
+
"application/vnd.schemaregistry+json"
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
def post_json(path, body)
|
|
525
|
+
post path, body.to_json, "CONTENT_TYPE" => json_content_type
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def put_json(path, body)
|
|
529
|
+
put path, body.to_json, "CONTENT_TYPE" => json_content_type
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
describe "prefixed routes" do
|
|
533
|
+
describe "POST /prefix/subjects/:subject/versions" do
|
|
534
|
+
it "returns 200 status and schema id" do
|
|
535
|
+
post_json "/prefix/subjects/test/versions", {schema: schema}
|
|
536
|
+
|
|
537
|
+
expect(last_response.status).to eq(200)
|
|
538
|
+
body = JSON.parse(last_response.body)
|
|
539
|
+
expect(body).to have_key("id")
|
|
540
|
+
end
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
describe "GET /prefix/schemas/ids/:schema_id" do
|
|
544
|
+
it "returns schema by id" do
|
|
545
|
+
post_json "/prefix/subjects/test/versions", {schema: schema}
|
|
546
|
+
schema_id = JSON.parse(last_response.body)["id"]
|
|
547
|
+
|
|
548
|
+
get "/prefix/schemas/ids/#{schema_id}"
|
|
549
|
+
|
|
550
|
+
expect(last_response.status).to eq(200)
|
|
551
|
+
body = JSON.parse(last_response.body)
|
|
552
|
+
expect(body).to have_key("schema")
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
it "returns 404 for non-existent schema" do
|
|
556
|
+
get "/prefix/schemas/ids/999"
|
|
557
|
+
|
|
558
|
+
expect(last_response.status).to eq(404)
|
|
559
|
+
end
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
describe "GET /prefix/subjects" do
|
|
563
|
+
it "returns list of subjects" do
|
|
564
|
+
post_json "/prefix/subjects/test1/versions", {schema: schema(name: "s1")}
|
|
565
|
+
post_json "/prefix/subjects/test2/versions", {schema: schema(name: "s2")}
|
|
566
|
+
|
|
567
|
+
get "/prefix/subjects"
|
|
568
|
+
|
|
569
|
+
expect(last_response.status).to eq(200)
|
|
570
|
+
body = JSON.parse(last_response.body)
|
|
571
|
+
expect(body).to include("test1", "test2")
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
describe "GET /prefix/subjects/:subject/versions" do
|
|
576
|
+
it "returns version list" do
|
|
577
|
+
post_json "/prefix/subjects/test/versions", {schema: schema(name: "v1")}
|
|
578
|
+
post_json "/prefix/subjects/test/versions", {schema: schema(name: "v2")}
|
|
579
|
+
|
|
580
|
+
get "/prefix/subjects/test/versions"
|
|
581
|
+
|
|
582
|
+
expect(last_response.status).to eq(200)
|
|
583
|
+
body = JSON.parse(last_response.body)
|
|
584
|
+
expect(body).to eq([1, 2])
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
it "returns 404 for non-existent subject" do
|
|
588
|
+
get "/prefix/subjects/nonexistent/versions"
|
|
589
|
+
|
|
590
|
+
expect(last_response.status).to eq(404)
|
|
591
|
+
end
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
describe "GET /prefix/subjects/:subject/versions/:version" do
|
|
595
|
+
it "returns schema details" do
|
|
596
|
+
test_schema = schema(name: "versioned")
|
|
597
|
+
post_json "/prefix/subjects/test/versions", {schema: test_schema}
|
|
598
|
+
schema_id = JSON.parse(last_response.body)["id"]
|
|
599
|
+
|
|
600
|
+
get "/prefix/subjects/test/versions/1"
|
|
601
|
+
|
|
602
|
+
expect(last_response.status).to eq(200)
|
|
603
|
+
body = JSON.parse(last_response.body)
|
|
604
|
+
expect(body["name"]).to eq("test")
|
|
605
|
+
expect(body["version"]).to eq(1)
|
|
606
|
+
expect(body["id"]).to eq(schema_id)
|
|
607
|
+
expect(body["schema"]).to eq(test_schema)
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
it "supports 'latest' version" do
|
|
611
|
+
post_json "/prefix/subjects/test/versions", {schema: schema(name: "v1")}
|
|
612
|
+
post_json "/prefix/subjects/test/versions", {schema: schema(name: "v2")}
|
|
613
|
+
|
|
614
|
+
get "/prefix/subjects/test/versions/latest"
|
|
615
|
+
|
|
616
|
+
expect(last_response.status).to eq(200)
|
|
617
|
+
body = JSON.parse(last_response.body)
|
|
618
|
+
expect(body["version"]).to eq(2)
|
|
619
|
+
end
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
describe "POST /prefix/subjects/:subject (check schema)" do
|
|
623
|
+
it "returns schema details for registered schema" do
|
|
624
|
+
test_schema = schema(name: "check")
|
|
625
|
+
post_json "/prefix/subjects/test/versions", {schema: test_schema}
|
|
626
|
+
schema_id = JSON.parse(last_response.body)["id"]
|
|
627
|
+
|
|
628
|
+
post_json "/prefix/subjects/test", {schema: test_schema}
|
|
629
|
+
|
|
630
|
+
expect(last_response.status).to eq(200)
|
|
631
|
+
body = JSON.parse(last_response.body)
|
|
632
|
+
expect(body["subject"]).to eq("test")
|
|
633
|
+
expect(body["id"]).to eq(schema_id)
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
it "returns 404 for unregistered schema" do
|
|
637
|
+
post_json "/prefix/subjects/test/versions", {schema: schema(name: "one")}
|
|
638
|
+
|
|
639
|
+
post_json "/prefix/subjects/test", {schema: schema(name: "other")}
|
|
640
|
+
|
|
641
|
+
expect(last_response.status).to eq(404)
|
|
642
|
+
end
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
describe "GET /prefix/config" do
|
|
646
|
+
it "returns global config" do
|
|
647
|
+
get "/prefix/config"
|
|
648
|
+
|
|
649
|
+
expect(last_response.status).to eq(200)
|
|
650
|
+
body = JSON.parse(last_response.body)
|
|
651
|
+
expect(body["compatibility"]).to eq("BACKWARD")
|
|
652
|
+
end
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
describe "PUT /prefix/config" do
|
|
656
|
+
it "updates global config" do
|
|
657
|
+
put_json "/prefix/config", {compatibility: "FULL"}
|
|
658
|
+
|
|
659
|
+
expect(last_response.status).to eq(200)
|
|
660
|
+
body = JSON.parse(last_response.body)
|
|
661
|
+
expect(body["compatibility"]).to eq("FULL")
|
|
662
|
+
end
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
describe "GET /prefix/config/:subject" do
|
|
666
|
+
it "returns subject config or global default" do
|
|
667
|
+
get "/prefix/config/test-subject"
|
|
668
|
+
|
|
669
|
+
expect(last_response.status).to eq(200)
|
|
670
|
+
body = JSON.parse(last_response.body)
|
|
671
|
+
expect(body["compatibility"]).to eq("BACKWARD")
|
|
672
|
+
end
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
describe "PUT /prefix/config/:subject" do
|
|
676
|
+
it "updates subject config" do
|
|
677
|
+
put_json "/prefix/config/test-subject", {compatibility: "NONE"}
|
|
678
|
+
|
|
679
|
+
expect(last_response.status).to eq(200)
|
|
680
|
+
body = JSON.parse(last_response.body)
|
|
681
|
+
expect(body["compatibility"]).to eq("NONE")
|
|
682
|
+
end
|
|
683
|
+
end
|
|
684
|
+
end
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
RSpec.describe "Host Authorization" do
|
|
688
|
+
include Rack::Test::Methods
|
|
689
|
+
|
|
690
|
+
def schema
|
|
691
|
+
{
|
|
692
|
+
type: "record",
|
|
693
|
+
name: "test",
|
|
694
|
+
fields: [{name: "name", type: "string"}]
|
|
695
|
+
}.to_json
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
describe "AuthorizedFakeConfluentSchemaRegistryServer" do
|
|
699
|
+
def app
|
|
700
|
+
AuthorizedFakeConfluentSchemaRegistryServer
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
it "allows requests from permitted hosts" do
|
|
704
|
+
# The default Rack::Test host is "example.org" which is in the permitted list
|
|
705
|
+
get "/subjects"
|
|
706
|
+
|
|
707
|
+
expect(last_response.status).to eq(200)
|
|
708
|
+
end
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
describe "AuthorizedFakePrefixedConfluentSchemaRegistryServer" do
|
|
712
|
+
def app
|
|
713
|
+
AuthorizedFakePrefixedConfluentSchemaRegistryServer
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
it "allows requests from permitted hosts" do
|
|
717
|
+
get "/prefix/subjects"
|
|
718
|
+
|
|
719
|
+
expect(last_response.status).to eq(200)
|
|
720
|
+
end
|
|
721
|
+
end
|
|
722
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: avro_turf
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.20.
|
|
4
|
+
version: 1.20.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel Schierbeck
|
|
@@ -119,20 +119,6 @@ dependencies:
|
|
|
119
119
|
- - ">="
|
|
120
120
|
- !ruby/object:Gem::Version
|
|
121
121
|
version: '0'
|
|
122
|
-
- !ruby/object:Gem::Dependency
|
|
123
|
-
name: sinatra
|
|
124
|
-
requirement: !ruby/object:Gem::Requirement
|
|
125
|
-
requirements:
|
|
126
|
-
- - ">="
|
|
127
|
-
- !ruby/object:Gem::Version
|
|
128
|
-
version: '0'
|
|
129
|
-
type: :development
|
|
130
|
-
prerelease: false
|
|
131
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
132
|
-
requirements:
|
|
133
|
-
- - ">="
|
|
134
|
-
- !ruby/object:Gem::Version
|
|
135
|
-
version: '0'
|
|
136
122
|
- !ruby/object:Gem::Dependency
|
|
137
123
|
name: json_spec
|
|
138
124
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -217,6 +203,7 @@ files:
|
|
|
217
203
|
- lib/avro_turf/test/fake_confluent_schema_registry_server.rb
|
|
218
204
|
- lib/avro_turf/test/fake_prefixed_confluent_schema_registry_server.rb
|
|
219
205
|
- lib/avro_turf/test/fake_schema_registry_server.rb
|
|
206
|
+
- lib/avro_turf/test/fake_server.rb
|
|
220
207
|
- lib/avro_turf/version.rb
|
|
221
208
|
- perf/address.avsc
|
|
222
209
|
- perf/encoding_size.rb
|
|
@@ -244,6 +231,7 @@ files:
|
|
|
244
231
|
- spec/support/authorized_fake_confluent_schema_registry_server.rb
|
|
245
232
|
- spec/support/authorized_fake_prefixed_confluent_schema_registry_server.rb
|
|
246
233
|
- spec/support/confluent_schema_registry_context.rb
|
|
234
|
+
- spec/test/fake_confluent_schema_registry_server_http_contract_spec.rb
|
|
247
235
|
- spec/test/fake_confluent_schema_registry_server_spec.rb
|
|
248
236
|
homepage: https://github.com/dasch/avro_turf
|
|
249
237
|
licenses:
|