initial commit
[squeep-amqp-helper] / test / lib / base.js
diff --git a/test/lib/base.js b/test/lib/base.js
new file mode 100644 (file)
index 0000000..a7844e5
--- /dev/null
@@ -0,0 +1,364 @@
+/* eslint-env mocha */
+/* eslint-disable node/no-unpublished-require */
+'use strict';
+const assert = require('assert');
+const sinon = require('sinon');
+const Base = require('../../lib/base');
+const amqp = require('amqplib');
+const { StubLogger } = require('@squeep/test-helper');
+
+describe('Base', function () {
+  let logger, options, base;
+  const expectedException = new Error('oh no');
+
+  beforeEach(function () {
+    logger = new StubLogger()
+    logger._reset();
+    options = {
+      amqp: {
+        url: 'amqp://user:password@rabbitmq.int:5672',
+      },
+    };
+    sinon.stub(amqp, 'connect').resolves({
+      createConfirmChannel: sinon.stub().resolves({
+        on: sinon.stub(),
+        checkQueue: sinon.stub(),
+        assertExchange: sinon.stub(),
+        assertQueue: sinon.stub(),
+        bindQueue: sinon.stub(),
+        prefetch: sinon.stub(),
+        consume: sinon.stub(),
+        recover: sinon.stub(),
+        close: sinon.stub(),
+      }),
+      connection: {
+        stream: {
+          writable: true,
+        },
+      },
+      on: sinon.stub(),
+      close: sinon.stub(),
+    });
+    base = new Base(logger, options);
+  });
+
+  afterEach(function () {
+    sinon.restore();
+  });
+
+  describe('connect', function () {
+    beforeEach(function () {
+      sinon.stub(base, '_connectAMQP');
+      sinon.stub(base, 'close');
+    });
+    it('covers success', async function () {
+      await base.connect();
+      assert(base._connectAMQP.called);
+    });
+    it('covers failure', async function () {
+      base._connectAMQP.rejects(expectedException);
+      await assert.rejects(base.connect(), expectedException);
+      assert(base._connectAMQP.called);
+      assert(base.close.called);
+      assert(base.logger.error.called);
+    });
+  }); // connect
+
+  describe('_connectAMQP', function () {
+    beforeEach(function () {
+      sinon.stub(base, '_establishAMQPConnection');
+      sinon.stub(base, '_establishAMQPChannel');
+    });
+    it('covers', async function () {
+      await base._connectAMQP();
+      assert(base._establishAMQPConnection.called);
+      assert(base._establishAMQPChannel.called);
+    });
+  }); // _connectAMQP
+
+  describe('_establishAMQPConnection', function () {
+    it('covers success', async function () {
+      await base._establishAMQPConnection();
+      assert(amqp.connect.called);
+      assert(base.connection.on.called);
+    });
+    it('covers failure', async function () {
+      amqp.connect.rejects(expectedException);
+      await assert.rejects(base._establishAMQPConnection(), expectedException);
+    });
+  }); // _establishAMQPConnection
+
+  describe('_establishAMQPChannel', function () {
+    beforeEach(async function () {
+      await base._establishAMQPConnection();
+    });
+    it('covers success', async function () {
+      await base._establishAMQPChannel();
+      assert(base.connection.createConfirmChannel.called);
+      assert(base.channel.on.called);
+    });
+    it('covers failure', async function () {
+      base.connection.createConfirmChannel.rejects(expectedException);
+      await assert.rejects(base._establishAMQPChannel(), expectedException);
+    });
+  }); // _establishAMQPChannel
+
+  describe('AMQP Event Handlers', function () {
+    let fatalErr, nonFatalErr;
+    beforeEach(function () {
+      fatalErr = {
+        code: 501,
+      };
+      nonFatalErr = {
+        code: 200,
+      };
+      base.connection = {};
+      base.channel = {};
+      base.queueConsumerTags = { 'consumer': 'placeholder' };
+    });
+    describe('_eventAMQPConnectionClose', function () {
+      it('fatal behavior', function () {
+        base._eventAMQPConnectionClose(fatalErr);
+        assert.strictEqual(base.connection, undefined);
+        assert.strictEqual(base.channel, undefined);
+        assert(base.logger.error.called);
+      });
+      it('non-fatal behavior', function () {
+        base._eventAMQPConnectionClose(nonFatalErr);
+        assert.strictEqual(base.connection, undefined);
+        assert.strictEqual(base.channel, undefined);
+        assert(base.logger.debug.called);
+      });
+    }); // _eventAMQPConnectionClose
+
+    describe('_eventAMQPConnectionError', function () {
+      it('logs event', function () {
+        base._eventAMQPConnectionError(fatalErr);
+        assert(base.logger.error.called);
+      });
+    }); // _eventAMQPConnectionError
+
+    describe('_eventAMQPConnectionBlocked', function () {
+      it('logs event', function () {
+        const reason = 'because';
+        base._eventAMQPConnectionBlocked(reason);
+        assert(base.logger.debug.called);
+      });
+    }); // _eventAMQPConnectionBlocked
+
+    describe('_eventAMQPConnectionUnblocked', function () {
+      it('logs event', function () {
+        base._eventAMQPConnectionUnblocked();
+        assert(base.logger.debug.called);
+      });
+    }); // _eventAMQPConnectionUnblocked
+
+    describe('_eventAMQPChannelClose', function () {
+      it('logs event', function () {
+        base._eventAMQPChannelClose();
+        assert(base.logger.debug.called);
+      });
+      it('logs fatal event', function () {
+        base._eventAMQPChannelClose(fatalErr);
+        assert(base.logger.error.called);
+      });
+    }); // _eventAMQPChannelClose
+
+    describe('_eventAMQPChannelError', function () {
+      it('logs event', function () {
+        base._eventAMQPChannelError(fatalErr);
+        assert(base.logger.error.called);
+      });
+    }); // _eventAMQPChannelError
+
+    describe('_eventAMQPChannelReturn', function () {
+      it('logs event', function () {
+        const msg = 'msg';
+        base._eventAMQPChannelReturn(msg);
+        assert(base.logger.error.called);
+      });
+    }); // _eventAMQPChannelReturn
+
+    describe('_eventAMQPChannelDrain', function () {
+      it('logs event', function () {
+        base._eventAMQPChannelDrain();
+        assert(base.logger.debug.called);
+      });
+    }); // _eventAMQPChannelDrain
+  }); // AMQP Event Handlers
+
+  describe('AMQP Plumbing Naming', function () {
+    let name, prefix;
+    beforeEach(function () {
+      name = 'name';
+      prefix = base.options.prefix;
+    });
+    function _expectedParts(optionNames = []) {
+      const expected = [name];
+      optionNames.forEach((name) => {
+        if (base.options[name]) {
+          expected.push(...base.options[name].split('.'));
+        }
+      }); // eslint-disable-line security/detect-object-injection
+      return expected;
+    }
+    function _assertParts(value, expectedParts, splitter = '.') {
+      const parts = value.split(splitter);
+      expectedParts.forEach((part) => assert(parts.includes(part), `missing ${part} expected [${expectedParts}] got [${parts}]`));
+    }
+    function _assertNoPrefix(value, splitter = '.') {
+      const parts = value.split(splitter);
+      assert(!parts.includes(prefix));
+    }
+
+    describe('_exchangeName', function () {
+      it('names exchange', function () {
+        const expectedParts = _expectedParts(['prefix']);
+        const result = base._exchangeName(name);
+        _assertParts(result, expectedParts);
+      });
+      it('covers no prefix', function () {
+        const expectedParts = _expectedParts([]);
+        base.options.prefix = undefined;
+        const result = base._exchangeName(name);
+        _assertParts(result, expectedParts);
+        _assertNoPrefix(result);
+      });
+    }); // _exchangeName
+
+    describe('_retryExchangeName', function () {
+      it('names retry exchange', function () {
+        const expectedParts = _expectedParts(['prefix', 'retrySuffix']);
+        const result = base._retryExchangeName(name);
+        _assertParts(result, expectedParts);
+      });
+      it('covers no prefix', function () {
+        const expectedParts = _expectedParts(['retrySuffix']);
+        base.options.prefix = undefined;
+        const result = base._retryExchangeName(name);
+        _assertParts(result, expectedParts);
+        _assertNoPrefix(result);
+      });
+    }); // _retryExchangeName
+
+    describe('_queueName', function () {
+      it('names queue', function () {
+        const expectedParts = _expectedParts(['prefix', 'queueSuffix']);
+        const result = base._queueName(name);
+        _assertParts(result, expectedParts);
+      });
+      it('covers no prefix', function () {
+        const expectedParts = _expectedParts(['queueSuffix']);
+        base.options.prefix = undefined;
+        const result = base._queueName(name);
+        _assertParts(result, expectedParts);
+        _assertNoPrefix(result);
+      });
+    }); // _queueName
+
+    describe('_retryQueueName', function () {
+      it('names retry queue', function () {
+        const expectedParts = _expectedParts(['prefix', 'retrySuffix', 'queueSuffix']);
+        const result = base._retryQueueName(name);
+        _assertParts(result, expectedParts);
+      });
+      it('covers no prefix', function () {
+        const expectedParts = _expectedParts(['retrySuffix', 'queueSuffix']);
+        base.options.prefix = undefined;
+        const result = base._retryQueueName(name);
+        _assertParts(result, expectedParts);
+        _assertNoPrefix(result);
+      });
+    }); // _retryQueueName
+
+    describe('_retryQueuePolicyName', function () {
+      it('names retry wildcard queue for ttl policy', function () {
+        const expectedParts = _expectedParts(['prefix', 'retrySuffix', 'queueSuffix']);
+        expectedParts.shift();
+        expectedParts.push('.*');
+        const result = base._retryQueuePolicyName(name);
+        _assertParts(result, expectedParts, '\\.');
+      });
+      it('covers no prefix', function () {
+        const expectedParts = _expectedParts(['retrySuffix', 'queueSuffix']);
+        base.options.prefix = undefined;
+        expectedParts.shift();
+        expectedParts.push('.*');
+        const result = base._retryQueuePolicyName(name);
+        _assertParts(result, expectedParts, '\\.');
+        _assertNoPrefix(result, '\\.');
+      });
+    });
+  }); // AMQP Plumbing Naming
+
+  describe('establishAMQPPlumbing', function () {
+    beforeEach(async function () {
+      await base.connect();
+      sinon.stub(base, 'close');
+    });
+    it('covers success', async function () {
+      await base.establishAMQPPlumbing();
+      assert(base.channel.assertExchange.called);
+      assert(base.channel.assertQueue.called);
+      assert(base.channel.bindQueue.called);
+    });
+    it('covers failure', async function () {
+      base.channel.assertQueue.rejects(expectedException);
+      await assert.rejects(base.establishAMQPPlumbing(), expectedException);
+      assert(base.close.called);
+    });
+  }); // establishAMQPPlumbing
+
+  describe('close', function () {
+    let channelRecoverStub, channelCloseStub, connectionCloseStub;
+    beforeEach(async function () {
+      await base.connect();
+      channelRecoverStub = base.channel.recover;
+      channelCloseStub = base.channel.close;
+      connectionCloseStub = base.connection.close;
+    });
+    it('closes active connection and channel', async function () {
+      await base.close();
+      assert(channelRecoverStub.called);
+      assert(channelCloseStub.called);
+      assert(connectionCloseStub.called);
+    });
+    it('covers no active channel', async function () {
+      base.channel = undefined;
+      await base.close();
+      assert(channelRecoverStub.notCalled);
+      assert(channelCloseStub.notCalled);
+      assert(connectionCloseStub.called);
+    });
+    it('covers no active connection or channel', async function () {
+      base.channel = undefined;
+      base.connection = undefined;
+      await base.close();
+      assert(channelRecoverStub.notCalled);
+      assert(channelCloseStub.notCalled);
+      assert(connectionCloseStub.notCalled);
+    });
+    it('covers failure', async function () {
+      channelCloseStub.rejects(expectedException);
+      await assert.rejects(base.close(), expectedException);
+    });
+  }); // close
+
+  describe('health', function () {
+    beforeEach(async function () {
+      await base._establishAMQPConnection();
+    });
+    it('checks connection is writable', function () {
+      const result = base.health();
+      assert.strictEqual(result, true);
+    });
+  }); // health
+
+  describe('policyCommand', function () {
+    it('covers', function () {
+      const result = base.policyCommand();
+      assert(result);
+    });
+  }); // policyCommand
+
+}); // Base
\ No newline at end of file