easy-scratch/test/unit/util/cloud-provider.test.js

192 lines
7.0 KiB
JavaScript
Raw Normal View History

2025-02-12 12:27:32 +00:00
import CloudProvider from '../../../src/lib/cloud-provider';
let websocketConstructorCount = 0;
// Stub the global websocket so we can call open/close/error/send on it
global.WebSocket = function (url) {
this._url = url;
this._sentMessages = [];
// These are not real websocket methods, but used to trigger callbacks
this._open = () => this.onopen();
this._error = e => this.onerror(e);
this._receive = msg => this.onmessage(msg);
// Stub the real websocket.send to store sent messages
this.send = msg => this._sentMessages.push(msg);
this.close = () => this.onclose();
websocketConstructorCount++;
};
global.WebSocket.CLOSING = 'CLOSING';
global.WebSocket.CLOSED = 'CLOSED';
describe('CloudProvider', () => {
let cloudProvider = null;
let vmIOData = [];
let timeout = 0;
beforeEach(() => {
vmIOData = [];
cloudProvider = new CloudProvider();
// Stub vm
cloudProvider.vm = {
postIOData: (_namespace, data) => {
vmIOData.push(data);
}
};
// Stub setTimeout so this can run instantly.
cloudProvider.setTimeout = (fn, after) => {
timeout = after;
fn();
};
// Stub randomize to make it consistent for testing.
cloudProvider.randomizeDuration = t => t;
});
test('createVariable', () => {
cloudProvider.createVariable('hello', 1);
const obj = JSON.parse(cloudProvider.connection._sentMessages[0]);
expect(obj.method).toEqual('create');
expect(obj.name).toEqual('hello');
expect(obj.value).toEqual(1);
});
test('updateVariable', () => {
cloudProvider.updateVariable('hello', 1);
const obj = JSON.parse(cloudProvider.connection._sentMessages[0]);
expect(obj.method).toEqual('set');
expect(obj.name).toEqual('hello');
expect(obj.value).toEqual(1);
});
test('updateVariable with falsey value', () => {
cloudProvider.updateVariable('hello', 0);
const obj = JSON.parse(cloudProvider.connection._sentMessages[0]);
expect(obj.method).toEqual('set');
expect(obj.name).toEqual('hello');
expect(obj.value).toEqual(0);
});
test('renameVariable', () => {
cloudProvider.renameVariable('oldName', 'newName');
const obj = JSON.parse(cloudProvider.connection._sentMessages[0]);
expect(obj.method).toEqual('rename');
expect(obj.name).toEqual('oldName');
expect(typeof obj.value).toEqual('undefined');
expect(obj.new_name).toEqual('newName');
});
test('deleteVariable', () => {
cloudProvider.deleteVariable('hello');
const obj = JSON.parse(cloudProvider.connection._sentMessages[0]);
expect(obj.method).toEqual('delete');
expect(obj.name).toEqual('hello');
expect(typeof obj.value).toEqual('undefined');
});
test('onMessage set', () => {
const msg = JSON.stringify({
method: 'set',
name: 'name',
value: 'value'
});
cloudProvider.connection._receive({data: msg});
expect(vmIOData[0].varUpdate.name).toEqual('name');
expect(vmIOData[0].varUpdate.value).toEqual('value');
});
test('onMessage with newline at the end', () => {
const msg1 = JSON.stringify({
method: 'set',
name: 'name1',
value: 'value'
});
cloudProvider.onMessage({data: `${msg1}\n`});
expect(vmIOData[0].varUpdate.name).toEqual('name1');
});
test('onMessage with multiple commands', () => {
const msg1 = JSON.stringify({
method: 'set',
name: 'name1',
value: 'value'
});
const msg2 = JSON.stringify({
method: 'set',
name: 'name2',
value: 'value2'
});
cloudProvider.connection._receive({data: `${msg1}\n${msg2}`});
expect(vmIOData[0].varUpdate.name).toEqual('name1');
expect(vmIOData[1].varUpdate.name).toEqual('name2');
});
test('connnection attempts set back to 1 when socket is opened', () => {
cloudProvider.connectionAttempts = 100;
cloudProvider.connection._open();
expect(cloudProvider.connectionAttempts).toBe(1);
});
test('disconnect waits for a period equal to 2^k-1 before trying again', () => {
websocketConstructorCount = 1; // This is global, so set it back to 1 to start
// Constructor attempts to open connection, so attempts is initially 1
expect(cloudProvider.connectionAttempts).toBe(1);
// Make sure a close without a previous OPEN still waits 1s before reconnecting
cloudProvider.connection.close();
expect(timeout).toEqual(1 * 1000); // 2^1 - 1
expect(websocketConstructorCount).toBe(2);
expect(cloudProvider.connectionAttempts).toBe(2);
cloudProvider.connection.close();
expect(timeout).toEqual(3 * 1000); // 2^2 - 1
expect(websocketConstructorCount).toBe(3);
expect(cloudProvider.connectionAttempts).toBe(3);
cloudProvider.connection.close();
expect(timeout).toEqual(7 * 1000); // 2^3 - 1
expect(websocketConstructorCount).toBe(4);
expect(cloudProvider.connectionAttempts).toBe(4);
cloudProvider.connection.close();
expect(timeout).toEqual(15 * 1000); // 2^4 - 1
expect(websocketConstructorCount).toBe(5);
expect(cloudProvider.connectionAttempts).toBe(5);
cloudProvider.connection.close();
expect(timeout).toEqual(31 * 1000); // 2^5 - 1
expect(websocketConstructorCount).toBe(6);
expect(cloudProvider.connectionAttempts).toBe(6);
cloudProvider.connection.close();
expect(timeout).toEqual(31 * 1000); // maxed out at 2^5 - 1
expect(websocketConstructorCount).toBe(7);
expect(cloudProvider.connectionAttempts).toBe(7);
});
test('close after connection is opened waits 1s before reconnecting', () => {
// This test is basically to check that opening the connection does not impact
// the time until reconnection for the first reconnect.
// It is easy to introduce a bug that causes reconnection time to be different
// based on whether an initial connection was made.
websocketConstructorCount = 1;
cloudProvider.connection._open();
cloudProvider.connection.close();
expect(timeout).toEqual(1 * 1000); // 2^1 - 1
expect(websocketConstructorCount).toBe(2);
expect(cloudProvider.connectionAttempts).toBe(2);
});
test('exponentialTimeout caps connection attempt number', () => {
cloudProvider.connectionAttempts = 1000;
expect(cloudProvider.exponentialTimeout()).toEqual(31 * 1000);
});
test('requestCloseConnection does not try to reconnect', () => {
websocketConstructorCount = 1; // This is global, so set it back to 1 to start
cloudProvider.requestCloseConnection();
expect(websocketConstructorCount).toBe(1); // No reconnection attempts
});
});