source: node_modules/pg-protocol/src/inbound-parser.test.ts@ 2d1ec46

main
Last change on this file since 2d1ec46 was 62b2964, checked in by Klimentina Efremova <klimentina08642@…>, 2 weeks ago

Project Handcraft Marketplace

  • Property mode set to 100644
File size: 15.3 KB
Line 
1import buffers from './testing/test-buffers'
2import BufferList from './testing/buffer-list'
3import { parse } from '.'
4import assert from 'assert'
5import { PassThrough } from 'stream'
6import { BackendMessage } from './messages'
7import { Parser } from './parser'
8
9const authOkBuffer = buffers.authenticationOk()
10const paramStatusBuffer = buffers.parameterStatus('client_encoding', 'UTF8')
11const readyForQueryBuffer = buffers.readyForQuery()
12const backendKeyDataBuffer = buffers.backendKeyData(1, 2)
13const commandCompleteBuffer = buffers.commandComplete('SELECT 3')
14const parseCompleteBuffer = buffers.parseComplete()
15const bindCompleteBuffer = buffers.bindComplete()
16const portalSuspendedBuffer = buffers.portalSuspended()
17
18const row1 = {
19 name: 'id',
20 tableID: 1,
21 attributeNumber: 2,
22 dataTypeID: 3,
23 dataTypeSize: 4,
24 typeModifier: 5,
25 formatCode: 0,
26}
27const oneRowDescBuff = buffers.rowDescription([row1])
28row1.name = 'bang'
29
30const twoRowBuf = buffers.rowDescription([
31 row1,
32 {
33 name: 'whoah',
34 tableID: 10,
35 attributeNumber: 11,
36 dataTypeID: 12,
37 dataTypeSize: 13,
38 typeModifier: 14,
39 formatCode: 0,
40 },
41])
42
43const rowWithBigOids = {
44 name: 'bigoid',
45 tableID: 3000000001,
46 attributeNumber: 2,
47 dataTypeID: 3000000003,
48 dataTypeSize: 4,
49 typeModifier: 5,
50 formatCode: 0,
51}
52const bigOidDescBuff = buffers.rowDescription([rowWithBigOids])
53
54const emptyRowFieldBuf = buffers.dataRow([])
55
56const oneFieldBuf = buffers.dataRow(['test'])
57
58const expectedAuthenticationOkayMessage = {
59 name: 'authenticationOk',
60 length: 8,
61}
62
63const expectedParameterStatusMessage = {
64 name: 'parameterStatus',
65 parameterName: 'client_encoding',
66 parameterValue: 'UTF8',
67 length: 25,
68}
69
70const expectedBackendKeyDataMessage = {
71 name: 'backendKeyData',
72 processID: 1,
73 secretKey: 2,
74}
75
76const expectedReadyForQueryMessage = {
77 name: 'readyForQuery',
78 length: 5,
79 status: 'I',
80}
81
82const expectedCommandCompleteMessage = {
83 name: 'commandComplete',
84 length: 13,
85 text: 'SELECT 3',
86}
87const emptyRowDescriptionBuffer = new BufferList()
88 .addInt16(0) // number of fields
89 .join(true, 'T')
90
91const expectedEmptyRowDescriptionMessage = {
92 name: 'rowDescription',
93 length: 6,
94 fieldCount: 0,
95 fields: [],
96}
97const expectedOneRowMessage = {
98 name: 'rowDescription',
99 length: 27,
100 fieldCount: 1,
101 fields: [
102 {
103 name: 'id',
104 tableID: 1,
105 columnID: 2,
106 dataTypeID: 3,
107 dataTypeSize: 4,
108 dataTypeModifier: 5,
109 format: 'text',
110 },
111 ],
112}
113
114const expectedTwoRowMessage = {
115 name: 'rowDescription',
116 length: 53,
117 fieldCount: 2,
118 fields: [
119 {
120 name: 'bang',
121 tableID: 1,
122 columnID: 2,
123 dataTypeID: 3,
124 dataTypeSize: 4,
125 dataTypeModifier: 5,
126 format: 'text',
127 },
128 {
129 name: 'whoah',
130 tableID: 10,
131 columnID: 11,
132 dataTypeID: 12,
133 dataTypeSize: 13,
134 dataTypeModifier: 14,
135 format: 'text',
136 },
137 ],
138}
139const expectedBigOidMessage = {
140 name: 'rowDescription',
141 length: 31,
142 fieldCount: 1,
143 fields: [
144 {
145 name: 'bigoid',
146 tableID: 3000000001,
147 columnID: 2,
148 dataTypeID: 3000000003,
149 dataTypeSize: 4,
150 dataTypeModifier: 5,
151 format: 'text',
152 },
153 ],
154}
155
156const emptyParameterDescriptionBuffer = new BufferList()
157 .addInt16(0) // number of parameters
158 .join(true, 't')
159
160const oneParameterDescBuf = buffers.parameterDescription([1111])
161
162const twoParameterDescBuf = buffers.parameterDescription([2222, 3333])
163
164const bigOidParameterDescBuf = buffers.parameterDescription([3000000003])
165
166const expectedEmptyParameterDescriptionMessage = {
167 name: 'parameterDescription',
168 length: 6,
169 parameterCount: 0,
170 dataTypeIDs: [],
171}
172
173const expectedOneParameterMessage = {
174 name: 'parameterDescription',
175 length: 10,
176 parameterCount: 1,
177 dataTypeIDs: [1111],
178}
179
180const expectedTwoParameterMessage = {
181 name: 'parameterDescription',
182 length: 14,
183 parameterCount: 2,
184 dataTypeIDs: [2222, 3333],
185}
186
187const expectedBigOidParameterMessage = {
188 name: 'parameterDescription',
189 length: 10,
190 parameterCount: 1,
191 dataTypeIDs: [3000000003],
192}
193
194const testForMessage = function (buffer: Buffer, expectedMessage: any) {
195 it('receives and parses ' + expectedMessage.name, async () => {
196 const messages = await parseBuffers([buffer])
197 const [lastMessage] = messages
198
199 for (const key in expectedMessage) {
200 assert.deepEqual((lastMessage as any)[key], expectedMessage[key])
201 }
202 })
203}
204
205const plainPasswordBuffer = buffers.authenticationCleartextPassword()
206const md5PasswordBuffer = buffers.authenticationMD5Password()
207const SASLBuffer = buffers.authenticationSASL()
208const SASLContinueBuffer = buffers.authenticationSASLContinue()
209const SASLFinalBuffer = buffers.authenticationSASLFinal()
210
211const expectedPlainPasswordMessage = {
212 name: 'authenticationCleartextPassword',
213}
214
215const expectedMD5PasswordMessage = {
216 name: 'authenticationMD5Password',
217 salt: Buffer.from([1, 2, 3, 4]),
218}
219
220const expectedSASLMessage = {
221 name: 'authenticationSASL',
222 mechanisms: ['SCRAM-SHA-256'],
223}
224
225const expectedSASLContinueMessage = {
226 name: 'authenticationSASLContinue',
227 data: 'data',
228}
229
230const expectedSASLFinalMessage = {
231 name: 'authenticationSASLFinal',
232 data: 'data',
233}
234
235const notificationResponseBuffer = buffers.notification(4, 'hi', 'boom')
236const expectedNotificationResponseMessage = {
237 name: 'notification',
238 processId: 4,
239 channel: 'hi',
240 payload: 'boom',
241}
242
243const parseBuffers = async (buffers: Buffer[]): Promise<BackendMessage[]> => {
244 const stream = new PassThrough()
245 for (const buffer of buffers) {
246 stream.write(buffer)
247 }
248 stream.end()
249 const msgs: BackendMessage[] = []
250 await parse(stream, (msg) => msgs.push(msg))
251 return msgs
252}
253
254describe('PgPacketStream', function () {
255 testForMessage(authOkBuffer, expectedAuthenticationOkayMessage)
256 testForMessage(plainPasswordBuffer, expectedPlainPasswordMessage)
257 testForMessage(md5PasswordBuffer, expectedMD5PasswordMessage)
258 testForMessage(SASLBuffer, expectedSASLMessage)
259 testForMessage(SASLContinueBuffer, expectedSASLContinueMessage)
260
261 // this exercises a found bug in the parser:
262 // https://github.com/brianc/node-postgres/pull/2210#issuecomment-627626084
263 // and adds a test which is deterministic, rather than relying on network packet chunking
264 const extendedSASLContinueBuffer = Buffer.concat([SASLContinueBuffer, Buffer.from([1, 2, 3, 4])])
265 testForMessage(extendedSASLContinueBuffer, expectedSASLContinueMessage)
266
267 testForMessage(SASLFinalBuffer, expectedSASLFinalMessage)
268
269 // this exercises a found bug in the parser:
270 // https://github.com/brianc/node-postgres/pull/2210#issuecomment-627626084
271 // and adds a test which is deterministic, rather than relying on network packet chunking
272 const extendedSASLFinalBuffer = Buffer.concat([SASLFinalBuffer, Buffer.from([1, 2, 4, 5])])
273 testForMessage(extendedSASLFinalBuffer, expectedSASLFinalMessage)
274
275 testForMessage(paramStatusBuffer, expectedParameterStatusMessage)
276 testForMessage(backendKeyDataBuffer, expectedBackendKeyDataMessage)
277 testForMessage(readyForQueryBuffer, expectedReadyForQueryMessage)
278 testForMessage(commandCompleteBuffer, expectedCommandCompleteMessage)
279 testForMessage(notificationResponseBuffer, expectedNotificationResponseMessage)
280 testForMessage(buffers.emptyQuery(), {
281 name: 'emptyQuery',
282 length: 4,
283 })
284
285 testForMessage(Buffer.from([0x6e, 0, 0, 0, 4]), {
286 name: 'noData',
287 })
288
289 describe('rowDescription messages', function () {
290 testForMessage(emptyRowDescriptionBuffer, expectedEmptyRowDescriptionMessage)
291 testForMessage(oneRowDescBuff, expectedOneRowMessage)
292 testForMessage(twoRowBuf, expectedTwoRowMessage)
293 testForMessage(bigOidDescBuff, expectedBigOidMessage)
294 })
295
296 describe('parameterDescription messages', function () {
297 testForMessage(emptyParameterDescriptionBuffer, expectedEmptyParameterDescriptionMessage)
298 testForMessage(oneParameterDescBuf, expectedOneParameterMessage)
299 testForMessage(twoParameterDescBuf, expectedTwoParameterMessage)
300 testForMessage(bigOidParameterDescBuf, expectedBigOidParameterMessage)
301 })
302
303 describe('parsing rows', function () {
304 describe('parsing empty row', function () {
305 testForMessage(emptyRowFieldBuf, {
306 name: 'dataRow',
307 fieldCount: 0,
308 })
309 })
310
311 describe('parsing data row with fields', function () {
312 testForMessage(oneFieldBuf, {
313 name: 'dataRow',
314 fieldCount: 1,
315 fields: ['test'],
316 })
317 })
318 })
319
320 describe('notice message', function () {
321 // this uses the same logic as error message
322 const buff = buffers.notice([{ type: 'C', value: 'code' }])
323 testForMessage(buff, {
324 name: 'notice',
325 code: 'code',
326 })
327 })
328
329 testForMessage(buffers.error([]), {
330 name: 'error',
331 })
332
333 describe('with all the fields', function () {
334 const buffer = buffers.error([
335 {
336 type: 'S',
337 value: 'ERROR',
338 },
339 {
340 type: 'C',
341 value: 'code',
342 },
343 {
344 type: 'M',
345 value: 'message',
346 },
347 {
348 type: 'D',
349 value: 'details',
350 },
351 {
352 type: 'H',
353 value: 'hint',
354 },
355 {
356 type: 'P',
357 value: '100',
358 },
359 {
360 type: 'p',
361 value: '101',
362 },
363 {
364 type: 'q',
365 value: 'query',
366 },
367 {
368 type: 'W',
369 value: 'where',
370 },
371 {
372 type: 'F',
373 value: 'file',
374 },
375 {
376 type: 'L',
377 value: 'line',
378 },
379 {
380 type: 'R',
381 value: 'routine',
382 },
383 {
384 type: 'Z', // ignored
385 value: 'alsdkf',
386 },
387 ])
388
389 testForMessage(buffer, {
390 name: 'error',
391 severity: 'ERROR',
392 code: 'code',
393 message: 'message',
394 detail: 'details',
395 hint: 'hint',
396 position: '100',
397 internalPosition: '101',
398 internalQuery: 'query',
399 where: 'where',
400 file: 'file',
401 line: 'line',
402 routine: 'routine',
403 })
404 })
405
406 testForMessage(parseCompleteBuffer, {
407 name: 'parseComplete',
408 })
409
410 testForMessage(bindCompleteBuffer, {
411 name: 'bindComplete',
412 })
413
414 testForMessage(bindCompleteBuffer, {
415 name: 'bindComplete',
416 })
417
418 testForMessage(buffers.closeComplete(), {
419 name: 'closeComplete',
420 })
421
422 describe('parses portal suspended message', function () {
423 testForMessage(portalSuspendedBuffer, {
424 name: 'portalSuspended',
425 })
426 })
427
428 describe('parses replication start message', function () {
429 testForMessage(Buffer.from([0x57, 0x00, 0x00, 0x00, 0x04]), {
430 name: 'replicationStart',
431 length: 4,
432 })
433 })
434
435 describe('copy', () => {
436 testForMessage(buffers.copyIn(0), {
437 name: 'copyInResponse',
438 length: 7,
439 binary: false,
440 columnTypes: [],
441 })
442
443 testForMessage(buffers.copyIn(2), {
444 name: 'copyInResponse',
445 length: 11,
446 binary: false,
447 columnTypes: [0, 1],
448 })
449
450 testForMessage(buffers.copyOut(0), {
451 name: 'copyOutResponse',
452 length: 7,
453 binary: false,
454 columnTypes: [],
455 })
456
457 testForMessage(buffers.copyOut(3), {
458 name: 'copyOutResponse',
459 length: 13,
460 binary: false,
461 columnTypes: [0, 1, 2],
462 })
463
464 testForMessage(buffers.copyDone(), {
465 name: 'copyDone',
466 length: 4,
467 })
468
469 testForMessage(buffers.copyData(Buffer.from([5, 6, 7])), {
470 name: 'copyData',
471 length: 7,
472 chunk: Buffer.from([5, 6, 7]),
473 })
474 })
475
476 // since the data message on a stream can randomly divide the incomming
477 // tcp packets anywhere, we need to make sure we can parse every single
478 // split on a tcp message
479 describe('split buffer, single message parsing', function () {
480 const fullBuffer = buffers.dataRow([null, 'bang', 'zug zug', null, '!'])
481
482 it('parses when full buffer comes in', async function () {
483 const messages = await parseBuffers([fullBuffer])
484 const message = messages[0] as any
485 assert.equal(message.fields.length, 5)
486 assert.equal(message.fields[0], null)
487 assert.equal(message.fields[1], 'bang')
488 assert.equal(message.fields[2], 'zug zug')
489 assert.equal(message.fields[3], null)
490 assert.equal(message.fields[4], '!')
491 })
492
493 const testMessageReceivedAfterSplitAt = async function (split: number) {
494 const firstBuffer = Buffer.alloc(fullBuffer.length - split)
495 const secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length)
496 fullBuffer.copy(firstBuffer, 0, 0)
497 fullBuffer.copy(secondBuffer, 0, firstBuffer.length)
498 const messages = await parseBuffers([firstBuffer, secondBuffer])
499 const message = messages[0] as any
500 assert.equal(message.fields.length, 5)
501 assert.equal(message.fields[0], null)
502 assert.equal(message.fields[1], 'bang')
503 assert.equal(message.fields[2], 'zug zug')
504 assert.equal(message.fields[3], null)
505 assert.equal(message.fields[4], '!')
506 }
507
508 it('parses when split in the middle', function () {
509 return testMessageReceivedAfterSplitAt(6)
510 })
511
512 it('parses when split at end', function () {
513 return testMessageReceivedAfterSplitAt(2)
514 })
515
516 it('parses when split at beginning', function () {
517 return Promise.all([
518 testMessageReceivedAfterSplitAt(fullBuffer.length - 2),
519 testMessageReceivedAfterSplitAt(fullBuffer.length - 1),
520 testMessageReceivedAfterSplitAt(fullBuffer.length - 5),
521 ])
522 })
523 })
524
525 describe('split buffer, multiple message parsing', function () {
526 const dataRowBuffer = buffers.dataRow(['!'])
527 const readyForQueryBuffer = buffers.readyForQuery()
528 const fullBuffer = Buffer.alloc(dataRowBuffer.length + readyForQueryBuffer.length)
529 dataRowBuffer.copy(fullBuffer, 0, 0)
530 readyForQueryBuffer.copy(fullBuffer, dataRowBuffer.length, 0)
531
532 const verifyMessages = function (messages: any[]) {
533 assert.strictEqual(messages.length, 2)
534 assert.deepEqual(messages[0], {
535 name: 'dataRow',
536 fieldCount: 1,
537 length: 11,
538 fields: ['!'],
539 })
540 assert.equal(messages[0].fields[0], '!')
541 assert.deepEqual(messages[1], {
542 name: 'readyForQuery',
543 length: 5,
544 status: 'I',
545 })
546 }
547 // sanity check
548 it('receives both messages when packet is not split', async function () {
549 const messages = await parseBuffers([fullBuffer])
550 verifyMessages(messages)
551 })
552
553 const splitAndVerifyTwoMessages = async function (split: number) {
554 const firstBuffer = Buffer.alloc(fullBuffer.length - split)
555 const secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length)
556 fullBuffer.copy(firstBuffer, 0, 0)
557 fullBuffer.copy(secondBuffer, 0, firstBuffer.length)
558 const messages = await parseBuffers([firstBuffer, secondBuffer])
559 verifyMessages(messages)
560 }
561
562 describe('receives both messages when packet is split', function () {
563 it('in the middle', function () {
564 return splitAndVerifyTwoMessages(11)
565 })
566 it('at the front', function () {
567 return Promise.all([
568 splitAndVerifyTwoMessages(fullBuffer.length - 1),
569 splitAndVerifyTwoMessages(fullBuffer.length - 4),
570 splitAndVerifyTwoMessages(fullBuffer.length - 6),
571 ])
572 })
573
574 it('at the end', function () {
575 return Promise.all([splitAndVerifyTwoMessages(8), splitAndVerifyTwoMessages(1)])
576 })
577 })
578 })
579
580 it('cleans up the reader after handling a packet', function () {
581 const parser = new Parser()
582 parser.parse(oneFieldBuf, () => {})
583 assert.strictEqual((parser as any).reader.buffer.byteLength, 0)
584 })
585})
Note: See TracBrowser for help on using the repository browser.