Skip to content

Commit

Permalink
fix: Bug fixes, refinements, and lots of tests! (#131)
Browse files Browse the repository at this point in the history
* Add tests for 100% coverage on synchronize.tsx

* Add tests for 100% coverage on courierSync.ts

* Add tests for 100% coverage in connectionStatus.ts

* Add tests for 100% coverate in settings.ts

* Lint

* Add test for 100% coverage in message.ts

* Expand onboarding to fill the window

* lint

* Hide menubar on About and Libraires windows

Only effects windows and linux.

* Handle close codes from the courier sync websocket, Fixes #129

Anything other 1000 is an error.

* Fix tsc import error

* Provide icons for packaging

Hopefully this fix the app icon on Windows.

Co-authored-by: Gus Narea <[email protected]>
  • Loading branch information
liliakai and gnarea authored Apr 30, 2021
1 parent cdac8bd commit a03b034
Show file tree
Hide file tree
Showing 21 changed files with 180 additions and 23 deletions.
Binary file added packages/ui/buildResources/icon.icns
Binary file not shown.
Binary file added packages/ui/buildResources/icon.ico
Binary file not shown.
Binary file added packages/ui/buildResources/icons/1024x1024.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/ui/buildResources/icons/128x128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/ui/buildResources/icons/16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/ui/buildResources/icons/24x24.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/ui/buildResources/icons/256x256.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/ui/buildResources/icons/32x32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/ui/buildResources/icons/48x48.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/ui/buildResources/icons/512x512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/ui/buildResources/icons/64x64.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@
},
"build": {
"appId": "tech.relaycorp.gateway",
"directories": {
"buildResources": "./buildResources"
},
"linux": {
"category": "Network",
"icon": "src/electron/assets/logo.png",
Expand Down
36 changes: 36 additions & 0 deletions packages/ui/src/electron/components/synchronize.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,48 @@
import { render } from "@testing-library/react";
import React from 'react';
import { CourierSyncError, CourierSyncStatus, synchronizeWithCourier } from '../../ipc/courierSync';
import Synchronize from './synchronize';

jest.mock('../../ipc/courierSync');

describe('Synchronize', () => {
test('renders', async () => {
(synchronizeWithCourier as jest.Mock).mockReturnValue({
promise: (async function* (): AsyncIterable<CourierSyncStatus> {
yield CourierSyncStatus.COLLECTING_CARGO;
})()
})

const onComplete = jest.fn();
const onReset = jest.fn();
const el = render(<Synchronize token={"TOKEN"} onComplete={onComplete} onReset={onReset}/>);
expect(el.container.firstChild).toBeTruthy();
});
test('renders on an error', async () => {
(synchronizeWithCourier as jest.Mock).mockReturnValue({
promise: (async function* fakeSource(): AsyncIterable<CourierSyncStatus> {
throw new CourierSyncError('error');
})()
})

const onComplete = jest.fn();
const onReset = jest.fn();
const el = render(<Synchronize token={"TOKEN"} onComplete={onComplete} onReset={onReset}/>);
expect(el.container.firstChild).toBeTruthy();
});
test('aborts on unmount', async () => {
const abort = jest.fn();
(synchronizeWithCourier as jest.Mock).mockReturnValue({
abort,
promise: (async function* fakeSource(): AsyncIterable<CourierSyncStatus> {
return;
})(),
})

const onComplete = jest.fn();
const onReset = jest.fn();
const el = render(<Synchronize token={"TOKEN"} onComplete={onComplete} onReset={onReset}/>);
el.unmount();
expect(abort).toHaveBeenCalledTimes(1);
});
});
2 changes: 2 additions & 0 deletions packages/ui/src/electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ function showAbout(): void {
width: 400,
});

win.setMenuBarVisibility(false);
win.loadFile('about.html');
}

Expand All @@ -142,6 +143,7 @@ function showLibraries(): void {
width: 500,
});

win.setMenuBarVisibility(false);
win.loadFile('libraries.html');
}

Expand Down
11 changes: 6 additions & 5 deletions packages/ui/src/electron/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,12 @@ a:hover {
align-items: center;
}
.stepper {
width: 770px;
height: 460px;
height: 100%;
width: 100%;
margin: auto;
display: flex;
flex-direction: row;
background: white;
box-shadow: 4px 4px 10px 0px rgb(0, 0, 0, 0.1);
}
.stepper .left {
padding: 10px 30px;
Expand All @@ -162,12 +161,14 @@ a:hover {
align-items: center;
}
.stepper .right {
flex-grow: 1;
box-sizing: border-box;
height: 460px;
width: 700px;
margin: auto;
padding: 40px 30px;
display: flex;
flex-direction: column;
align-items: flex-start;

}
.stepper .right h1 {
text-align: left;
Expand Down
25 changes: 19 additions & 6 deletions packages/ui/src/ipc/connectionStatus.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import pipe from 'it-pipe';
import * as itws from 'it-ws';
import { sleep } from './_utils';

import { asyncIterableToArray, iterableTake } from '../testUtils/iterables';
import { asyncIterableToArray } from '../testUtils/iterables';
import { ConnectionStatus, pollConnectionStatus } from './connectionStatus';

jest.mock('it-ws', () => ({
Expand All @@ -23,16 +23,13 @@ describe('pollConnectionStatus', () => {
await sleep(1);

yield 'DISCONNECTED';
await sleep(1);
})(),
});

jest.setTimeout(10_000);

const statuses = await pipe(
pollConnectionStatus('TOKEN').promise,
iterableTake(4),
asyncIterableToArray,
);
const statuses = await pipe(pollConnectionStatus('TOKEN').promise, asyncIterableToArray);

expect(statuses).toEqual([
ConnectionStatus.UNREGISTERED,
Expand All @@ -41,4 +38,20 @@ describe('pollConnectionStatus', () => {
ConnectionStatus.DISCONNECTED,
]);
});
test('should be abortable', async () => {
(itws.connect as jest.Mock).mockReturnValue({
source: (async function* fakeSource(): AsyncIterable<string> {
await sleep(1);
yield 'CONNECTED_TO_PUBLIC_GATEWAY';
})(),
});

const { promise, abort } = pollConnectionStatus('TOKEN');
abort();
const handleStatus = jest.fn();
for await (const item of promise) {
handleStatus(item);
}
expect(handleStatus).toHaveBeenCalledTimes(0);
});
});
74 changes: 64 additions & 10 deletions packages/ui/src/ipc/courierSync.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ jest.mock('it-ws', () => ({
describe('synchronizeWithCourier', () => {
test('should temporarily cycle through all the possible statuses', async () => {
(itws.connect as jest.Mock).mockReturnValue({
socket: {
addEventListener: (_name: string, callback: (event: CloseEvent) => void) => {
callback(new CloseEvent('close', { code: 1000 }));
},
},
source: (async function* fakeSource(): AsyncIterable<string> {
yield 'COLLECTION';
await sleep(1);
Expand All @@ -34,46 +39,95 @@ describe('synchronizeWithCourier', () => {
CourierSyncStatus.COMPLETE,
]);
});
test('should throw an error on unknown status', async () => {
test('should throw an error and not yield an unknown status', async () => {
(itws.connect as jest.Mock).mockReturnValue({
socket: {
addEventListener: (_name: string, callback: (event: CloseEvent) => void) => {
callback(new CloseEvent('close', { code: 1000 }));
},
},
source: (async function* buggySource(): AsyncIterable<string> {
yield 'UNKNOWN_STATUS';
await sleep(1);
})(),
});
jest.setTimeout(3_000);

const handleStatus = jest.fn();
try {
await synchronizeWithCourier('TOKEN').promise;
for await (const item of synchronizeWithCourier('TOKEN').promise) {
handleStatus(item);
}
} catch (err) {
expect(err).toBeInstanceOf(CourierSyncError);
expect(err.message).toEqual('Unknown status: UNKNOWN_STATUS');
expect(err.message).toMatch(/Unknown status/i);
expect(handleStatus).toHaveBeenCalledTimes(0);
}
});
test('should throw an error on promise rejection status', async () => {
(itws.connect as jest.Mock).mockReturnValue({
socket: {
addEventListener: (_name: string, callback: (event: CloseEvent) => void) => {
callback(new CloseEvent('close', { code: 1000 }));
},
},
source: (async function* failingSource(): AsyncIterable<string> {
return Promise.reject('REJECTED');
})(),
});
jest.setTimeout(3_000);

const handleStatus = jest.fn();
try {
await synchronizeWithCourier('TOKEN').promise;
for await (const item of synchronizeWithCourier('TOKEN').promise) {
handleStatus(item);
}
} catch (err) {
expect(err).toBeInstanceOf(CourierSyncError);
expect(handleStatus).toHaveBeenCalledTimes(0);
}
});
test('should be abortable', async () => {
(itws.connect as jest.Mock).mockReturnValue({
socket: {
addEventListener: (_name: string, callback: (event: CloseEvent) => void) => {
callback(new CloseEvent('close', { code: 1000 }));
},
},
source: (async function* fakeSource(): AsyncIterable<string> {
await sleep(5);
yield 'COLLECTING_CARGO';
await sleep(1);
yield 'COLLECTION';
})(),
});
jest.setTimeout(1_000);

const { promise, abort } = synchronizeWithCourier('TOKEN');
abort();
await promise;
const handleStatus = jest.fn();
for await (const item of promise) {
handleStatus(item);
}
expect(handleStatus).toHaveBeenCalledTimes(0);
});
test('should throw an exception on a closing error code', async () => {
(itws.connect as jest.Mock).mockReturnValue({
socket: {
addEventListener: (_name: string, callback: (event: CloseEvent) => void) => {
callback(new CloseEvent('close', { code: 1011 }));
},
},
source: (async function* fakeSource(): AsyncIterable<string> {
await sleep(1);
yield 'COLLECTION';
})(),
});

const { promise } = synchronizeWithCourier('TOKEN');
const handleStatus = jest.fn();
try {
for await (const item of promise) {
handleStatus(item);
}
} catch (err) {
expect(err).toBeInstanceOf(CourierSyncError);
expect(err.message).toMatch(/1011/i);
}
});
});
9 changes: 9 additions & 0 deletions packages/ui/src/ipc/courierSync.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import abortable from 'abortable-iterator';
import { connect } from 'it-ws';
import { CloseEvent } from 'ws';
import PrivateGatewayError from '../PrivateGatewayError';

export enum CourierSyncStatus {
Expand Down Expand Up @@ -44,6 +45,11 @@ async function* _synchronizeWithCourier(token: string): AsyncIterable<CourierSyn
try {
const WS_URL = 'ws://127.0.0.1:13276/_control/courier-sync?auth=' + token;
const stream = connect(WS_URL, { binary: true });
let closeEventCode = 0;
stream.socket.addEventListener('close', (event: CloseEvent) => {
closeEventCode = event.code;
});

for await (const buffer of stream.source) {
const name = buffer.toString();
switch (name) {
Expand All @@ -60,6 +66,9 @@ async function* _synchronizeWithCourier(token: string): AsyncIterable<CourierSyn
throw new CourierSyncError(`Unknown status: ${name}`);
}
}
if (closeEventCode !== 1000) {
throw new CourierSyncError(`Socket error: ${closeEventCode}`);
}
// Server does not send this one, but the UI is waiting for it
yield CourierSyncStatus.COMPLETE;
} catch (err) {
Expand Down
14 changes: 14 additions & 0 deletions packages/ui/src/ipc/message.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ServerMessage, ServerMessageType } from '../ipc/message';

describe('ServerMessage', () => {
test('is defined', async () => {
function handler(message: ServerMessage): ServerMessage {
return message;
}
const serverMessage = handler({
type: ServerMessageType.TOKEN_MESSAGE,
value: 'authtoken',
});
expect(serverMessage.value).toEqual('authtoken');
});
});
27 changes: 25 additions & 2 deletions packages/ui/src/ipc/settings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,22 @@ describe('getPublicGatewayAddress', () => {

expect(fetchMock.lastUrl()).toEqual('http://127.0.0.1:13276/_control/public-gateway');
});
test('should throw SettingError on a server error message', async () => {
fetchMock.get(request, {
body: { message: 'error message' },
status: 500,
});
try {
await getPublicGatewayAddress('TOKEN');
} catch (error) {
expect(error).toBeInstanceOf(SettingError);
expect(error.message).toEqual('error message');
}
});
test('should throw SettingError on a random response', async () => {
fetchMock.get(request, {
body: {},
status: 404,
statusText: 'Unknown',
});
try {
await getPublicGatewayAddress('TOKEN');
Expand Down Expand Up @@ -72,10 +84,21 @@ describe('migratePublicGatewayAddress', () => {
expect(fetchMock.lastUrl()).toEqual('http://127.0.0.1:13276/_control/public-gateway');
}
});
test('should throw SettingError on a random response', async () => {
fetchMock.put(request, {
body: {},
status: 400,
});
try {
await migratePublicGatewayAddress('kings-landing.relaycorp.cloud', 'TOKEN');
} catch (error) {
expect(error).toBeInstanceOf(SettingError);
expect(fetchMock.lastUrl()).toEqual('http://127.0.0.1:13276/_control/public-gateway');
}
});
test('should throw SettingError on a random response', async () => {
fetchMock.put(request, {
status: 404,
statusText: 'Unknown',
});
try {
await migratePublicGatewayAddress('kings-landing.relaycorp.cloud', 'TOKEN');
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/src/types/it-ws.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
declare module 'it-ws' {
import { Duplex } from 'stream';
import WebSocket from 'ws';
interface Options {
readonly binary: boolean;
}
interface SocketStream {
readonly source: AsyncIterable<any>;
readonly socket: WebSocket;
}

export function connect(url: string, options?: Options): SocketStream;
Expand Down

0 comments on commit a03b034

Please sign in to comment.