confluent-schema-registry 3.3.2
Sign up to get free protection for your applications and to get access to all the features.
- package/.dockerignore +2 -0
- package/.prettierrc.js +8 -0
- package/CHANGELOG.md +166 -0
- package/Dockerfile +10 -0
- package/LICENSE +21 -0
- package/README.md +44 -0
- package/bin/avdlToAVSC.sh +9 -0
- package/dist/@types.d.ts +93 -0
- package/dist/@types.js +10 -0
- package/dist/@types.js.map +1 -0
- package/dist/AvroHelper.d.ts +12 -0
- package/dist/AvroHelper.js +67 -0
- package/dist/AvroHelper.js.map +1 -0
- package/dist/JsonHelper.d.ts +7 -0
- package/dist/JsonHelper.js +20 -0
- package/dist/JsonHelper.js.map +1 -0
- package/dist/JsonSchema.d.ts +31 -0
- package/dist/JsonSchema.js +58 -0
- package/dist/JsonSchema.js.map +1 -0
- package/dist/ProtoHelper.d.ts +7 -0
- package/dist/ProtoHelper.js +23 -0
- package/dist/ProtoHelper.js.map +1 -0
- package/dist/ProtoSchema.d.ts +14 -0
- package/dist/ProtoSchema.js +66 -0
- package/dist/ProtoSchema.js.map +1 -0
- package/dist/SchemaRegistry.d.ts +48 -0
- package/dist/SchemaRegistry.js +250 -0
- package/dist/SchemaRegistry.js.map +1 -0
- package/dist/api/index.d.ts +43 -0
- package/dist/api/index.js +90 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/middleware/confluentEncoderMiddleware.d.ts +3 -0
- package/dist/api/middleware/confluentEncoderMiddleware.js +31 -0
- package/dist/api/middleware/confluentEncoderMiddleware.js.map +1 -0
- package/dist/api/middleware/errorMiddleware.d.ts +3 -0
- package/dist/api/middleware/errorMiddleware.js +20 -0
- package/dist/api/middleware/errorMiddleware.js.map +1 -0
- package/dist/api/middleware/userAgent.d.ts +3 -0
- package/dist/api/middleware/userAgent.js +18 -0
- package/dist/api/middleware/userAgent.js.map +1 -0
- package/dist/cache.d.ts +20 -0
- package/dist/cache.js +24 -0
- package/dist/cache.js.map +1 -0
- package/dist/constants.d.ts +11 -0
- package/dist/constants.js +15 -0
- package/dist/constants.js.map +1 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +26 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/schemaTypeResolver.d.ts +4 -0
- package/dist/schemaTypeResolver.js +80 -0
- package/dist/schemaTypeResolver.js.map +1 -0
- package/dist/utils/avdlToAVSC.d.ts +2 -0
- package/dist/utils/avdlToAVSC.js +85 -0
- package/dist/utils/avdlToAVSC.js.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/readAVSC.d.ts +3 -0
- package/dist/utils/readAVSC.js +33 -0
- package/dist/utils/readAVSC.js.map +1 -0
- package/dist/wireDecoder.d.ts +7 -0
- package/dist/wireDecoder.js +8 -0
- package/dist/wireDecoder.js.map +1 -0
- package/dist/wireEncoder.d.ts +3 -0
- package/dist/wireEncoder.js +10 -0
- package/dist/wireEncoder.js.map +1 -0
- package/dockest-error.json +11 -0
- package/dockest.ts +30 -0
- package/jest.setup.ts +60 -0
- package/package.json +56 -0
- package/release/CHANGELOG.md +166 -0
- package/release/LICENSE +21 -0
- package/release/README.md +44 -0
- package/release/dist/@types.d.ts +93 -0
- package/release/dist/@types.js +10 -0
- package/release/dist/@types.js.map +1 -0
- package/release/dist/AvroHelper.d.ts +12 -0
- package/release/dist/AvroHelper.js +67 -0
- package/release/dist/AvroHelper.js.map +1 -0
- package/release/dist/JsonHelper.d.ts +7 -0
- package/release/dist/JsonHelper.js +20 -0
- package/release/dist/JsonHelper.js.map +1 -0
- package/release/dist/JsonSchema.d.ts +31 -0
- package/release/dist/JsonSchema.js +58 -0
- package/release/dist/JsonSchema.js.map +1 -0
- package/release/dist/ProtoHelper.d.ts +7 -0
- package/release/dist/ProtoHelper.js +23 -0
- package/release/dist/ProtoHelper.js.map +1 -0
- package/release/dist/ProtoSchema.d.ts +14 -0
- package/release/dist/ProtoSchema.js +66 -0
- package/release/dist/ProtoSchema.js.map +1 -0
- package/release/dist/SchemaRegistry.d.ts +48 -0
- package/release/dist/SchemaRegistry.js +250 -0
- package/release/dist/SchemaRegistry.js.map +1 -0
- package/release/dist/api/index.d.ts +43 -0
- package/release/dist/api/index.js +90 -0
- package/release/dist/api/index.js.map +1 -0
- package/release/dist/api/middleware/confluentEncoderMiddleware.d.ts +3 -0
- package/release/dist/api/middleware/confluentEncoderMiddleware.js +31 -0
- package/release/dist/api/middleware/confluentEncoderMiddleware.js.map +1 -0
- package/release/dist/api/middleware/errorMiddleware.d.ts +3 -0
- package/release/dist/api/middleware/errorMiddleware.js +20 -0
- package/release/dist/api/middleware/errorMiddleware.js.map +1 -0
- package/release/dist/api/middleware/userAgent.d.ts +3 -0
- package/release/dist/api/middleware/userAgent.js +18 -0
- package/release/dist/api/middleware/userAgent.js.map +1 -0
- package/release/dist/cache.d.ts +20 -0
- package/release/dist/cache.js +24 -0
- package/release/dist/cache.js.map +1 -0
- package/release/dist/constants.d.ts +11 -0
- package/release/dist/constants.js +15 -0
- package/release/dist/constants.js.map +1 -0
- package/release/dist/errors.d.ts +14 -0
- package/release/dist/errors.js +26 -0
- package/release/dist/errors.js.map +1 -0
- package/release/dist/index.d.ts +4 -0
- package/release/dist/index.js +13 -0
- package/release/dist/index.js.map +1 -0
- package/release/dist/schemaTypeResolver.d.ts +4 -0
- package/release/dist/schemaTypeResolver.js +80 -0
- package/release/dist/schemaTypeResolver.js.map +1 -0
- package/release/dist/utils/avdlToAVSC.d.ts +2 -0
- package/release/dist/utils/avdlToAVSC.js +85 -0
- package/release/dist/utils/avdlToAVSC.js.map +1 -0
- package/release/dist/utils/index.d.ts +2 -0
- package/release/dist/utils/index.js +9 -0
- package/release/dist/utils/index.js.map +1 -0
- package/release/dist/utils/readAVSC.d.ts +3 -0
- package/release/dist/utils/readAVSC.js +33 -0
- package/release/dist/utils/readAVSC.js.map +1 -0
- package/release/dist/wireDecoder.d.ts +7 -0
- package/release/dist/wireDecoder.js +8 -0
- package/release/dist/wireDecoder.js.map +1 -0
- package/release/dist/wireEncoder.d.ts +3 -0
- package/release/dist/wireEncoder.js +10 -0
- package/release/dist/wireEncoder.js.map +1 -0
- package/release/package.json +56 -0
- package/src/@types.ts +105 -0
- package/src/AvroHelper.ts +91 -0
- package/src/JsonHelper.ts +35 -0
- package/src/JsonSchema.ts +80 -0
- package/src/ProtoHelper.ts +38 -0
- package/src/ProtoSchema.ts +80 -0
- package/src/SchemaRegistry.avro.spec.ts +558 -0
- package/src/SchemaRegistry.json.spec.ts +364 -0
- package/src/SchemaRegistry.newApi.spec.ts +622 -0
- package/src/SchemaRegistry.protobuf.spec.ts +372 -0
- package/src/SchemaRegistry.spec.ts +252 -0
- package/src/SchemaRegistry.ts +387 -0
- package/src/api/index.spec.ts +23 -0
- package/src/api/index.ts +121 -0
- package/src/api/middleware/confluentEncoderMiddleware.ts +36 -0
- package/src/api/middleware/errorMiddleware.spec.ts +67 -0
- package/src/api/middleware/errorMiddleware.ts +37 -0
- package/src/api/middleware/userAgent.spec.ts +53 -0
- package/src/api/middleware/userAgent.ts +19 -0
- package/src/cache.ts +34 -0
- package/src/constants.ts +13 -0
- package/src/errors.ts +26 -0
- package/src/index.ts +4 -0
- package/src/schemaTypeResolver.ts +101 -0
- package/src/utils/avdlToAVSC.spec.ts +79 -0
- package/src/utils/avdlToAVSC.ts +106 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/readAVSC.spec.ts +23 -0
- package/src/utils/readAVSC.ts +36 -0
- package/src/wireDecoder.ts +5 -0
- package/src/wireEncoder.ts +10 -0
- package/tsconfig.json +22 -0
@@ -0,0 +1,372 @@
|
|
1
|
+
import SchemaRegistry, { RegisteredSchema } from './SchemaRegistry'
|
2
|
+
import API from './api'
|
3
|
+
import { ProtoConfluentSchema, SchemaType } from './@types'
|
4
|
+
|
5
|
+
const REGISTRY_HOST = 'http://localhost:8982'
|
6
|
+
const schemaRegistryAPIClientArgs = { host: REGISTRY_HOST }
|
7
|
+
const schemaRegistryArgs = { host: REGISTRY_HOST }
|
8
|
+
|
9
|
+
const TestSchemas = {
|
10
|
+
FirstLevelSchema: {
|
11
|
+
type: SchemaType.PROTOBUF,
|
12
|
+
schema: `
|
13
|
+
syntax = "proto3";
|
14
|
+
package test;
|
15
|
+
import "test/second_level_A.proto";
|
16
|
+
import "test/second_level_B.proto";
|
17
|
+
|
18
|
+
message FirstLevel {
|
19
|
+
int32 id1 = 1;
|
20
|
+
SecondLevelA level1a = 2;
|
21
|
+
SecondLevelB level1b = 3;
|
22
|
+
}`,
|
23
|
+
references: [
|
24
|
+
{
|
25
|
+
name: 'test/second_level_A.proto',
|
26
|
+
subject: 'Proto:SecondLevelA',
|
27
|
+
version: undefined,
|
28
|
+
},
|
29
|
+
{
|
30
|
+
name: 'test/second_level_B.proto',
|
31
|
+
subject: 'Proto:SecondLevelB',
|
32
|
+
version: undefined,
|
33
|
+
},
|
34
|
+
],
|
35
|
+
} as ProtoConfluentSchema,
|
36
|
+
|
37
|
+
SecondLevelASchema: {
|
38
|
+
type: SchemaType.PROTOBUF,
|
39
|
+
schema: `
|
40
|
+
syntax = "proto3";
|
41
|
+
package test;
|
42
|
+
import "test/third_level.proto";
|
43
|
+
|
44
|
+
message SecondLevelA {
|
45
|
+
int32 id2a = 1;
|
46
|
+
ThirdLevel level2a = 2;
|
47
|
+
}`,
|
48
|
+
references: [
|
49
|
+
{
|
50
|
+
name: 'test/third_level.proto',
|
51
|
+
subject: 'Proto:ThirdLevel',
|
52
|
+
version: undefined,
|
53
|
+
},
|
54
|
+
],
|
55
|
+
} as ProtoConfluentSchema,
|
56
|
+
|
57
|
+
SecondLevelBSchema: {
|
58
|
+
type: SchemaType.PROTOBUF,
|
59
|
+
schema: `
|
60
|
+
syntax = "proto3";
|
61
|
+
package test;
|
62
|
+
import "test/third_level.proto";
|
63
|
+
|
64
|
+
message SecondLevelB {
|
65
|
+
int32 id2b = 1;
|
66
|
+
ThirdLevel level2b = 2;
|
67
|
+
}`,
|
68
|
+
references: [
|
69
|
+
{
|
70
|
+
name: 'test/third_level.proto',
|
71
|
+
subject: 'Proto:ThirdLevel',
|
72
|
+
version: undefined,
|
73
|
+
},
|
74
|
+
],
|
75
|
+
} as ProtoConfluentSchema,
|
76
|
+
|
77
|
+
ThirdLevelSchema: {
|
78
|
+
type: SchemaType.PROTOBUF,
|
79
|
+
schema: `
|
80
|
+
syntax = "proto3";
|
81
|
+
package test;
|
82
|
+
|
83
|
+
message ThirdLevel {
|
84
|
+
int32 id3 = 1;
|
85
|
+
}`,
|
86
|
+
} as ProtoConfluentSchema,
|
87
|
+
}
|
88
|
+
|
89
|
+
function apiResponse(result) {
|
90
|
+
return JSON.parse(result.responseData)
|
91
|
+
}
|
92
|
+
|
93
|
+
describe('SchemaRegistry', () => {
|
94
|
+
let schemaRegistry: SchemaRegistry
|
95
|
+
let registeredSchema: RegisteredSchema
|
96
|
+
let api
|
97
|
+
|
98
|
+
beforeEach(async () => {
|
99
|
+
api = API(schemaRegistryAPIClientArgs)
|
100
|
+
schemaRegistry = new SchemaRegistry(schemaRegistryArgs)
|
101
|
+
})
|
102
|
+
|
103
|
+
describe('when register', () => {
|
104
|
+
describe('when no reference', () => {
|
105
|
+
beforeEach(async () => {
|
106
|
+
registeredSchema = await schemaRegistry.register(TestSchemas.ThirdLevelSchema, {
|
107
|
+
subject: 'Proto:ThirdLevel',
|
108
|
+
})
|
109
|
+
})
|
110
|
+
it('should return schema id', async () => {
|
111
|
+
expect(registeredSchema.id).toEqual(expect.any(Number))
|
112
|
+
})
|
113
|
+
|
114
|
+
it('should be able to encode/decode', async () => {
|
115
|
+
const obj = { id3: 3 }
|
116
|
+
|
117
|
+
const buffer = await schemaRegistry.encode(registeredSchema.id, obj)
|
118
|
+
const resultObj = await schemaRegistry.decode(buffer)
|
119
|
+
|
120
|
+
expect(resultObj).toEqual(obj)
|
121
|
+
})
|
122
|
+
})
|
123
|
+
|
124
|
+
describe('with reference', () => {
|
125
|
+
let schemaId
|
126
|
+
let referenceSchema
|
127
|
+
|
128
|
+
beforeEach(async () => {
|
129
|
+
await schemaRegistry.register(TestSchemas.ThirdLevelSchema, {
|
130
|
+
subject: 'Proto:ThirdLevel',
|
131
|
+
})
|
132
|
+
|
133
|
+
const latest = apiResponse(await api.Subject.latestVersion({ subject: 'Proto:ThirdLevel' }))
|
134
|
+
TestSchemas.SecondLevelASchema.references[0].version = latest.version
|
135
|
+
registeredSchema = await schemaRegistry.register(TestSchemas.SecondLevelASchema, {
|
136
|
+
subject: 'Proto:SecondLevelA',
|
137
|
+
})
|
138
|
+
schemaId = registeredSchema.id
|
139
|
+
|
140
|
+
const schemaRaw = apiResponse(await api.Schema.find({ id: schemaId }))
|
141
|
+
referenceSchema = schemaRaw.references[0].subject
|
142
|
+
})
|
143
|
+
|
144
|
+
it('should return schema id', async () => {
|
145
|
+
expect(schemaId).toEqual(expect.any(Number))
|
146
|
+
})
|
147
|
+
it('should create a schema with reference', async () => {
|
148
|
+
expect(referenceSchema).toEqual('Proto:ThirdLevel')
|
149
|
+
})
|
150
|
+
|
151
|
+
it('should be able to encode/decode', async () => {
|
152
|
+
const obj = { id2a: 2, level2a: { id3: 3 } }
|
153
|
+
|
154
|
+
const buffer = await schemaRegistry.encode(registeredSchema.id, obj)
|
155
|
+
const resultObj = await schemaRegistry.decode(buffer)
|
156
|
+
|
157
|
+
expect(resultObj).toEqual(obj)
|
158
|
+
})
|
159
|
+
})
|
160
|
+
|
161
|
+
describe('with multiple reference', () => {
|
162
|
+
beforeEach(async () => {
|
163
|
+
let latest
|
164
|
+
|
165
|
+
await schemaRegistry.register(TestSchemas.ThirdLevelSchema, {
|
166
|
+
subject: 'Proto:ThirdLevel',
|
167
|
+
})
|
168
|
+
|
169
|
+
latest = apiResponse(await api.Subject.latestVersion({ subject: 'Proto:ThirdLevel' }))
|
170
|
+
TestSchemas.SecondLevelASchema.references[0].version = latest.version
|
171
|
+
registeredSchema = await schemaRegistry.register(TestSchemas.SecondLevelASchema, {
|
172
|
+
subject: 'Proto:SecondLevelA',
|
173
|
+
})
|
174
|
+
|
175
|
+
latest = apiResponse(await api.Subject.latestVersion({ subject: 'Proto:ThirdLevel' }))
|
176
|
+
TestSchemas.SecondLevelBSchema.references[0].version = latest.version
|
177
|
+
registeredSchema = await schemaRegistry.register(TestSchemas.SecondLevelBSchema, {
|
178
|
+
subject: 'Proto:SecondLevelB',
|
179
|
+
})
|
180
|
+
|
181
|
+
latest = apiResponse(await api.Subject.latestVersion({ subject: 'Proto:SecondLevelA' }))
|
182
|
+
TestSchemas.FirstLevelSchema.references[0].version = latest.version
|
183
|
+
latest = apiResponse(await api.Subject.latestVersion({ subject: 'Proto:SecondLevelB' }))
|
184
|
+
TestSchemas.FirstLevelSchema.references[1].version = latest.version
|
185
|
+
registeredSchema = await schemaRegistry.register(TestSchemas.FirstLevelSchema, {
|
186
|
+
subject: 'Proto:FirstLevel',
|
187
|
+
})
|
188
|
+
})
|
189
|
+
|
190
|
+
it('should be able to encode/decode', async () => {
|
191
|
+
const obj = {
|
192
|
+
id1: 1,
|
193
|
+
level1a: { id2a: 2, level2a: { id3: 3 } },
|
194
|
+
level1b: { id2b: 4, level2b: { id3: 5 } },
|
195
|
+
}
|
196
|
+
|
197
|
+
const buffer = await schemaRegistry.encode(registeredSchema.id, obj)
|
198
|
+
const resultObj = await schemaRegistry.decode(buffer)
|
199
|
+
|
200
|
+
expect(resultObj).toEqual(obj)
|
201
|
+
})
|
202
|
+
|
203
|
+
it('should be able to encode/decode independent', async () => {
|
204
|
+
const obj = {
|
205
|
+
id1: 1,
|
206
|
+
level1a: { id2a: 2, level2a: { id3: 3 } },
|
207
|
+
level1b: { id2b: 4, level2b: { id3: 5 } },
|
208
|
+
}
|
209
|
+
|
210
|
+
schemaRegistry = new SchemaRegistry(schemaRegistryArgs)
|
211
|
+
const buffer = await schemaRegistry.encode(registeredSchema.id, obj)
|
212
|
+
|
213
|
+
schemaRegistry = new SchemaRegistry(schemaRegistryArgs)
|
214
|
+
const resultObj = await schemaRegistry.decode(buffer)
|
215
|
+
|
216
|
+
expect(resultObj).toEqual(obj)
|
217
|
+
})
|
218
|
+
})
|
219
|
+
})
|
220
|
+
|
221
|
+
describe('_getSchema', () => {
|
222
|
+
let schema
|
223
|
+
|
224
|
+
describe('no references', () => {
|
225
|
+
beforeEach(async () => {
|
226
|
+
registeredSchema = await schemaRegistry.register(TestSchemas.ThirdLevelSchema, {
|
227
|
+
subject: 'Proto:ThirdLevel',
|
228
|
+
})
|
229
|
+
;({ schema } = await schemaRegistry['_getSchema'](registeredSchema.id))
|
230
|
+
})
|
231
|
+
|
232
|
+
it('should return schema that match message', async () => {
|
233
|
+
expect(schema.message.name).toEqual('ThirdLevel')
|
234
|
+
})
|
235
|
+
|
236
|
+
it('should be able to encode/decode', async () => {
|
237
|
+
const obj = { id3: 3 }
|
238
|
+
|
239
|
+
const buffer = await schema.toBuffer(obj)
|
240
|
+
const resultObj = await schema.fromBuffer(buffer)
|
241
|
+
|
242
|
+
expect(resultObj).toEqual(obj)
|
243
|
+
})
|
244
|
+
})
|
245
|
+
|
246
|
+
describe('with references', () => {
|
247
|
+
beforeEach(async () => {
|
248
|
+
await schemaRegistry.register(TestSchemas.ThirdLevelSchema, { subject: 'Proto:ThirdLevel' })
|
249
|
+
|
250
|
+
const latest = apiResponse(await api.Subject.latestVersion({ subject: 'Proto:ThirdLevel' }))
|
251
|
+
TestSchemas.SecondLevelASchema.references[0].version = latest.version
|
252
|
+
registeredSchema = await schemaRegistry.register(TestSchemas.SecondLevelASchema, {
|
253
|
+
subject: 'Proto:SecondLevelA',
|
254
|
+
})
|
255
|
+
;({ schema } = await schemaRegistry['_getSchema'](registeredSchema.id))
|
256
|
+
})
|
257
|
+
|
258
|
+
it('should return schema that match message', async () => {
|
259
|
+
expect(schema.message.name).toEqual('SecondLevelA')
|
260
|
+
})
|
261
|
+
|
262
|
+
it('should be able to encode/decode', async () => {
|
263
|
+
const obj = { id2a: 2, level2a: { id3: 3 } }
|
264
|
+
|
265
|
+
const buffer = await schema.toBuffer(obj)
|
266
|
+
const resultObj = await schema.fromBuffer(buffer)
|
267
|
+
|
268
|
+
expect(resultObj).toEqual(obj)
|
269
|
+
})
|
270
|
+
})
|
271
|
+
|
272
|
+
describe('with multi references', () => {
|
273
|
+
beforeEach(async () => {
|
274
|
+
let latest
|
275
|
+
|
276
|
+
await schemaRegistry.register(TestSchemas.ThirdLevelSchema, {
|
277
|
+
subject: 'Proto:ThirdLevel',
|
278
|
+
})
|
279
|
+
|
280
|
+
latest = apiResponse(await api.Subject.latestVersion({ subject: 'Proto:ThirdLevel' }))
|
281
|
+
TestSchemas.SecondLevelASchema.references[0].version = latest.version
|
282
|
+
registeredSchema = await schemaRegistry.register(TestSchemas.SecondLevelASchema, {
|
283
|
+
subject: 'Proto:SecondLevelA',
|
284
|
+
})
|
285
|
+
|
286
|
+
latest = apiResponse(await api.Subject.latestVersion({ subject: 'Proto:ThirdLevel' }))
|
287
|
+
TestSchemas.SecondLevelBSchema.references[0].version = latest.version
|
288
|
+
registeredSchema = await schemaRegistry.register(TestSchemas.SecondLevelBSchema, {
|
289
|
+
subject: 'Proto:SecondLevelB',
|
290
|
+
})
|
291
|
+
|
292
|
+
latest = apiResponse(await api.Subject.latestVersion({ subject: 'Proto:SecondLevelA' }))
|
293
|
+
TestSchemas.FirstLevelSchema.references[0].version = latest.version
|
294
|
+
latest = apiResponse(await api.Subject.latestVersion({ subject: 'Proto:SecondLevelB' }))
|
295
|
+
TestSchemas.FirstLevelSchema.references[1].version = latest.version
|
296
|
+
registeredSchema = await schemaRegistry.register(TestSchemas.FirstLevelSchema, {
|
297
|
+
subject: 'Proto:FirstLevel',
|
298
|
+
})
|
299
|
+
;({ schema } = await schemaRegistry['_getSchema'](registeredSchema.id))
|
300
|
+
})
|
301
|
+
|
302
|
+
it('should return schema that match message', async () => {
|
303
|
+
expect(schema.message.name).toEqual('FirstLevel')
|
304
|
+
})
|
305
|
+
|
306
|
+
it('should be able to encode/decode', async () => {
|
307
|
+
const obj = {
|
308
|
+
id1: 1,
|
309
|
+
level1a: { id2a: 2, level2a: { id3: 3 } },
|
310
|
+
level1b: { id2b: 4, level2b: { id3: 5 } },
|
311
|
+
}
|
312
|
+
|
313
|
+
const buffer = await schema.toBuffer(obj)
|
314
|
+
const resultObj = await schema.fromBuffer(buffer)
|
315
|
+
|
316
|
+
expect(resultObj).toEqual(obj)
|
317
|
+
})
|
318
|
+
})
|
319
|
+
})
|
320
|
+
|
321
|
+
describe('when document example', () => {
|
322
|
+
it('should encode/decode', async () => {
|
323
|
+
const schemaA = `
|
324
|
+
syntax = "proto3";
|
325
|
+
package test;
|
326
|
+
import "test/B.proto";
|
327
|
+
|
328
|
+
message A {
|
329
|
+
int32 id = 1;
|
330
|
+
B b = 2;
|
331
|
+
}`
|
332
|
+
|
333
|
+
const schemaB = `
|
334
|
+
syntax = "proto3";
|
335
|
+
package test;
|
336
|
+
|
337
|
+
message B {
|
338
|
+
int32 id = 1;
|
339
|
+
}`
|
340
|
+
|
341
|
+
await schemaRegistry.register(
|
342
|
+
{ type: SchemaType.PROTOBUF, schema: schemaB },
|
343
|
+
{ subject: 'Proto:B' },
|
344
|
+
)
|
345
|
+
|
346
|
+
const response = await schemaRegistry.api.Subject.latestVersion({ subject: 'Proto:B' })
|
347
|
+
const { version } = JSON.parse(response.responseData)
|
348
|
+
|
349
|
+
const { id } = await schemaRegistry.register(
|
350
|
+
{
|
351
|
+
type: SchemaType.PROTOBUF,
|
352
|
+
schema: schemaA,
|
353
|
+
references: [
|
354
|
+
{
|
355
|
+
name: 'test/B.proto',
|
356
|
+
subject: 'Proto:B',
|
357
|
+
version,
|
358
|
+
},
|
359
|
+
],
|
360
|
+
},
|
361
|
+
{ subject: 'Proto:A' },
|
362
|
+
)
|
363
|
+
|
364
|
+
const obj = { id: 1, b: { id: 2 } }
|
365
|
+
|
366
|
+
const buffer = await schemaRegistry.encode(id, obj)
|
367
|
+
const decodedObj = await schemaRegistry.decode(buffer)
|
368
|
+
|
369
|
+
expect(decodedObj).toEqual(obj)
|
370
|
+
})
|
371
|
+
})
|
372
|
+
})
|
@@ -0,0 +1,252 @@
|
|
1
|
+
import path from 'path'
|
2
|
+
import { v4 as uuid } from 'uuid'
|
3
|
+
|
4
|
+
import { readAVSC } from './utils'
|
5
|
+
import SchemaRegistry from './SchemaRegistry'
|
6
|
+
import API, { SchemaRegistryAPIClient } from './api'
|
7
|
+
import { COMPATIBILITY, DEFAULT_API_CLIENT_ID } from './constants'
|
8
|
+
import encodedAnotherPersonV2 from '../fixtures/avro/encodedAnotherPersonV2'
|
9
|
+
import wrongMagicByte from '../fixtures/wrongMagicByte'
|
10
|
+
import { RawAvroSchema } from './@types'
|
11
|
+
|
12
|
+
const REGISTRY_HOST = 'http://localhost:8982'
|
13
|
+
const schemaRegistryAPIClientArgs = { host: REGISTRY_HOST }
|
14
|
+
const schemaRegistryArgs = { host: REGISTRY_HOST }
|
15
|
+
|
16
|
+
const personSchema = readAVSC(path.join(__dirname, '../fixtures/avsc/person.avsc'))
|
17
|
+
const payload = { fullName: 'John Doe' } // eslint-disable-line @typescript-eslint/camelcase
|
18
|
+
|
19
|
+
describe('SchemaRegistry - old AVRO api', () => {
|
20
|
+
let schemaRegistry: SchemaRegistry
|
21
|
+
|
22
|
+
beforeEach(async () => {
|
23
|
+
schemaRegistry = new SchemaRegistry(schemaRegistryArgs)
|
24
|
+
await schemaRegistry.register(personSchema)
|
25
|
+
})
|
26
|
+
|
27
|
+
describe('#register', () => {
|
28
|
+
let namespace: string, Schema: RawAvroSchema, subject: string, api: SchemaRegistryAPIClient
|
29
|
+
|
30
|
+
beforeEach(() => {
|
31
|
+
api = API(schemaRegistryAPIClientArgs)
|
32
|
+
namespace = `N${uuid().replace(/-/g, '_')}`
|
33
|
+
subject = `${namespace}.RandomTest`
|
34
|
+
Schema = {
|
35
|
+
namespace,
|
36
|
+
type: 'record',
|
37
|
+
name: 'RandomTest',
|
38
|
+
fields: [{ type: 'string', name: 'fullName' }],
|
39
|
+
}
|
40
|
+
})
|
41
|
+
|
42
|
+
it('uploads the new schema', async () => {
|
43
|
+
await expect(api.Subject.latestVersion({ subject })).rejects.toHaveProperty(
|
44
|
+
'message',
|
45
|
+
`${DEFAULT_API_CLIENT_ID} - Subject '${namespace}.${Schema.name}' not found.`,
|
46
|
+
)
|
47
|
+
|
48
|
+
await expect(schemaRegistry.register(Schema)).resolves.toEqual({ id: expect.any(Number) })
|
49
|
+
})
|
50
|
+
|
51
|
+
it('automatically cache the id and schema', async () => {
|
52
|
+
const { id } = await schemaRegistry.register(Schema)
|
53
|
+
|
54
|
+
expect(schemaRegistry.cache.getSchema(id)).toBeTruthy()
|
55
|
+
})
|
56
|
+
|
57
|
+
it('fetch and validate the latest schema id after registering a new schema', async () => {
|
58
|
+
const { id } = await schemaRegistry.register(Schema)
|
59
|
+
const latestSchemaId = await schemaRegistry.getLatestSchemaId(subject)
|
60
|
+
|
61
|
+
expect(id).toBe(latestSchemaId)
|
62
|
+
})
|
63
|
+
|
64
|
+
it('set the default compatibility to BACKWARD', async () => {
|
65
|
+
await schemaRegistry.register(Schema)
|
66
|
+
const response = await api.Subject.config({ subject })
|
67
|
+
expect(response.data()).toEqual({ compatibilityLevel: COMPATIBILITY.BACKWARD })
|
68
|
+
})
|
69
|
+
|
70
|
+
it('sets the compatibility according to param', async () => {
|
71
|
+
await schemaRegistry.register(Schema, { compatibility: COMPATIBILITY.NONE })
|
72
|
+
const response = await api.Subject.config({ subject })
|
73
|
+
expect(response.data()).toEqual({ compatibilityLevel: COMPATIBILITY.NONE })
|
74
|
+
})
|
75
|
+
|
76
|
+
it('throws an error when schema does not have a name', async () => {
|
77
|
+
delete Schema.name
|
78
|
+
await expect(schemaRegistry.register(Schema)).rejects.toHaveProperty(
|
79
|
+
'message',
|
80
|
+
'Invalid name: undefined',
|
81
|
+
)
|
82
|
+
})
|
83
|
+
|
84
|
+
it('throws an error when schema does not have a namespace', async () => {
|
85
|
+
delete Schema.namespace
|
86
|
+
await expect(schemaRegistry.register(Schema)).rejects.toHaveProperty(
|
87
|
+
'message',
|
88
|
+
'Invalid namespace: undefined',
|
89
|
+
)
|
90
|
+
})
|
91
|
+
|
92
|
+
it('accepts schema without a namespace when subject is specified', async () => {
|
93
|
+
delete Schema.namespace
|
94
|
+
const nonNamespaced = readAVSC(path.join(__dirname, '../fixtures/avsc/non_namespaced.avsc'))
|
95
|
+
await expect(schemaRegistry.register(nonNamespaced, { subject })).resolves.toEqual({
|
96
|
+
id: expect.any(Number),
|
97
|
+
})
|
98
|
+
})
|
99
|
+
|
100
|
+
it('throws an error when the configured compatibility is different than defined in the client', async () => {
|
101
|
+
await schemaRegistry.register(Schema)
|
102
|
+
await api.Subject.updateConfig({ subject, body: { compatibility: COMPATIBILITY.FULL } })
|
103
|
+
await expect(schemaRegistry.register(Schema)).rejects.toHaveProperty(
|
104
|
+
'message',
|
105
|
+
'Compatibility does not match the configuration (BACKWARD != FULL)',
|
106
|
+
)
|
107
|
+
})
|
108
|
+
})
|
109
|
+
|
110
|
+
describe('#encode', () => {
|
111
|
+
beforeEach(async () => {
|
112
|
+
await schemaRegistry.register(personSchema)
|
113
|
+
})
|
114
|
+
|
115
|
+
it('throws an error if registryId is empty', async () => {
|
116
|
+
await expect(schemaRegistry.encode(undefined, payload)).rejects.toHaveProperty(
|
117
|
+
'message',
|
118
|
+
'Invalid registryId: undefined',
|
119
|
+
)
|
120
|
+
})
|
121
|
+
|
122
|
+
it('encodes using a defined registryId', async () => {
|
123
|
+
const SchemaV1 = Object.assign({}, personSchema, {
|
124
|
+
name: 'AnotherPerson',
|
125
|
+
fields: [{ type: 'string', name: 'fullName' }],
|
126
|
+
})
|
127
|
+
const SchemaV2 = Object.assign({}, SchemaV1, {
|
128
|
+
fields: [
|
129
|
+
{ type: 'string', name: 'fullName' },
|
130
|
+
{ type: 'string', name: 'city', default: 'Stockholm' },
|
131
|
+
],
|
132
|
+
})
|
133
|
+
|
134
|
+
const schema1 = await schemaRegistry.register(SchemaV1)
|
135
|
+
const schema2 = await schemaRegistry.register(SchemaV2)
|
136
|
+
expect(schema2.id).not.toEqual(schema1.id)
|
137
|
+
|
138
|
+
const data = await schemaRegistry.encode(schema2.id, payload)
|
139
|
+
expect(data).toMatchConfluentEncodedPayload({
|
140
|
+
registryId: schema2.id,
|
141
|
+
payload: Buffer.from(encodedAnotherPersonV2),
|
142
|
+
})
|
143
|
+
})
|
144
|
+
})
|
145
|
+
|
146
|
+
describe('#decode', () => {
|
147
|
+
let registryId: number
|
148
|
+
|
149
|
+
beforeEach(async () => {
|
150
|
+
registryId = (await schemaRegistry.register(personSchema)).id
|
151
|
+
})
|
152
|
+
|
153
|
+
it('decodes data', async () => {
|
154
|
+
const buffer = Buffer.from(await schemaRegistry.encode(registryId, payload))
|
155
|
+
const data = await schemaRegistry.decode(buffer)
|
156
|
+
|
157
|
+
expect(data).toEqual(payload)
|
158
|
+
})
|
159
|
+
|
160
|
+
it('throws an error if the magic byte is not supported', async () => {
|
161
|
+
const buffer = Buffer.from(wrongMagicByte)
|
162
|
+
await expect(schemaRegistry.decode(buffer)).rejects.toHaveProperty(
|
163
|
+
'message',
|
164
|
+
'Message encoded with magic byte {"type":"Buffer","data":[48]}, expected {"type":"Buffer","data":[0]}',
|
165
|
+
)
|
166
|
+
})
|
167
|
+
|
168
|
+
it('caches the schema', async () => {
|
169
|
+
const buffer = Buffer.from(await schemaRegistry.encode(registryId, payload))
|
170
|
+
|
171
|
+
schemaRegistry.cache.clear()
|
172
|
+
await schemaRegistry.decode(buffer)
|
173
|
+
|
174
|
+
expect(schemaRegistry.cache.getSchema(registryId)).toBeTruthy()
|
175
|
+
})
|
176
|
+
|
177
|
+
it('creates a single origin request for a schema cache-miss', async () => {
|
178
|
+
const buffer = Buffer.from(await schemaRegistry.encode(registryId, payload))
|
179
|
+
|
180
|
+
schemaRegistry.cache.clear()
|
181
|
+
|
182
|
+
const spy = jest.spyOn((schemaRegistry as any).api.Schema, 'find')
|
183
|
+
|
184
|
+
await Promise.all([
|
185
|
+
schemaRegistry.decode(buffer),
|
186
|
+
schemaRegistry.decode(buffer),
|
187
|
+
schemaRegistry.decode(buffer),
|
188
|
+
])
|
189
|
+
|
190
|
+
expect(spy).toHaveBeenCalledTimes(1)
|
191
|
+
})
|
192
|
+
|
193
|
+
describe('when the cache is populated', () => {
|
194
|
+
it('uses the cache data', async () => {
|
195
|
+
const buffer = Buffer.from(await schemaRegistry.encode(registryId, payload))
|
196
|
+
expect(schemaRegistry.cache.getSchema(registryId)).toBeTruthy()
|
197
|
+
|
198
|
+
jest.spyOn(schemaRegistry.cache, 'setSchema')
|
199
|
+
await schemaRegistry.decode(buffer)
|
200
|
+
|
201
|
+
expect(schemaRegistry.cache.setSchema).not.toHaveBeenCalled()
|
202
|
+
})
|
203
|
+
})
|
204
|
+
})
|
205
|
+
|
206
|
+
describe('#getRegistryIdBySchema', () => {
|
207
|
+
let namespace: string, Schema: RawAvroSchema, subject: string
|
208
|
+
|
209
|
+
beforeEach(() => {
|
210
|
+
namespace = `N${uuid().replace(/-/g, '_')}`
|
211
|
+
subject = `${namespace}.RandomTest`
|
212
|
+
Schema = JSON.parse(`
|
213
|
+
{
|
214
|
+
"type": "record",
|
215
|
+
"name": "RandomTest",
|
216
|
+
"namespace": "${namespace}",
|
217
|
+
"fields": [{ "type": "string", "name": "fullName" }]
|
218
|
+
}
|
219
|
+
`)
|
220
|
+
})
|
221
|
+
|
222
|
+
it('returns the registry id if the schema has already been registered under that subject', async () => {
|
223
|
+
const { id } = await schemaRegistry.register(Schema, { subject })
|
224
|
+
|
225
|
+
await expect(schemaRegistry.getRegistryIdBySchema(subject, Schema)).resolves.toEqual(id)
|
226
|
+
})
|
227
|
+
|
228
|
+
it('throws an error if the subject does not exist', async () => {
|
229
|
+
await expect(schemaRegistry.getRegistryIdBySchema(subject, Schema)).rejects.toHaveProperty(
|
230
|
+
'message',
|
231
|
+
`Confluent_Schema_Registry - Subject '${namespace}.${Schema.name}' not found.`,
|
232
|
+
)
|
233
|
+
})
|
234
|
+
|
235
|
+
it('throws an error if the schema has not been registered under that subject', async () => {
|
236
|
+
const otherSchema = JSON.parse(`
|
237
|
+
{
|
238
|
+
"type": "record",
|
239
|
+
"name": "RandomTest",
|
240
|
+
"namespace": "${namespace}",
|
241
|
+
"fields": [{ "type": "string", "name": "notFullName" }]
|
242
|
+
}
|
243
|
+
`)
|
244
|
+
await schemaRegistry.register(otherSchema, { subject })
|
245
|
+
|
246
|
+
await expect(schemaRegistry.getRegistryIdBySchema(subject, Schema)).rejects.toHaveProperty(
|
247
|
+
'message',
|
248
|
+
'Confluent_Schema_Registry - Schema not found',
|
249
|
+
)
|
250
|
+
})
|
251
|
+
})
|
252
|
+
})
|