7926746ae95fe9e2c5e87e7541ee66c270394ed8
[websub-hub] / test / src / db / postgres-listener.js
1 /* eslint-env mocha */
2 'use strict';
3
4 const assert = require('assert');
5 const sinon = require('sinon');
6 const stubLogger = require('../../stub-logger');
7 const Listener = require('../../../src/db/postgres/listener');
8
9 const snooze = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
10 const noExpectedException = 'did not get expected exception';
11
12 describe('Postgres Listener', function () {
13 let listener, options, connectionStub, pgpStub;
14 beforeEach(function () {
15 connectionStub = {
16 client: {
17 on: sinon.stub(),
18 removeListener: sinon.stub(),
19 },
20 done: sinon.stub(),
21 none: sinon.stub(),
22 };
23 pgpStub = {
24 connect: sinon.stub().resolves(connectionStub),
25 };
26 options = {
27 dataCallback: sinon.stub(),
28 connectionLostCallback: sinon.stub(),
29 connectionEstablishedCallback: sinon.stub(),
30 pingDelayMs: 100,
31 reconnectDelayMs: 1000,
32 reconnectTimes: 1,
33 };
34 listener = new Listener(stubLogger, pgpStub, options);
35 });
36 afterEach(function () {
37 sinon.restore();
38 });
39
40 describe('start', function () {
41 it('covers', async function () {
42 sinon.stub(listener, '_reconnect').resolves();
43 sinon.stub(listener, '_sendPing').resolves();
44 await listener.start();
45 assert(listener._reconnect.called);
46 assert(listener._sendPing.called);
47 });
48 }); // start
49
50 describe('stop', function () {
51 it('covers not started', async function () {
52 await listener.stop();
53 });
54 it('cancels pending reconnect', async function() {
55 const pendingReconnect = sinon.stub();
56 listener.reconnectPending = setTimeout(pendingReconnect, 100);
57 await listener.stop();
58 snooze(110);
59 assert(!pendingReconnect.called);
60 });
61 it('closes existing connection', async function () {
62 listener.connection = connectionStub;
63 await listener.stop();
64 assert(connectionStub.client.removeListener.called);
65 assert.strictEqual(listener.connection, null);
66 assert(options.connectionLostCallback.called);
67 });
68 }); // stop
69
70 describe('_reconnect', function () {
71 it('reconnects', async function () {
72 await listener._reconnect(0, 1);
73 assert(listener.connection);
74 assert(options.connectionEstablishedCallback.called);
75 });
76 it('closes existing connection before reconnecting', async function () {
77 const existingConnection = {
78 done: sinon.stub(),
79 };
80 listener.connection = existingConnection;
81 await listener._reconnect(0, 1);
82 assert(existingConnection.done.called);
83 });
84 it('overrides a pending reconnect', async function () {
85 this.slow(300);
86 const pendingReconnect = sinon.stub();
87 listener.reconnectPending = setTimeout(pendingReconnect, 100);
88 await listener._reconnect(0, 1);
89 await snooze(110);
90 assert(!pendingReconnect.called);
91 });
92 it('fails with no remaining retries', async function () {
93 const expected = new Error('foo');
94 pgpStub.connect = sinon.stub().rejects(expected);
95 try {
96 await listener._reconnect(0, 0);
97 assert.fail(noExpectedException);
98 } catch (e) {
99 assert.deepStrictEqual(e, expected);
100 }
101 });
102 it('fails all remaining retries', async function () {
103 const expected = new Error('foo');
104 pgpStub.connect = sinon.stub().rejects(expected);
105 try {
106 await listener._reconnect(0, 1);
107 assert.fail(noExpectedException);
108 } catch (e) {
109 assert.deepStrictEqual(e, expected);
110 }
111 });
112 it('fails first retry', async function () {
113 const expected = new Error('foo');
114 pgpStub.connect = sinon.stub().onCall(0).rejects(expected).resolves(connectionStub);
115 await listener._reconnect(0, 1);
116 assert(options.connectionEstablishedCallback.called);
117 });
118 }); // _reconnect
119
120 describe('_onConnectionLost', function () {
121 let error, event;
122 beforeEach(function () {
123 error = new Error('blah');
124 event = connectionStub;
125 sinon.stub(listener, '_reconnect');
126 });
127 it('success', async function () {
128 await listener._onConnectionLost(error, event);
129 assert.strictEqual(listener.connection, null);
130 assert(event.client.removeListener.called);
131 assert(listener.options.connectionLostCallback.called);
132 assert(listener._reconnect.called);
133 });
134 it('covers reconnect failure', async function () {
135 listener._reconnect.rejects(error);
136 await listener._onConnectionLost(error, event);
137 assert.strictEqual(listener.connection, null);
138 assert(event.client.removeListener.called);
139 assert(listener.options.connectionLostCallback.called);
140 assert(listener._reconnect.called);
141 });
142 it('covers listener removal failure', async function () {
143 event.client.removeListener.throws(error);
144 await listener._onConnectionLost(error, event);
145 assert.strictEqual(listener.connection, null);
146 assert(event.client.removeListener.called);
147 assert(listener.options.connectionLostCallback.called);
148 assert(listener._reconnect.called);
149 });
150 }); // _onConnectionLost
151
152 describe('_onNotification', function () {
153 it('sends data', async function () {
154 const data = {
155 payload: 'foo',
156 };
157 await listener._onNotification(data);
158 assert(listener.options.dataCallback.called);
159 });
160 it('ignores pings', async function () {
161 const data = {
162 payload: 'ping',
163 };
164 await listener._onNotification(data);
165 assert(!listener.options.dataCallback.called);
166 });
167 }); // _onNotification
168
169 describe('_sendPing', function () {
170 it('covers no connection', async function () {
171 this.slow(300);
172 await listener._sendPing();
173 await snooze(110);
174 clearTimeout(listener.nextPingTimeout);
175 });
176 it('success', async function () {
177 this.slow(300);
178 listener.connection = connectionStub;
179 await listener._sendPing();
180 await snooze(110);
181 clearTimeout(listener.nextPingTimeout);
182 assert(connectionStub.none.called);
183 });
184 it('covers error', async function () {
185 const err = new Error('blah');
186 this.slow(300);
187 listener.connection = connectionStub;
188 listener.connection.none.rejects(err);
189 await listener._sendPing();
190 await snooze(110);
191 clearTimeout(listener.nextPingTimeout);
192 assert(listener.connection.none.called);
193
194 });
195 }); // _sendPing
196
197 }); // Postgres Listener