confluent-schema-registry 3.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (173) hide show
  1. package/.dockerignore +2 -0
  2. package/.prettierrc.js +8 -0
  3. package/CHANGELOG.md +166 -0
  4. package/Dockerfile +10 -0
  5. package/LICENSE +21 -0
  6. package/README.md +44 -0
  7. package/bin/avdlToAVSC.sh +9 -0
  8. package/dist/@types.d.ts +93 -0
  9. package/dist/@types.js +10 -0
  10. package/dist/@types.js.map +1 -0
  11. package/dist/AvroHelper.d.ts +12 -0
  12. package/dist/AvroHelper.js +67 -0
  13. package/dist/AvroHelper.js.map +1 -0
  14. package/dist/JsonHelper.d.ts +7 -0
  15. package/dist/JsonHelper.js +20 -0
  16. package/dist/JsonHelper.js.map +1 -0
  17. package/dist/JsonSchema.d.ts +31 -0
  18. package/dist/JsonSchema.js +58 -0
  19. package/dist/JsonSchema.js.map +1 -0
  20. package/dist/ProtoHelper.d.ts +7 -0
  21. package/dist/ProtoHelper.js +23 -0
  22. package/dist/ProtoHelper.js.map +1 -0
  23. package/dist/ProtoSchema.d.ts +14 -0
  24. package/dist/ProtoSchema.js +66 -0
  25. package/dist/ProtoSchema.js.map +1 -0
  26. package/dist/SchemaRegistry.d.ts +48 -0
  27. package/dist/SchemaRegistry.js +250 -0
  28. package/dist/SchemaRegistry.js.map +1 -0
  29. package/dist/api/index.d.ts +43 -0
  30. package/dist/api/index.js +90 -0
  31. package/dist/api/index.js.map +1 -0
  32. package/dist/api/middleware/confluentEncoderMiddleware.d.ts +3 -0
  33. package/dist/api/middleware/confluentEncoderMiddleware.js +31 -0
  34. package/dist/api/middleware/confluentEncoderMiddleware.js.map +1 -0
  35. package/dist/api/middleware/errorMiddleware.d.ts +3 -0
  36. package/dist/api/middleware/errorMiddleware.js +20 -0
  37. package/dist/api/middleware/errorMiddleware.js.map +1 -0
  38. package/dist/api/middleware/userAgent.d.ts +3 -0
  39. package/dist/api/middleware/userAgent.js +18 -0
  40. package/dist/api/middleware/userAgent.js.map +1 -0
  41. package/dist/cache.d.ts +20 -0
  42. package/dist/cache.js +24 -0
  43. package/dist/cache.js.map +1 -0
  44. package/dist/constants.d.ts +11 -0
  45. package/dist/constants.js +15 -0
  46. package/dist/constants.js.map +1 -0
  47. package/dist/errors.d.ts +14 -0
  48. package/dist/errors.js +26 -0
  49. package/dist/errors.js.map +1 -0
  50. package/dist/index.d.ts +4 -0
  51. package/dist/index.js +13 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/schemaTypeResolver.d.ts +4 -0
  54. package/dist/schemaTypeResolver.js +80 -0
  55. package/dist/schemaTypeResolver.js.map +1 -0
  56. package/dist/utils/avdlToAVSC.d.ts +2 -0
  57. package/dist/utils/avdlToAVSC.js +85 -0
  58. package/dist/utils/avdlToAVSC.js.map +1 -0
  59. package/dist/utils/index.d.ts +2 -0
  60. package/dist/utils/index.js +9 -0
  61. package/dist/utils/index.js.map +1 -0
  62. package/dist/utils/readAVSC.d.ts +3 -0
  63. package/dist/utils/readAVSC.js +33 -0
  64. package/dist/utils/readAVSC.js.map +1 -0
  65. package/dist/wireDecoder.d.ts +7 -0
  66. package/dist/wireDecoder.js +8 -0
  67. package/dist/wireDecoder.js.map +1 -0
  68. package/dist/wireEncoder.d.ts +3 -0
  69. package/dist/wireEncoder.js +10 -0
  70. package/dist/wireEncoder.js.map +1 -0
  71. package/dockest-error.json +11 -0
  72. package/dockest.ts +30 -0
  73. package/jest.setup.ts +60 -0
  74. package/package.json +56 -0
  75. package/release/CHANGELOG.md +166 -0
  76. package/release/LICENSE +21 -0
  77. package/release/README.md +44 -0
  78. package/release/dist/@types.d.ts +93 -0
  79. package/release/dist/@types.js +10 -0
  80. package/release/dist/@types.js.map +1 -0
  81. package/release/dist/AvroHelper.d.ts +12 -0
  82. package/release/dist/AvroHelper.js +67 -0
  83. package/release/dist/AvroHelper.js.map +1 -0
  84. package/release/dist/JsonHelper.d.ts +7 -0
  85. package/release/dist/JsonHelper.js +20 -0
  86. package/release/dist/JsonHelper.js.map +1 -0
  87. package/release/dist/JsonSchema.d.ts +31 -0
  88. package/release/dist/JsonSchema.js +58 -0
  89. package/release/dist/JsonSchema.js.map +1 -0
  90. package/release/dist/ProtoHelper.d.ts +7 -0
  91. package/release/dist/ProtoHelper.js +23 -0
  92. package/release/dist/ProtoHelper.js.map +1 -0
  93. package/release/dist/ProtoSchema.d.ts +14 -0
  94. package/release/dist/ProtoSchema.js +66 -0
  95. package/release/dist/ProtoSchema.js.map +1 -0
  96. package/release/dist/SchemaRegistry.d.ts +48 -0
  97. package/release/dist/SchemaRegistry.js +250 -0
  98. package/release/dist/SchemaRegistry.js.map +1 -0
  99. package/release/dist/api/index.d.ts +43 -0
  100. package/release/dist/api/index.js +90 -0
  101. package/release/dist/api/index.js.map +1 -0
  102. package/release/dist/api/middleware/confluentEncoderMiddleware.d.ts +3 -0
  103. package/release/dist/api/middleware/confluentEncoderMiddleware.js +31 -0
  104. package/release/dist/api/middleware/confluentEncoderMiddleware.js.map +1 -0
  105. package/release/dist/api/middleware/errorMiddleware.d.ts +3 -0
  106. package/release/dist/api/middleware/errorMiddleware.js +20 -0
  107. package/release/dist/api/middleware/errorMiddleware.js.map +1 -0
  108. package/release/dist/api/middleware/userAgent.d.ts +3 -0
  109. package/release/dist/api/middleware/userAgent.js +18 -0
  110. package/release/dist/api/middleware/userAgent.js.map +1 -0
  111. package/release/dist/cache.d.ts +20 -0
  112. package/release/dist/cache.js +24 -0
  113. package/release/dist/cache.js.map +1 -0
  114. package/release/dist/constants.d.ts +11 -0
  115. package/release/dist/constants.js +15 -0
  116. package/release/dist/constants.js.map +1 -0
  117. package/release/dist/errors.d.ts +14 -0
  118. package/release/dist/errors.js +26 -0
  119. package/release/dist/errors.js.map +1 -0
  120. package/release/dist/index.d.ts +4 -0
  121. package/release/dist/index.js +13 -0
  122. package/release/dist/index.js.map +1 -0
  123. package/release/dist/schemaTypeResolver.d.ts +4 -0
  124. package/release/dist/schemaTypeResolver.js +80 -0
  125. package/release/dist/schemaTypeResolver.js.map +1 -0
  126. package/release/dist/utils/avdlToAVSC.d.ts +2 -0
  127. package/release/dist/utils/avdlToAVSC.js +85 -0
  128. package/release/dist/utils/avdlToAVSC.js.map +1 -0
  129. package/release/dist/utils/index.d.ts +2 -0
  130. package/release/dist/utils/index.js +9 -0
  131. package/release/dist/utils/index.js.map +1 -0
  132. package/release/dist/utils/readAVSC.d.ts +3 -0
  133. package/release/dist/utils/readAVSC.js +33 -0
  134. package/release/dist/utils/readAVSC.js.map +1 -0
  135. package/release/dist/wireDecoder.d.ts +7 -0
  136. package/release/dist/wireDecoder.js +8 -0
  137. package/release/dist/wireDecoder.js.map +1 -0
  138. package/release/dist/wireEncoder.d.ts +3 -0
  139. package/release/dist/wireEncoder.js +10 -0
  140. package/release/dist/wireEncoder.js.map +1 -0
  141. package/release/package.json +56 -0
  142. package/src/@types.ts +105 -0
  143. package/src/AvroHelper.ts +91 -0
  144. package/src/JsonHelper.ts +35 -0
  145. package/src/JsonSchema.ts +80 -0
  146. package/src/ProtoHelper.ts +38 -0
  147. package/src/ProtoSchema.ts +80 -0
  148. package/src/SchemaRegistry.avro.spec.ts +558 -0
  149. package/src/SchemaRegistry.json.spec.ts +364 -0
  150. package/src/SchemaRegistry.newApi.spec.ts +622 -0
  151. package/src/SchemaRegistry.protobuf.spec.ts +372 -0
  152. package/src/SchemaRegistry.spec.ts +252 -0
  153. package/src/SchemaRegistry.ts +387 -0
  154. package/src/api/index.spec.ts +23 -0
  155. package/src/api/index.ts +121 -0
  156. package/src/api/middleware/confluentEncoderMiddleware.ts +36 -0
  157. package/src/api/middleware/errorMiddleware.spec.ts +67 -0
  158. package/src/api/middleware/errorMiddleware.ts +37 -0
  159. package/src/api/middleware/userAgent.spec.ts +53 -0
  160. package/src/api/middleware/userAgent.ts +19 -0
  161. package/src/cache.ts +34 -0
  162. package/src/constants.ts +13 -0
  163. package/src/errors.ts +26 -0
  164. package/src/index.ts +4 -0
  165. package/src/schemaTypeResolver.ts +101 -0
  166. package/src/utils/avdlToAVSC.spec.ts +79 -0
  167. package/src/utils/avdlToAVSC.ts +106 -0
  168. package/src/utils/index.ts +2 -0
  169. package/src/utils/readAVSC.spec.ts +23 -0
  170. package/src/utils/readAVSC.ts +36 -0
  171. package/src/wireDecoder.ts +5 -0
  172. package/src/wireEncoder.ts +10 -0
  173. 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
+ })