confluent-schema-registry 3.3.2

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.
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,622 @@
1
+ import { Type } from 'avsc'
2
+ import { v4 as uuid } from 'uuid'
3
+
4
+ import SchemaRegistry from './SchemaRegistry'
5
+ import { ConfluentSubject, ConfluentSchema, SchemaType } from './@types'
6
+ import API, { SchemaRegistryAPIClient } from './api'
7
+ import { COMPATIBILITY, DEFAULT_API_CLIENT_ID } from './constants'
8
+ import encodedAnotherPersonV2Avro from '../fixtures/avro/encodedAnotherPersonV2'
9
+ import encodedAnotherPersonV2Json from '../fixtures/json/encodedAnotherPersonV2'
10
+ import encodedAnotherPersonV2Proto from '../fixtures/proto/encodedAnotherPersonV2'
11
+ import encodedNestedV2Proto from '../fixtures/proto/encodedNestedV2'
12
+ import wrongMagicByte from '../fixtures/wrongMagicByte'
13
+ import Ajv2020 from 'ajv8/dist/2020'
14
+ import Ajv from 'ajv'
15
+ import { ConfluentSchemaRegistryValidationError } from './errors'
16
+
17
+ const REGISTRY_HOST = 'http://localhost:8982'
18
+ const schemaRegistryAPIClientArgs = { host: REGISTRY_HOST }
19
+ const schemaRegistryArgs = { host: REGISTRY_HOST }
20
+
21
+ const payload = { fullName: 'John Doe' }
22
+
23
+ type KnownSchemaTypes = Exclude<SchemaType, SchemaType.UNKNOWN>
24
+
25
+ describe('SchemaRegistry - new Api', () => {
26
+ let schemaRegistry: SchemaRegistry
27
+
28
+ const schemaStringsByType: Record<KnownSchemaTypes, any> = {
29
+ [SchemaType.AVRO]: {
30
+ random: (namespace: string) => `
31
+ {
32
+ "type": "record",
33
+ "name": "RandomTest",
34
+ "namespace": "${namespace}",
35
+ "fields": [{ "type": "string", "name": "fullName" }]
36
+ }
37
+ `,
38
+ otherRandom: (namespace: string) => `
39
+ {
40
+ "type": "record",
41
+ "name": "RandomTest",
42
+ "namespace": "${namespace}",
43
+ "fields": [{ "type": "string", "name": "notFullName" }]
44
+ }
45
+ `,
46
+ v1: `{
47
+ "type": "record",
48
+ "name": "AnotherPerson",
49
+ "namespace": "com.org.domain.fixtures",
50
+ "fields": [ { "type": "string", "name": "fullName" } ]
51
+ }`,
52
+ v2: `{
53
+ "type": "record",
54
+ "name": "AnotherPerson",
55
+ "namespace": "com.org.domain.fixtures",
56
+ "fields": [
57
+ { "type": "string", "name": "fullName" },
58
+ { "type": "string", "name": "city", "default": "Stockholm" }
59
+ ]
60
+ }`,
61
+ encodedAnotherPersonV2: encodedAnotherPersonV2Avro,
62
+ },
63
+ [SchemaType.JSON]: {
64
+ random: (namespace: string) => `
65
+ {
66
+ "definitions" : {
67
+ "record:${namespace}.RandomTest" : {
68
+ "type" : "object",
69
+ "required" : [ "fullName" ],
70
+ "additionalProperties" : false,
71
+ "properties" : {
72
+ "fullName" : {
73
+ "type" : "string"
74
+ }
75
+ }
76
+ }
77
+ },
78
+ "$ref" : "#/definitions/record:${namespace}.RandomTest"
79
+ }
80
+ `,
81
+ otherRandom: (namespace: string) => `
82
+ {
83
+ "definitions" : {
84
+ "record:${namespace}.RandomTest" : {
85
+ "type" : "object",
86
+ "required" : [ "notFullName" ],
87
+ "additionalProperties" : false,
88
+ "properties" : {
89
+ "notFullName" : {
90
+ "type" : "string"
91
+ }
92
+ }
93
+ }
94
+ },
95
+ "$ref" : "#/definitions/record:${namespace}.RandomTest"
96
+ }
97
+ `,
98
+ v1: `
99
+ {
100
+ "title": "AnotherPerson",
101
+ "type": "object",
102
+ "required": [
103
+ "fullName"
104
+ ],
105
+ "properties": {
106
+ "fullName": {
107
+ "type": "string",
108
+ "pattern": "^.*$"
109
+ }
110
+ }
111
+ }
112
+ `,
113
+ v2: `
114
+ {
115
+ "title": "AnotherPerson",
116
+ "type": "object",
117
+ "required": [
118
+ "fullName"
119
+ ],
120
+ "properties": {
121
+ "fullName": {
122
+ "type": "string",
123
+ "pattern": "^.*$"
124
+ },
125
+ "city": {
126
+ "type": "string",
127
+ "pattern": "^.*$"
128
+ }
129
+ }
130
+ }
131
+ `,
132
+ encodedAnotherPersonV2: encodedAnotherPersonV2Json,
133
+ },
134
+ [SchemaType.PROTOBUF]: {
135
+ random: (namespace: string) => `
136
+ package ${namespace};
137
+ message RandomTest {
138
+ required string fullName = 1;
139
+ }
140
+ `,
141
+ otherRandom: (namespace: string) => `
142
+ package ${namespace};
143
+ message RandomTest {
144
+ required string notFullName = 1;
145
+ }
146
+ `,
147
+ v1: `
148
+ syntax = "proto2";
149
+ package com.org.domain.fixtures;
150
+ message AnotherPerson {
151
+ required string fullName = 1;
152
+ }
153
+ `,
154
+ v2: `
155
+ syntax = "proto2";
156
+ package com.org.domain.fixtures;
157
+ message AnotherPerson {
158
+ required string fullName = 1;
159
+ optional string city = 2 [default = "Stockholm"];
160
+ }
161
+ `,
162
+ encodedAnotherPersonV2: encodedAnotherPersonV2Proto,
163
+ },
164
+ }
165
+ const types = Object.keys(schemaStringsByType).map(str => SchemaType[str]) as KnownSchemaTypes[]
166
+
167
+ types.forEach(type =>
168
+ describe(`${type}`, () => {
169
+ const subject: ConfluentSubject = {
170
+ name: [type, 'com.org.domain.fixtures', 'AnotherPerson'].join('.'),
171
+ }
172
+ const schema: ConfluentSchema = {
173
+ type,
174
+ schema: schemaStringsByType[type].v1,
175
+ }
176
+
177
+ beforeEach(async () => {
178
+ schemaRegistry = new SchemaRegistry(schemaRegistryArgs)
179
+ await schemaRegistry.register(schema, { subject: subject.name })
180
+ })
181
+
182
+ describe('#register', () => {
183
+ let namespace,
184
+ Schema,
185
+ subject: string,
186
+ api: SchemaRegistryAPIClient,
187
+ confluentSubject: ConfluentSubject,
188
+ confluentSchema: ConfluentSchema
189
+
190
+ beforeEach(() => {
191
+ api = API(schemaRegistryAPIClientArgs)
192
+ namespace = `N${uuid().replace(/-/g, '_')}`
193
+ subject = `${namespace}.RandomTest`
194
+ Schema = schemaStringsByType[type].random(namespace)
195
+ confluentSubject = { name: subject }
196
+ confluentSchema = { type, schema: Schema }
197
+ })
198
+
199
+ it('uploads the new schema', async () => {
200
+ await expect(api.Subject.latestVersion({ subject })).rejects.toHaveProperty(
201
+ 'message',
202
+ `${DEFAULT_API_CLIENT_ID} - Subject '${subject}' not found.`,
203
+ )
204
+
205
+ await expect(
206
+ schemaRegistry.register(confluentSchema, { subject: confluentSubject.name }),
207
+ ).resolves.toEqual({
208
+ id: expect.any(Number),
209
+ })
210
+ })
211
+
212
+ it('automatically cache the id and schema', async () => {
213
+ const { id } = await schemaRegistry.register(confluentSchema, {
214
+ subject: confluentSubject.name,
215
+ })
216
+
217
+ expect(schemaRegistry.cache.getSchema(id)).toBeTruthy()
218
+ })
219
+
220
+ it('fetch and validate the latest schema id after registering a new schema', async () => {
221
+ const { id } = await schemaRegistry.register(confluentSchema, {
222
+ subject: confluentSubject.name,
223
+ })
224
+ const latestSchemaId = await schemaRegistry.getLatestSchemaId(subject)
225
+
226
+ expect(id).toBe(latestSchemaId)
227
+ })
228
+
229
+ it('set the default compatibility to BACKWARD', async () => {
230
+ await schemaRegistry.register(confluentSchema, { subject: confluentSubject.name })
231
+ const response = await api.Subject.config({ subject })
232
+ expect(response.data()).toEqual({ compatibilityLevel: COMPATIBILITY.BACKWARD })
233
+ })
234
+
235
+ it('sets the compatibility according to param', async () => {
236
+ await schemaRegistry.register(confluentSchema, {
237
+ subject: confluentSubject.name,
238
+ compatibility: COMPATIBILITY.NONE,
239
+ })
240
+ const response = await api.Subject.config({ subject })
241
+ expect(response.data()).toEqual({ compatibilityLevel: COMPATIBILITY.NONE })
242
+ })
243
+
244
+ it('throws an error when the configured compatibility is different than defined in the client', async () => {
245
+ await schemaRegistry.register(confluentSchema, { subject: confluentSubject.name })
246
+ await api.Subject.updateConfig({ subject, body: { compatibility: COMPATIBILITY.FULL } })
247
+ await expect(
248
+ schemaRegistry.register(confluentSchema, { subject: confluentSubject.name }),
249
+ ).rejects.toHaveProperty(
250
+ 'message',
251
+ 'Compatibility does not match the configuration (BACKWARD != FULL)',
252
+ )
253
+ })
254
+
255
+ it('throws an error when the given schema string is invalid', async () => {
256
+ const invalidSchema = `asdf`
257
+ const invalidConfluentSchema: ConfluentSchema = {
258
+ type,
259
+ schema: invalidSchema,
260
+ }
261
+ await expect(
262
+ schemaRegistry.register(invalidConfluentSchema, { subject: confluentSubject.name }),
263
+ ).rejects.toHaveProperty('name', 'ConfluentSchemaRegistryArgumentError')
264
+ })
265
+ })
266
+
267
+ describe('#encode', () => {
268
+ beforeEach(async () => {
269
+ await schemaRegistry.register(schema, { subject: subject.name })
270
+ })
271
+
272
+ it('throws an error if registryId is empty', async () => {
273
+ await expect(schemaRegistry.encode(undefined, payload)).rejects.toHaveProperty(
274
+ 'message',
275
+ 'Invalid registryId: undefined',
276
+ )
277
+ })
278
+
279
+ it('encodes using a defined registryId', async () => {
280
+ const confluentSchemaV1: ConfluentSchema = {
281
+ type,
282
+ schema: schemaStringsByType[type].v1,
283
+ }
284
+ const confluentSchemaV2: ConfluentSchema = {
285
+ type,
286
+ schema: schemaStringsByType[type].v2,
287
+ }
288
+
289
+ const schema1 = await schemaRegistry.register(confluentSchemaV1, {
290
+ subject: `${type}_test1`,
291
+ })
292
+ const schema2 = await schemaRegistry.register(confluentSchemaV2, {
293
+ subject: `${type}_test2`,
294
+ })
295
+ expect(schema2.id).not.toEqual(schema1.id)
296
+
297
+ const data = await schemaRegistry.encode(schema2.id, payload)
298
+
299
+ expect(data).toMatchConfluentEncodedPayload({
300
+ registryId: schema2.id,
301
+ payload: Buffer.from(schemaStringsByType[type].encodedAnotherPersonV2),
302
+ })
303
+ })
304
+
305
+ it('throws an error if the payload does not match the schema', async () => {
306
+ const confluentSchema: ConfluentSchema = {
307
+ type,
308
+ schema: schemaStringsByType[type].v1,
309
+ }
310
+ const schema = await schemaRegistry.register(confluentSchema, {
311
+ subject: `${type}_test`,
312
+ })
313
+
314
+ const badPayload = { asdf: 123 }
315
+
316
+ await expect(schemaRegistry.encode(schema.id, badPayload)).rejects.toHaveProperty(
317
+ 'name',
318
+ 'ConfluentSchemaRegistryValidationError',
319
+ )
320
+ })
321
+ })
322
+
323
+ describe('#decode', () => {
324
+ let registryId: number
325
+
326
+ beforeEach(async () => {
327
+ registryId = (await schemaRegistry.register(schema, { subject: subject.name })).id
328
+ })
329
+
330
+ it('decodes data', async () => {
331
+ const buffer = Buffer.from(await schemaRegistry.encode(registryId, payload))
332
+ const data = await schemaRegistry.decode(buffer)
333
+
334
+ expect(data).toEqual(payload)
335
+ })
336
+
337
+ it('throws an error if the magic byte is not supported', async () => {
338
+ const buffer = Buffer.from(wrongMagicByte)
339
+ await expect(schemaRegistry.decode(buffer)).rejects.toHaveProperty(
340
+ 'message',
341
+ 'Message encoded with magic byte {"type":"Buffer","data":[48]}, expected {"type":"Buffer","data":[0]}',
342
+ )
343
+ })
344
+
345
+ it.skip('throws an error if the payload does not match the schema', async () => {
346
+ const badPayload = { asdf: 123 }
347
+ // TODO: find a way to encode the bad payload with the registryId
348
+ const buffer = Buffer.from(await schemaRegistry.encode(registryId, badPayload))
349
+
350
+ await expect(schemaRegistry.decode(buffer)).rejects.toHaveProperty(
351
+ 'name',
352
+ 'ConfluentSchemaRegistryValidationError',
353
+ )
354
+ })
355
+
356
+ it('caches the schema', async () => {
357
+ const buffer = Buffer.from(await schemaRegistry.encode(registryId, payload))
358
+
359
+ schemaRegistry.cache.clear()
360
+ await schemaRegistry.decode(buffer)
361
+
362
+ expect(schemaRegistry.cache.getSchema(registryId)).toBeTruthy()
363
+ })
364
+
365
+ it('creates a single origin request for a schema cache-miss', async () => {
366
+ const buffer = Buffer.from(await schemaRegistry.encode(registryId, payload))
367
+
368
+ schemaRegistry.cache.clear()
369
+
370
+ const spy = jest.spyOn((schemaRegistry as any).api.Schema, 'find')
371
+
372
+ await Promise.all([
373
+ schemaRegistry.decode(buffer),
374
+ schemaRegistry.decode(buffer),
375
+ schemaRegistry.decode(buffer),
376
+ ])
377
+
378
+ expect(spy).toHaveBeenCalledTimes(1)
379
+ })
380
+
381
+ describe('when the cache is populated', () => {
382
+ it('uses the cache data', async () => {
383
+ const buffer = Buffer.from(await schemaRegistry.encode(registryId, payload))
384
+ expect(schemaRegistry.cache.getSchema(registryId)).toBeTruthy()
385
+
386
+ jest.spyOn(schemaRegistry.cache, 'setSchema')
387
+ await schemaRegistry.decode(buffer)
388
+
389
+ expect(schemaRegistry.cache.setSchema).not.toHaveBeenCalled()
390
+ })
391
+ })
392
+ })
393
+
394
+ describe('#getRegistryIdBySchema', () => {
395
+ let namespace: string, confluentSubject: ConfluentSubject, confluentSchema: ConfluentSchema
396
+
397
+ beforeEach(() => {
398
+ namespace = `N${uuid().replace(/-/g, '_')}`
399
+ const subject = `${namespace}.RandomTest`
400
+ const schema = schemaStringsByType[type].random(namespace)
401
+ confluentSubject = { name: subject }
402
+ confluentSchema = { type, schema: schema }
403
+ })
404
+
405
+ it('returns the registry id if the schema has already been registered under that subject', async () => {
406
+ const { id } = await schemaRegistry.register(confluentSchema, {
407
+ subject: confluentSubject.name,
408
+ })
409
+
410
+ await expect(
411
+ schemaRegistry.getRegistryIdBySchema(confluentSubject.name, confluentSchema),
412
+ ).resolves.toEqual(id)
413
+ })
414
+
415
+ it('throws an error if the subject does not exist', async () => {
416
+ await expect(
417
+ schemaRegistry.getRegistryIdBySchema(confluentSubject.name, confluentSchema),
418
+ ).rejects.toHaveProperty(
419
+ 'message',
420
+ `Confluent_Schema_Registry - Subject '${confluentSubject.name}' not found.`,
421
+ )
422
+ })
423
+
424
+ it('throws an error if the schema has not been registered under that subject', async () => {
425
+ const otherSchema = schemaStringsByType[type].otherRandom(namespace)
426
+ const confluentOtherSchema: ConfluentSchema = {
427
+ type,
428
+ schema: otherSchema,
429
+ }
430
+
431
+ await schemaRegistry.register(confluentOtherSchema, { subject: confluentSubject.name })
432
+
433
+ await expect(
434
+ schemaRegistry.getRegistryIdBySchema(confluentSubject.name, confluentSchema),
435
+ ).rejects.toHaveProperty('message', 'Confluent_Schema_Registry - Schema not found')
436
+ })
437
+ })
438
+ }),
439
+ )
440
+
441
+ describe('PROTOBUF tests', () => {
442
+ const v3 = `
443
+ syntax = "proto2";
444
+ package com.org.domain.fixtures;
445
+ message SomeOtherMessage {
446
+ required string bla = 1;
447
+ required string foo = 2;
448
+ }
449
+ message AnotherPerson {
450
+ required string fullName = 1;
451
+ optional string city = 2 [default = "Stockholm"];
452
+ }
453
+ `,
454
+ v3Opts = { [SchemaType.PROTOBUF]: { messageName: 'AnotherPerson' } },
455
+ type = SchemaType.PROTOBUF
456
+
457
+ beforeAll(() => {
458
+ schemaRegistry = new SchemaRegistry(schemaRegistryArgs, v3Opts)
459
+ })
460
+
461
+ it('encodes using schemaOptions', async () => {
462
+ const confluentSchemaV3: ConfluentSchema = {
463
+ type,
464
+ schema: v3,
465
+ }
466
+
467
+ const schema3 = await schemaRegistry.register(confluentSchemaV3, {
468
+ subject: `${type}_test3`,
469
+ })
470
+
471
+ const data = await schemaRegistry.encode(schema3.id, payload)
472
+
473
+ expect(data).toMatchConfluentEncodedPayload({
474
+ registryId: schema3.id,
475
+ payload: Buffer.from(schemaStringsByType[type].encodedAnotherPersonV2),
476
+ })
477
+ })
478
+
479
+ it('decodes using schemaOptions', async () => {
480
+ const confluentSchemaV3: ConfluentSchema = {
481
+ type,
482
+ schema: v3,
483
+ }
484
+
485
+ const schema3 = await schemaRegistry.register(confluentSchemaV3, {
486
+ subject: `${type}_test3`,
487
+ })
488
+
489
+ const buffer = Buffer.from(await schemaRegistry.encode(schema3.id, payload))
490
+ const data = await schemaRegistry.decode(buffer)
491
+
492
+ expect(data).toEqual(payload)
493
+ })
494
+
495
+ describe('nested message types tests', () => {
496
+ const v4 = `
497
+ syntax = "proto2";
498
+ package com.org.domain.fixtures;
499
+ message OuterMessageType {
500
+ required string data = 1;
501
+ required InnerMessageType1 innerMessageType1 = 2;
502
+ required InnerMessageType2 innerMessageType2 = 3;
503
+
504
+ message InnerMessageType1 {
505
+ required string someField = 1;
506
+ }
507
+ message InnerMessageType2 {
508
+ required string someOtherField = 1;
509
+ }
510
+ }
511
+ `,
512
+ type = SchemaType.PROTOBUF,
513
+ nestedPayload = {
514
+ data: 'data-value',
515
+ innerMessageType1: {
516
+ someField: 'someField-value',
517
+ },
518
+ innerMessageType2: {
519
+ someOtherField: 'someOtherField-value',
520
+ },
521
+ }
522
+
523
+ beforeAll(() => {
524
+ schemaRegistry = new SchemaRegistry(schemaRegistryArgs)
525
+ })
526
+
527
+ it('encodes', async () => {
528
+ const confluentSchemaV4: ConfluentSchema = {
529
+ type,
530
+ schema: v4,
531
+ }
532
+
533
+ const schema4 = await schemaRegistry.register(confluentSchemaV4, {
534
+ subject: `${type}_test4`,
535
+ })
536
+
537
+ const data = await schemaRegistry.encode(schema4.id, nestedPayload)
538
+
539
+ expect(data).toMatchConfluentEncodedPayload({
540
+ registryId: schema4.id,
541
+ payload: Buffer.from(encodedNestedV2Proto),
542
+ })
543
+ })
544
+
545
+ it('decodes', async () => {
546
+ const confluentSchemaV4: ConfluentSchema = {
547
+ type,
548
+ schema: v4,
549
+ }
550
+
551
+ const schema4 = await schemaRegistry.register(confluentSchemaV4, {
552
+ subject: `${type}_test4`,
553
+ })
554
+
555
+ const buffer = Buffer.from(await schemaRegistry.encode(schema4.id, nestedPayload))
556
+ const data = await schemaRegistry.decode(buffer)
557
+
558
+ expect(data).toEqual(nestedPayload)
559
+ })
560
+ })
561
+ })
562
+
563
+ describe('JSON Schema tests', () => {
564
+ describe('passing an Ajv instance in the constructor', () => {
565
+ test.each([
566
+ ['Ajv 7', new Ajv()],
567
+ ['Ajv2020', new Ajv2020()],
568
+ ])(
569
+ 'Errors are thrown with their path in %s when the validation fails',
570
+ async (_, ajvInstance) => {
571
+ expect.assertions(3)
572
+ const registry = new SchemaRegistry(schemaRegistryArgs, {
573
+ [SchemaType.JSON]: { ajvInstance },
574
+ })
575
+ const subject: ConfluentSubject = {
576
+ name: [SchemaType.JSON, 'com.org.domain.fixtures', 'AnotherPerson'].join('.'),
577
+ }
578
+ const schema: ConfluentSchema = {
579
+ type: SchemaType.JSON,
580
+ schema: schemaStringsByType[SchemaType.JSON].v1,
581
+ }
582
+
583
+ const { id: schemaId } = await registry.register(schema, { subject: subject.name })
584
+
585
+ try {
586
+ await schemaRegistry.encode(schemaId, { fullName: true })
587
+ } catch (error) {
588
+ expect(error).toBeInstanceOf(ConfluentSchemaRegistryValidationError)
589
+ expect(error.message).toEqual('invalid payload')
590
+ expect(error.paths).toEqual([['/fullName']])
591
+ }
592
+ },
593
+ )
594
+ })
595
+ })
596
+
597
+ describe('Avro tests', () => {
598
+ it('uses reader schema if specified (avro-only)', async () => {
599
+ const subject: ConfluentSubject = {
600
+ name: [SchemaType.AVRO, 'com.org.domain.fixtures', 'AnotherPerson'].join('.'),
601
+ }
602
+ const schema: ConfluentSchema = {
603
+ type: SchemaType.AVRO,
604
+ schema: schemaStringsByType[SchemaType.AVRO].v1,
605
+ }
606
+ const registryId = (await schemaRegistry.register(schema, { subject: subject.name })).id
607
+ const writerBuffer = Buffer.from(await schemaRegistry.encode(registryId, payload))
608
+ const readerSchema = JSON.parse(schemaStringsByType[SchemaType.AVRO].v2)
609
+
610
+ await expect(
611
+ schemaRegistry.decode(writerBuffer, { [SchemaType.AVRO]: { readerSchema } }),
612
+ ).resolves.toHaveProperty('city', 'Stockholm')
613
+
614
+ const registeredReaderSchema = await schemaRegistry.getSchema(registryId)
615
+ await expect(
616
+ schemaRegistry.decode(writerBuffer, {
617
+ [SchemaType.AVRO]: { readerSchema: registeredReaderSchema },
618
+ }),
619
+ )
620
+ })
621
+ })
622
+ })