cloud-provider.test.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import CloudProvider from '../../../src/lib/cloud-provider';
  2. let websocketConstructorCount = 0;
  3. // Stub the global websocket so we can call open/close/error/send on it
  4. global.WebSocket = function (url) {
  5. this._url = url;
  6. this._sentMessages = [];
  7. // These are not real websocket methods, but used to trigger callbacks
  8. this._open = () => this.onopen();
  9. this._error = e => this.onerror(e);
  10. this._receive = msg => this.onmessage(msg);
  11. // Stub the real websocket.send to store sent messages
  12. this.send = msg => this._sentMessages.push(msg);
  13. this.close = () => this.onclose();
  14. websocketConstructorCount++;
  15. };
  16. global.WebSocket.CLOSING = 'CLOSING';
  17. global.WebSocket.CLOSED = 'CLOSED';
  18. describe('CloudProvider', () => {
  19. let cloudProvider = null;
  20. let vmIOData = [];
  21. let timeout = 0;
  22. beforeEach(() => {
  23. vmIOData = [];
  24. cloudProvider = new CloudProvider();
  25. // Stub vm
  26. cloudProvider.vm = {
  27. postIOData: (_namespace, data) => {
  28. vmIOData.push(data);
  29. }
  30. };
  31. // Stub setTimeout so this can run instantly.
  32. cloudProvider.setTimeout = (fn, after) => {
  33. timeout = after;
  34. fn();
  35. };
  36. // Stub randomize to make it consistent for testing.
  37. cloudProvider.randomizeDuration = t => t;
  38. });
  39. test('createVariable', () => {
  40. cloudProvider.createVariable('hello', 1);
  41. const obj = JSON.parse(cloudProvider.connection._sentMessages[0]);
  42. expect(obj.method).toEqual('create');
  43. expect(obj.name).toEqual('hello');
  44. expect(obj.value).toEqual(1);
  45. });
  46. test('updateVariable', () => {
  47. cloudProvider.updateVariable('hello', 1);
  48. const obj = JSON.parse(cloudProvider.connection._sentMessages[0]);
  49. expect(obj.method).toEqual('set');
  50. expect(obj.name).toEqual('hello');
  51. expect(obj.value).toEqual(1);
  52. });
  53. test('updateVariable with falsey value', () => {
  54. cloudProvider.updateVariable('hello', 0);
  55. const obj = JSON.parse(cloudProvider.connection._sentMessages[0]);
  56. expect(obj.method).toEqual('set');
  57. expect(obj.name).toEqual('hello');
  58. expect(obj.value).toEqual(0);
  59. });
  60. test('renameVariable', () => {
  61. cloudProvider.renameVariable('oldName', 'newName');
  62. const obj = JSON.parse(cloudProvider.connection._sentMessages[0]);
  63. expect(obj.method).toEqual('rename');
  64. expect(obj.name).toEqual('oldName');
  65. expect(typeof obj.value).toEqual('undefined');
  66. expect(obj.new_name).toEqual('newName');
  67. });
  68. test('deleteVariable', () => {
  69. cloudProvider.deleteVariable('hello');
  70. const obj = JSON.parse(cloudProvider.connection._sentMessages[0]);
  71. expect(obj.method).toEqual('delete');
  72. expect(obj.name).toEqual('hello');
  73. expect(typeof obj.value).toEqual('undefined');
  74. });
  75. test('onMessage set', () => {
  76. const msg = JSON.stringify({
  77. method: 'set',
  78. name: 'name',
  79. value: 'value'
  80. });
  81. cloudProvider.connection._receive({data: msg});
  82. expect(vmIOData[0].varUpdate.name).toEqual('name');
  83. expect(vmIOData[0].varUpdate.value).toEqual('value');
  84. });
  85. test('onMessage with newline at the end', () => {
  86. const msg1 = JSON.stringify({
  87. method: 'set',
  88. name: 'name1',
  89. value: 'value'
  90. });
  91. cloudProvider.onMessage({data: `${msg1}\n`});
  92. expect(vmIOData[0].varUpdate.name).toEqual('name1');
  93. });
  94. test('onMessage with multiple commands', () => {
  95. const msg1 = JSON.stringify({
  96. method: 'set',
  97. name: 'name1',
  98. value: 'value'
  99. });
  100. const msg2 = JSON.stringify({
  101. method: 'set',
  102. name: 'name2',
  103. value: 'value2'
  104. });
  105. cloudProvider.connection._receive({data: `${msg1}\n${msg2}`});
  106. expect(vmIOData[0].varUpdate.name).toEqual('name1');
  107. expect(vmIOData[1].varUpdate.name).toEqual('name2');
  108. });
  109. test('connnection attempts set back to 1 when socket is opened', () => {
  110. cloudProvider.connectionAttempts = 100;
  111. cloudProvider.connection._open();
  112. expect(cloudProvider.connectionAttempts).toBe(1);
  113. });
  114. test('disconnect waits for a period equal to 2^k-1 before trying again', () => {
  115. websocketConstructorCount = 1; // This is global, so set it back to 1 to start
  116. // Constructor attempts to open connection, so attempts is initially 1
  117. expect(cloudProvider.connectionAttempts).toBe(1);
  118. // Make sure a close without a previous OPEN still waits 1s before reconnecting
  119. cloudProvider.connection.close();
  120. expect(timeout).toEqual(1 * 1000); // 2^1 - 1
  121. expect(websocketConstructorCount).toBe(2);
  122. expect(cloudProvider.connectionAttempts).toBe(2);
  123. cloudProvider.connection.close();
  124. expect(timeout).toEqual(3 * 1000); // 2^2 - 1
  125. expect(websocketConstructorCount).toBe(3);
  126. expect(cloudProvider.connectionAttempts).toBe(3);
  127. cloudProvider.connection.close();
  128. expect(timeout).toEqual(7 * 1000); // 2^3 - 1
  129. expect(websocketConstructorCount).toBe(4);
  130. expect(cloudProvider.connectionAttempts).toBe(4);
  131. cloudProvider.connection.close();
  132. expect(timeout).toEqual(15 * 1000); // 2^4 - 1
  133. expect(websocketConstructorCount).toBe(5);
  134. expect(cloudProvider.connectionAttempts).toBe(5);
  135. cloudProvider.connection.close();
  136. expect(timeout).toEqual(31 * 1000); // 2^5 - 1
  137. expect(websocketConstructorCount).toBe(6);
  138. expect(cloudProvider.connectionAttempts).toBe(6);
  139. cloudProvider.connection.close();
  140. expect(timeout).toEqual(31 * 1000); // maxed out at 2^5 - 1
  141. expect(websocketConstructorCount).toBe(7);
  142. expect(cloudProvider.connectionAttempts).toBe(7);
  143. });
  144. test('close after connection is opened waits 1s before reconnecting', () => {
  145. // This test is basically to check that opening the connection does not impact
  146. // the time until reconnection for the first reconnect.
  147. // It is easy to introduce a bug that causes reconnection time to be different
  148. // based on whether an initial connection was made.
  149. websocketConstructorCount = 1;
  150. cloudProvider.connection._open();
  151. cloudProvider.connection.close();
  152. expect(timeout).toEqual(1 * 1000); // 2^1 - 1
  153. expect(websocketConstructorCount).toBe(2);
  154. expect(cloudProvider.connectionAttempts).toBe(2);
  155. });
  156. test('exponentialTimeout caps connection attempt number', () => {
  157. cloudProvider.connectionAttempts = 1000;
  158. expect(cloudProvider.exponentialTimeout()).toEqual(31 * 1000);
  159. });
  160. test('requestCloseConnection does not try to reconnect', () => {
  161. websocketConstructorCount = 1; // This is global, so set it back to 1 to start
  162. cloudProvider.requestCloseConnection();
  163. expect(websocketConstructorCount).toBe(1); // No reconnection attempts
  164. });
  165. });