avro_turf 1.19.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/.github/workflows/ruby.yml +20 -11
- data/CHANGELOG.md +12 -0
- data/Gemfile +5 -2
- data/Rakefile +2 -1
- data/avro_turf.gemspec +16 -17
- data/lib/avro_turf/cached_confluent_schema_registry.rb +9 -8
- data/lib/avro_turf/cached_schema_registry.rb +3 -1
- data/lib/avro_turf/confluent_schema_registry.rb +23 -17
- data/lib/avro_turf/core_ext/date.rb +2 -0
- data/lib/avro_turf/core_ext/enumerable.rb +2 -0
- data/lib/avro_turf/core_ext/false_class.rb +2 -0
- data/lib/avro_turf/core_ext/hash.rb +4 -2
- data/lib/avro_turf/core_ext/nil_class.rb +2 -0
- data/lib/avro_turf/core_ext/numeric.rb +2 -0
- data/lib/avro_turf/core_ext/string.rb +2 -0
- data/lib/avro_turf/core_ext/symbol.rb +2 -0
- data/lib/avro_turf/core_ext/time.rb +2 -0
- data/lib/avro_turf/core_ext/true_class.rb +2 -0
- data/lib/avro_turf/core_ext.rb +12 -10
- data/lib/avro_turf/disk_cache.rb +13 -12
- data/lib/avro_turf/in_memory_cache.rb +2 -0
- data/lib/avro_turf/messaging.rb +25 -15
- data/lib/avro_turf/mutable_schema_store.rb +25 -4
- data/lib/avro_turf/schema_registry.rb +3 -1
- data/lib/avro_turf/schema_store.rb +3 -2
- data/lib/avro_turf/schema_to_avro_patch.rb +14 -12
- data/lib/avro_turf/test/fake_confluent_schema_registry_server.rb +39 -37
- data/lib/avro_turf/test/fake_prefixed_confluent_schema_registry_server.rb +12 -10
- data/lib/avro_turf/test/fake_schema_registry_server.rb +3 -1
- data/lib/avro_turf/test/fake_server.rb +186 -0
- data/lib/avro_turf/version.rb +3 -1
- data/lib/avro_turf.rb +15 -13
- data/perf/encoding_size.rb +4 -2
- data/perf/encoding_speed.rb +4 -2
- data/spec/avro_turf_spec.rb +24 -23
- data/spec/cached_confluent_schema_registry_spec.rb +9 -7
- data/spec/confluent_schema_registry_spec.rb +31 -10
- data/spec/core_ext/date_spec.rb +2 -0
- data/spec/core_ext/enumerable_spec.rb +2 -0
- data/spec/core_ext/false_class_spec.rb +2 -0
- data/spec/core_ext/hash_spec.rb +3 -1
- data/spec/core_ext/nil_class_spec.rb +2 -0
- data/spec/core_ext/numeric_spec.rb +2 -0
- data/spec/core_ext/string_spec.rb +2 -0
- data/spec/core_ext/symbol_spec.rb +2 -0
- data/spec/core_ext/time_spec.rb +2 -0
- data/spec/core_ext/true_class_spec.rb +2 -0
- data/spec/disk_cached_confluent_schema_registry_spec.rb +23 -21
- data/spec/messaging_spec.rb +145 -99
- data/spec/mutable_schema_store_spec.rb +134 -0
- data/spec/schema_store_spec.rb +23 -21
- data/spec/schema_to_avro_patch_spec.rb +8 -7
- data/spec/spec_helper.rb +9 -9
- data/spec/support/authorized_fake_confluent_schema_registry_server.rb +4 -2
- data/spec/support/authorized_fake_prefixed_confluent_schema_registry_server.rb +4 -2
- data/spec/support/confluent_schema_registry_context.rb +32 -30
- data/spec/test/fake_confluent_schema_registry_server_http_contract_spec.rb +722 -0
- data/spec/test/fake_confluent_schema_registry_server_spec.rb +97 -94
- metadata +7 -40
|
@@ -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
|