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