require(__dirname+'/test-helper'); var Connection = require(__dirname + '/../../../lib/connection'); var buffers = require(__dirname + '/../../test-buffers'); var PARSE = function(buffer) { return new Parser(buffer).parse(); }; var authOkBuffer = buffers.authenticationOk(); var paramStatusBuffer = buffers.parameterStatus('client_encoding', 'UTF8'); var readyForQueryBuffer = buffers.readyForQuery(); var backendKeyDataBuffer = buffers.backendKeyData(1,2); var commandCompleteBuffer = buffers.commandComplete("SELECT 3"); var parseCompleteBuffer = buffers.parseComplete(); var bindCompleteBuffer = buffers.bindComplete(); var portalSuspendedBuffer = buffers.portalSuspended(); var addRow = function(bufferList, name, offset) { return bufferList.addCString(name) //field name .addInt32(offset++) //table id .addInt16(offset++) //attribute of column number .addInt32(offset++) //objectId of field's data type .addInt16(offset++) //datatype size .addInt32(offset++) //type modifier .addInt16(0) //format code, 0 => text }; var row1 = { name: 'id', tableID: 1, attributeNumber: 2, dataTypeID: 3, dataTypeSize: 4, typeModifier: 5, formatCode: 0 }; var oneRowDescBuff = new buffers.rowDescription([row1]); row1.name = 'bang'; var twoRowBuf = new buffers.rowDescription([row1,{ name: 'whoah', tableID: 10, attributeNumber: 11, dataTypeID: 12, dataTypeSize: 13, typeModifier: 14, formatCode: 0 }]) var emptyRowFieldBuf = new BufferList() .addInt16(0) .join(true, 'D'); var emptyRowFieldBuf = buffers.dataRow(); var oneFieldBuf = new BufferList() .addInt16(1) //number of fields .addInt32(5) //length of bytes of fields .addCString('test') .join(true, 'D'); var oneFieldBuf = buffers.dataRow(['test']); var expectedAuthenticationOkayMessage = { name: 'authenticationOk', length: 8 }; var expectedParameterStatusMessage = { name: 'parameterStatus', parameterName: 'client_encoding', parameterValue: 'UTF8', length: 25 }; var expectedBackendKeyDataMessage = { name: 'backendKeyData', processID: 1, secretKey: 2 }; var expectedReadyForQueryMessage = { name: 'readyForQuery', length: 5, status: 'I' }; var expectedCommandCompleteMessage = { length: 13, text: "SELECT 3" }; var emptyRowDescriptionBuffer = new BufferList() .addInt16(0) //number of fields .join(true,'T'); var expectedEmptyRowDescriptionMessage = { name: 'rowDescription', length: 6, fieldCount: 0 }; var expectedOneRowMessage = { name: 'rowDescription', length: 27, fieldCount: 1 }; var expectedTwoRowMessage = { name: 'rowDescription', length: 53, fieldCount: 2 }; var testForMessage = function(buffer, expectedMessage) { var lastMessage = {}; test('recieves and parses ' + expectedMessage.name, function() { var stream = new MemoryStream(); var client = new Connection({ stream: stream }); client.connect(); client.on('message',function(msg) { lastMessage = msg; }); client.on(expectedMessage.name, function() { client.removeAllListeners(expectedMessage.name); }); stream.emit('data', buffer); assert.same(lastMessage, expectedMessage); }); return lastMessage; }; var plainPasswordBuffer = buffers.authenticationCleartextPassword(); var md5PasswordBuffer = buffers.authenticationMD5Password(); var expectedPlainPasswordMessage = { name: 'authenticationCleartextPassword' }; var expectedMD5PasswordMessage = { name: 'authenticationMD5Password' }; var notificationResponseBuffer = buffers.notification(4, 'hi', 'boom'); var expectedNotificationResponseMessage = { name: 'notification', processId: 4, channel: 'hi', payload: 'boom' }; test('Connection', function() { testForMessage(authOkBuffer, expectedAuthenticationOkayMessage); testForMessage(plainPasswordBuffer, expectedPlainPasswordMessage); var msg = testForMessage(md5PasswordBuffer, expectedMD5PasswordMessage); test('md5 has right salt', function() { assert.equalBuffers(msg.salt, Buffer([1,2,3,4])); }); testForMessage(paramStatusBuffer, expectedParameterStatusMessage); testForMessage(backendKeyDataBuffer, expectedBackendKeyDataMessage); testForMessage(readyForQueryBuffer, expectedReadyForQueryMessage); testForMessage(commandCompleteBuffer,expectedCommandCompleteMessage); testForMessage(notificationResponseBuffer, expectedNotificationResponseMessage); test('empty row message', function() { var message = testForMessage(emptyRowDescriptionBuffer, expectedEmptyRowDescriptionMessage); test('has no fields', function() { assert.equal(message.fields.length, 0); }); }); test("no data message", function() { testForMessage(Buffer([0x6e, 0, 0, 0, 4]), { name: 'noData' }); }); test('one row message', function() { var message = testForMessage(oneRowDescBuff, expectedOneRowMessage); test('has one field', function() { assert.equal(message.fields.length, 1); }); test('has correct field info', function() { assert.same(message.fields[0], { name: 'id', tableID: 1, columnID: 2, dataTypeID: 3, dataTypeSize: 4, dataTypeModifier: 5, format: 'text' }); }); }); test('two row message', function() { var message = testForMessage(twoRowBuf, expectedTwoRowMessage); test('has two fields', function() { assert.equal(message.fields.length, 2); }); test('has correct first field', function() { assert.same(message.fields[0], { name: 'bang', tableID: 1, columnID: 2, dataTypeID: 3, dataTypeSize: 4, dataTypeModifier: 5, format: 'text' }) }); test('has correct second field', function() { assert.same(message.fields[1], { name: 'whoah', tableID: 10, columnID: 11, dataTypeID: 12, dataTypeSize: 13, dataTypeModifier: 14, format: 'text' }); }); }); test('parsing rows', function() { test('parsing empty row', function() { var message = testForMessage(emptyRowFieldBuf, { name: 'dataRow', fieldCount: 0 }); test('has 0 fields', function() { assert.equal(message.fields.length, 0); }); }); test('parsing data row with fields', function() { var message = testForMessage(oneFieldBuf, { name: 'dataRow', fieldCount: 1 }); test('has 1 field', function() { assert.equal(message.fields.length, 1); }); test('field is correct', function() { assert.equal(message.fields[0],'test'); }); }); }); test('notice message', function() { //this uses the same logic as error message var buff = buffers.notice([{type: 'C', value: 'code'}]); testForMessage(buff, { name: 'notice', code: 'code' }); }); test('error messages', function() { test('with no fields', function() { var msg = testForMessage(buffers.error(),{ name: 'error' }); }); test('with all the fields', function() { var buffer = buffers.error([{ type: 'S', value: 'ERROR' },{ type: 'C', value: 'code' },{ type: 'M', value: 'message' },{ type: 'D', value: 'details' },{ type: 'H', value: 'hint' },{ type: 'P', value: '100' },{ type: 'p', value: '101' },{ type: 'q', value: 'query' },{ type: 'W', value: 'where' },{ type: 'F', value: 'file' },{ type: 'L', value: 'line' },{ type: 'R', value: 'routine' },{ type: 'Z', //ignored value: 'alsdkf' }]); testForMessage(buffer,{ name: 'error', severity: 'ERROR', code: 'code', message: 'message', detail: 'details', hint: 'hint', position: '100', internalPosition: '101', internalQuery: 'query', where: 'where', file: 'file', line: 'line', routine: 'routine' }); }); }); test('parses parse complete command', function() { testForMessage(parseCompleteBuffer, { name: 'parseComplete' }); }); test('parses bind complete command', function() { testForMessage(bindCompleteBuffer, { name: 'bindComplete' }); }); test('parses portal suspended message', function() { testForMessage(portalSuspendedBuffer, { name: 'portalSuspended' }); }); test('parses replication start message', function() { testForMessage(new Buffer([0x57, 0x00, 0x00, 0x00, 0x04]), { name: 'replicationStart', length: 4 }); }); }); //since the data message on a stream can randomly divide the incomming //tcp packets anywhere, we need to make sure we can parse every single //split on a tcp message test('split buffer, single message parsing', function() { var fullBuffer = buffers.dataRow([null, "bang", "zug zug", null, "!"]); var stream = new MemoryStream(); stream.readyState = 'open'; var client = new Connection({ stream: stream }); client.connect(); var message = null; client.on('message', function(msg) { message = msg; }); test('parses when full buffer comes in', function() { stream.emit('data', fullBuffer); assert.lengthIs(message.fields, 5); assert.equal(message.fields[0], null); assert.equal(message.fields[1], "bang"); assert.equal(message.fields[2], "zug zug"); assert.equal(message.fields[3], null); assert.equal(message.fields[4], "!"); }); var testMessageRecievedAfterSpiltAt = function(split) { var firstBuffer = new Buffer(fullBuffer.length-split); var secondBuffer = new Buffer(fullBuffer.length-firstBuffer.length); fullBuffer.copy(firstBuffer, 0, 0); fullBuffer.copy(secondBuffer, 0, firstBuffer.length); stream.emit('data', firstBuffer); stream.emit('data', secondBuffer); assert.lengthIs(message.fields, 5); assert.equal(message.fields[0], null); assert.equal(message.fields[1], "bang"); assert.equal(message.fields[2], "zug zug"); assert.equal(message.fields[3], null); assert.equal(message.fields[4], "!"); }; test('parses when split in the middle', function() { testMessageRecievedAfterSpiltAt(6); }); test('parses when split at end', function() { testMessageRecievedAfterSpiltAt(2); }); test('parses when split at beginning', function() { testMessageRecievedAfterSpiltAt(fullBuffer.length - 2); testMessageRecievedAfterSpiltAt(fullBuffer.length - 1); testMessageRecievedAfterSpiltAt(fullBuffer.length - 5); }); }); test('split buffer, multiple message parsing', function() { var dataRowBuffer = buffers.dataRow(['!']); var readyForQueryBuffer = buffers.readyForQuery(); var fullBuffer = new Buffer(dataRowBuffer.length + readyForQueryBuffer.length); dataRowBuffer.copy(fullBuffer, 0, 0); readyForQueryBuffer.copy(fullBuffer, dataRowBuffer.length, 0); var messages = []; var stream = new MemoryStream(); var client = new Connection({ stream: stream }); client.connect(); client.on('message', function(msg) { messages.push(msg); }); var verifyMessages = function() { assert.lengthIs(messages, 2); assert.same(messages[0],{ name: 'dataRow', fieldCount: 1 }); assert.equal(messages[0].fields[0],'!'); assert.same(messages[1],{ name: 'readyForQuery' }); messages = []; }; //sanity check test('recieves both messages when packet is not split', function() { stream.emit('data', fullBuffer); verifyMessages(); }); var splitAndVerifyTwoMessages = function(split) { var firstBuffer = new Buffer(fullBuffer.length-split); var secondBuffer = new Buffer(fullBuffer.length-firstBuffer.length); fullBuffer.copy(firstBuffer, 0, 0); fullBuffer.copy(secondBuffer, 0, firstBuffer.length); stream.emit('data', firstBuffer); stream.emit('data', secondBuffer); }; test('recieves both messages when packet is split', function() { test('in the middle', function() { splitAndVerifyTwoMessages(11); }); test('at the front', function() { splitAndVerifyTwoMessages(fullBuffer.length-1); splitAndVerifyTwoMessages(fullBuffer.length-4); splitAndVerifyTwoMessages(fullBuffer.length-6); }); test('at the end', function() { splitAndVerifyTwoMessages(8); splitAndVerifyTwoMessages(1); }); }); });