"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const test_buffers_1 = __importDefault(require("./testing/test-buffers"));
const buffer_list_1 = __importDefault(require("./testing/buffer-list"));
const _1 = require(".");
const assert_1 = __importDefault(require("assert"));
const stream_1 = require("stream");
var authOkBuffer = test_buffers_1.default.authenticationOk();
var paramStatusBuffer = test_buffers_1.default.parameterStatus('client_encoding', 'UTF8');
var readyForQueryBuffer = test_buffers_1.default.readyForQuery();
var backendKeyDataBuffer = test_buffers_1.default.backendKeyData(1, 2);
var commandCompleteBuffer = test_buffers_1.default.commandComplete('SELECT 3');
var parseCompleteBuffer = test_buffers_1.default.parseComplete();
var bindCompleteBuffer = test_buffers_1.default.bindComplete();
var portalSuspendedBuffer = test_buffers_1.default.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 = test_buffers_1.default.rowDescription([row1]);
row1.name = 'bang';
var twoRowBuf = test_buffers_1.default.rowDescription([
    row1,
    {
        name: 'whoah',
        tableID: 10,
        attributeNumber: 11,
        dataTypeID: 12,
        dataTypeSize: 13,
        typeModifier: 14,
        formatCode: 0,
    },
]);
var emptyRowFieldBuf = new buffer_list_1.default().addInt16(0).join(true, 'D');
var emptyRowFieldBuf = test_buffers_1.default.dataRow([]);
var oneFieldBuf = new buffer_list_1.default()
    .addInt16(1) // number of fields
    .addInt32(5) // length of bytes of fields
    .addCString('test')
    .join(true, 'D');
var oneFieldBuf = test_buffers_1.default.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 = {
    name: 'commandComplete',
    length: 13,
    text: 'SELECT 3',
};
var emptyRowDescriptionBuffer = new buffer_list_1.default()
    .addInt16(0) // number of fields
    .join(true, 'T');
var expectedEmptyRowDescriptionMessage = {
    name: 'rowDescription',
    length: 6,
    fieldCount: 0,
    fields: [],
};
var expectedOneRowMessage = {
    name: 'rowDescription',
    length: 27,
    fieldCount: 1,
    fields: [
        {
            name: 'id',
            tableID: 1,
            columnID: 2,
            dataTypeID: 3,
            dataTypeSize: 4,
            dataTypeModifier: 5,
            format: 'text',
        },
    ],
};
var expectedTwoRowMessage = {
    name: 'rowDescription',
    length: 53,
    fieldCount: 2,
    fields: [
        {
            name: 'bang',
            tableID: 1,
            columnID: 2,
            dataTypeID: 3,
            dataTypeSize: 4,
            dataTypeModifier: 5,
            format: 'text',
        },
        {
            name: 'whoah',
            tableID: 10,
            columnID: 11,
            dataTypeID: 12,
            dataTypeSize: 13,
            dataTypeModifier: 14,
            format: 'text',
        },
    ],
};
var testForMessage = function (buffer, expectedMessage) {
    it('recieves and parses ' + expectedMessage.name, () => __awaiter(this, void 0, void 0, function* () {
        const messages = yield parseBuffers([buffer]);
        const [lastMessage] = messages;
        for (const key in expectedMessage) {
            assert_1.default.deepEqual(lastMessage[key], expectedMessage[key]);
        }
    }));
};
var plainPasswordBuffer = test_buffers_1.default.authenticationCleartextPassword();
var md5PasswordBuffer = test_buffers_1.default.authenticationMD5Password();
var SASLBuffer = test_buffers_1.default.authenticationSASL();
var SASLContinueBuffer = test_buffers_1.default.authenticationSASLContinue();
var SASLFinalBuffer = test_buffers_1.default.authenticationSASLFinal();
var expectedPlainPasswordMessage = {
    name: 'authenticationCleartextPassword',
};
var expectedMD5PasswordMessage = {
    name: 'authenticationMD5Password',
    salt: Buffer.from([1, 2, 3, 4]),
};
var expectedSASLMessage = {
    name: 'authenticationSASL',
    mechanisms: ['SCRAM-SHA-256'],
};
var expectedSASLContinueMessage = {
    name: 'authenticationSASLContinue',
    data: 'data',
};
var expectedSASLFinalMessage = {
    name: 'authenticationSASLFinal',
    data: 'data',
};
var notificationResponseBuffer = test_buffers_1.default.notification(4, 'hi', 'boom');
var expectedNotificationResponseMessage = {
    name: 'notification',
    processId: 4,
    channel: 'hi',
    payload: 'boom',
};
const parseBuffers = (buffers) => __awaiter(void 0, void 0, void 0, function* () {
    const stream = new stream_1.PassThrough();
    for (const buffer of buffers) {
        stream.write(buffer);
    }
    stream.end();
    const msgs = [];
    yield _1.parse(stream, (msg) => msgs.push(msg));
    return msgs;
});
describe('PgPacketStream', function () {
    testForMessage(authOkBuffer, expectedAuthenticationOkayMessage);
    testForMessage(plainPasswordBuffer, expectedPlainPasswordMessage);
    testForMessage(md5PasswordBuffer, expectedMD5PasswordMessage);
    testForMessage(SASLBuffer, expectedSASLMessage);
    testForMessage(SASLContinueBuffer, expectedSASLContinueMessage);
    // this exercises a found bug in the parser:
    // https://github.com/brianc/node-postgres/pull/2210#issuecomment-627626084
    // and adds a test which is deterministic, rather than relying on network packet chunking
    const extendedSASLContinueBuffer = Buffer.concat([SASLContinueBuffer, Buffer.from([1, 2, 3, 4])]);
    testForMessage(extendedSASLContinueBuffer, expectedSASLContinueMessage);
    testForMessage(SASLFinalBuffer, expectedSASLFinalMessage);
    // this exercises a found bug in the parser:
    // https://github.com/brianc/node-postgres/pull/2210#issuecomment-627626084
    // and adds a test which is deterministic, rather than relying on network packet chunking
    const extendedSASLFinalBuffer = Buffer.concat([SASLFinalBuffer, Buffer.from([1, 2, 4, 5])]);
    testForMessage(extendedSASLFinalBuffer, expectedSASLFinalMessage);
    testForMessage(paramStatusBuffer, expectedParameterStatusMessage);
    testForMessage(backendKeyDataBuffer, expectedBackendKeyDataMessage);
    testForMessage(readyForQueryBuffer, expectedReadyForQueryMessage);
    testForMessage(commandCompleteBuffer, expectedCommandCompleteMessage);
    testForMessage(notificationResponseBuffer, expectedNotificationResponseMessage);
    testForMessage(test_buffers_1.default.emptyQuery(), {
        name: 'emptyQuery',
        length: 4,
    });
    testForMessage(Buffer.from([0x6e, 0, 0, 0, 4]), {
        name: 'noData',
    });
    describe('rowDescription messages', function () {
        testForMessage(emptyRowDescriptionBuffer, expectedEmptyRowDescriptionMessage);
        testForMessage(oneRowDescBuff, expectedOneRowMessage);
        testForMessage(twoRowBuf, expectedTwoRowMessage);
    });
    describe('parsing rows', function () {
        describe('parsing empty row', function () {
            testForMessage(emptyRowFieldBuf, {
                name: 'dataRow',
                fieldCount: 0,
            });
        });
        describe('parsing data row with fields', function () {
            testForMessage(oneFieldBuf, {
                name: 'dataRow',
                fieldCount: 1,
                fields: ['test'],
            });
        });
    });
    describe('notice message', function () {
        // this uses the same logic as error message
        var buff = test_buffers_1.default.notice([{ type: 'C', value: 'code' }]);
        testForMessage(buff, {
            name: 'notice',
            code: 'code',
        });
    });
    testForMessage(test_buffers_1.default.error([]), {
        name: 'error',
    });
    describe('with all the fields', function () {
        var buffer = test_buffers_1.default.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',
                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',
        });
    });
    testForMessage(parseCompleteBuffer, {
        name: 'parseComplete',
    });
    testForMessage(bindCompleteBuffer, {
        name: 'bindComplete',
    });
    testForMessage(bindCompleteBuffer, {
        name: 'bindComplete',
    });
    testForMessage(test_buffers_1.default.closeComplete(), {
        name: 'closeComplete',
    });
    describe('parses portal suspended message', function () {
        testForMessage(portalSuspendedBuffer, {
            name: 'portalSuspended',
        });
    });
    describe('parses replication start message', function () {
        testForMessage(Buffer.from([0x57, 0x00, 0x00, 0x00, 0x04]), {
            name: 'replicationStart',
            length: 4,
        });
    });
    describe('copy', () => {
        testForMessage(test_buffers_1.default.copyIn(0), {
            name: 'copyInResponse',
            length: 7,
            binary: false,
            columnTypes: [],
        });
        testForMessage(test_buffers_1.default.copyIn(2), {
            name: 'copyInResponse',
            length: 11,
            binary: false,
            columnTypes: [0, 1],
        });
        testForMessage(test_buffers_1.default.copyOut(0), {
            name: 'copyOutResponse',
            length: 7,
            binary: false,
            columnTypes: [],
        });
        testForMessage(test_buffers_1.default.copyOut(3), {
            name: 'copyOutResponse',
            length: 13,
            binary: false,
            columnTypes: [0, 1, 2],
        });
        testForMessage(test_buffers_1.default.copyDone(), {
            name: 'copyDone',
            length: 4,
        });
        testForMessage(test_buffers_1.default.copyData(Buffer.from([5, 6, 7])), {
            name: 'copyData',
            length: 7,
            chunk: Buffer.from([5, 6, 7]),
        });
    });
    // 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
    describe('split buffer, single message parsing', function () {
        var fullBuffer = test_buffers_1.default.dataRow([null, 'bang', 'zug zug', null, '!']);
        it('parses when full buffer comes in', function () {
            return __awaiter(this, void 0, void 0, function* () {
                const messages = yield parseBuffers([fullBuffer]);
                const message = messages[0];
                assert_1.default.equal(message.fields.length, 5);
                assert_1.default.equal(message.fields[0], null);
                assert_1.default.equal(message.fields[1], 'bang');
                assert_1.default.equal(message.fields[2], 'zug zug');
                assert_1.default.equal(message.fields[3], null);
                assert_1.default.equal(message.fields[4], '!');
            });
        });
        var testMessageRecievedAfterSpiltAt = function (split) {
            return __awaiter(this, void 0, void 0, function* () {
                var firstBuffer = Buffer.alloc(fullBuffer.length - split);
                var secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length);
                fullBuffer.copy(firstBuffer, 0, 0);
                fullBuffer.copy(secondBuffer, 0, firstBuffer.length);
                const messages = yield parseBuffers([fullBuffer]);
                const message = messages[0];
                assert_1.default.equal(message.fields.length, 5);
                assert_1.default.equal(message.fields[0], null);
                assert_1.default.equal(message.fields[1], 'bang');
                assert_1.default.equal(message.fields[2], 'zug zug');
                assert_1.default.equal(message.fields[3], null);
                assert_1.default.equal(message.fields[4], '!');
            });
        };
        it('parses when split in the middle', function () {
            testMessageRecievedAfterSpiltAt(6);
        });
        it('parses when split at end', function () {
            testMessageRecievedAfterSpiltAt(2);
        });
        it('parses when split at beginning', function () {
            testMessageRecievedAfterSpiltAt(fullBuffer.length - 2);
            testMessageRecievedAfterSpiltAt(fullBuffer.length - 1);
            testMessageRecievedAfterSpiltAt(fullBuffer.length - 5);
        });
    });
    describe('split buffer, multiple message parsing', function () {
        var dataRowBuffer = test_buffers_1.default.dataRow(['!']);
        var readyForQueryBuffer = test_buffers_1.default.readyForQuery();
        var fullBuffer = Buffer.alloc(dataRowBuffer.length + readyForQueryBuffer.length);
        dataRowBuffer.copy(fullBuffer, 0, 0);
        readyForQueryBuffer.copy(fullBuffer, dataRowBuffer.length, 0);
        var verifyMessages = function (messages) {
            assert_1.default.strictEqual(messages.length, 2);
            assert_1.default.deepEqual(messages[0], {
                name: 'dataRow',
                fieldCount: 1,
                length: 11,
                fields: ['!'],
            });
            assert_1.default.equal(messages[0].fields[0], '!');
            assert_1.default.deepEqual(messages[1], {
                name: 'readyForQuery',
                length: 5,
                status: 'I',
            });
        };
        // sanity check
        it('recieves both messages when packet is not split', function () {
            return __awaiter(this, void 0, void 0, function* () {
                const messages = yield parseBuffers([fullBuffer]);
                verifyMessages(messages);
            });
        });
        var splitAndVerifyTwoMessages = function (split) {
            return __awaiter(this, void 0, void 0, function* () {
                var firstBuffer = Buffer.alloc(fullBuffer.length - split);
                var secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length);
                fullBuffer.copy(firstBuffer, 0, 0);
                fullBuffer.copy(secondBuffer, 0, firstBuffer.length);
                const messages = yield parseBuffers([firstBuffer, secondBuffer]);
                verifyMessages(messages);
            });
        };
        describe('recieves both messages when packet is split', function () {
            it('in the middle', function () {
                return splitAndVerifyTwoMessages(11);
            });
            it('at the front', function () {
                return Promise.all([
                    splitAndVerifyTwoMessages(fullBuffer.length - 1),
                    splitAndVerifyTwoMessages(fullBuffer.length - 4),
                    splitAndVerifyTwoMessages(fullBuffer.length - 6),
                ]);
            });
            it('at the end', function () {
                return Promise.all([splitAndVerifyTwoMessages(8), splitAndVerifyTwoMessages(1)]);
            });
        });
    });
});
//# sourceMappingURL=inbound-parser.test.js.map