Skip to content

Commit

Permalink
feat(app): add network interface collection to system-info (#5764)
Browse files Browse the repository at this point in the history
Closes #5397
  • Loading branch information
mcous authored May 29, 2020
1 parent ce484cb commit 7d64efa
Show file tree
Hide file tree
Showing 21 changed files with 913 additions and 256 deletions.
93 changes: 73 additions & 20 deletions app-shell/src/system-info/__tests__/dispatch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as SystemInfo from '@opentrons/app/src/system-info'
import { uiInitialized } from '@opentrons/app/src/shell'
import * as OS from '../../os'
import * as UsbDevices from '../usb-devices'
import * as NetworkInterfaces from '../network-interfaces'
import { registerSystemInfo } from '..'

import type {
Expand All @@ -14,8 +15,15 @@ import type {
UsbDeviceMonitorOptions,
} from '../usb-devices'

import type {
NetworkInterface,
NetworkInterfaceMonitor,
NetworkInterfaceMonitorOptions,
} from '../network-interfaces'

jest.mock('../../os')
jest.mock('../usb-devices')
jest.mock('../network-interfaces')

const createUsbDeviceMonitor: JestMockFn<
[UsbDeviceMonitorOptions | void],
Expand All @@ -25,15 +33,23 @@ const createUsbDeviceMonitor: JestMockFn<
const getWindowsDriverVersion: JestMockFn<[Device], any> =
UsbDevices.getWindowsDriverVersion

const getActiveInterfaces: JestMockFn<[], Array<NetworkInterface>> =
NetworkInterfaces.getActiveInterfaces

const createNetworkInterfaceMonitor: JestMockFn<
[NetworkInterfaceMonitorOptions],
NetworkInterfaceMonitor
> = NetworkInterfaces.createNetworkInterfaceMonitor

const isWindows: JestMockFn<[], boolean> = OS.isWindows

const flush = () => new Promise(resolve => setTimeout(resolve, 0))

describe('app-shell::system-info module action tests', () => {
const dispatch = jest.fn()
const getAllDevices: JestMockFn<[], any> = jest.fn()
const stop = jest.fn()
const monitor: $Shape<UsbDeviceMonitor> = { getAllDevices, stop }
const usbMonitor: UsbDeviceMonitor = { getAllDevices, stop: jest.fn() }
const ifaceMonitor: NetworkInterfaceMonitor = { stop: jest.fn() }
const { windowsDriverVersion: _, ...notRealtek } = Fixtures.mockUsbDevice
const realtek0 = { ...notRealtek, manufacturer: 'Realtek' }
const realtek1 = { ...notRealtek, manufacturer: 'realtek' }
Expand All @@ -42,19 +58,29 @@ describe('app-shell::system-info module action tests', () => {
beforeEach(() => {
handler = registerSystemInfo(dispatch)
isWindows.mockReturnValue(false)
createUsbDeviceMonitor.mockReturnValue(monitor)
createUsbDeviceMonitor.mockReturnValue(usbMonitor)
createNetworkInterfaceMonitor.mockReturnValue(ifaceMonitor)
getAllDevices.mockResolvedValue([realtek0])
getActiveInterfaces.mockReturnValue([
Fixtures.mockNetworkInterface,
Fixtures.mockNetworkInterfaceV6,
])
})

afterEach(() => {
jest.resetAllMocks()
})

it('sends initial USB device list on shell:UI_INITIALIZED', () => {
it('sends initial USB device and network list on shell:UI_INITIALIZED', () => {
handler(uiInitialized())

return flush().then(() => {
expect(dispatch).toHaveBeenCalledWith(SystemInfo.initialized([realtek0]))
expect(dispatch).toHaveBeenCalledWith(
SystemInfo.initialized(
[realtek0],
[Fixtures.mockNetworkInterface, Fixtures.mockNetworkInterfaceV6]
)
)
expect(getWindowsDriverVersion).toHaveBeenCalledTimes(0)
})
})
Expand All @@ -65,16 +91,17 @@ describe('app-shell::system-info module action tests', () => {

return flush().then(() => {
expect(createUsbDeviceMonitor).toHaveBeenCalledTimes(1)
expect(dispatch).toHaveBeenCalledTimes(1)
expect(createNetworkInterfaceMonitor).toHaveBeenCalledTimes(1)
expect(dispatch).toHaveBeenCalledTimes(2)
})
})

it('sends systemInfo:USB_DEVICE_ADDED when device added', () => {
handler(uiInitialized())
const monitorOptions = createUsbDeviceMonitor.mock.calls[0][0]
const usbMonitorOptions = createUsbDeviceMonitor.mock.calls[0][0]

expect(monitorOptions?.onDeviceAdd).toEqual(expect.any(Function))
const onDeviceAdd = monitorOptions?.onDeviceAdd ?? noop
expect(usbMonitorOptions?.onDeviceAdd).toEqual(expect.any(Function))
const onDeviceAdd = usbMonitorOptions?.onDeviceAdd ?? noop
onDeviceAdd(realtek0)

return flush().then(() => {
Expand All @@ -85,10 +112,10 @@ describe('app-shell::system-info module action tests', () => {

it('sends systemInfo:USB_DEVICE_REMOVED when device removed', () => {
handler(uiInitialized())
const monitorOptions = createUsbDeviceMonitor.mock.calls[0][0]
const usbMonitorOptions = createUsbDeviceMonitor.mock.calls[0][0]

expect(monitorOptions?.onDeviceRemove).toEqual(expect.any(Function))
const onDeviceRemove = monitorOptions?.onDeviceRemove ?? noop
expect(usbMonitorOptions?.onDeviceRemove).toEqual(expect.any(Function))
const onDeviceRemove = usbMonitorOptions?.onDeviceRemove ?? noop
onDeviceRemove(realtek0)

return flush().then(() => {
Expand All @@ -98,6 +125,28 @@ describe('app-shell::system-info module action tests', () => {
})
})

it('sends systemInfo:NETWORK_INTERFACES_CHANGED when ifaces change', () => {
handler(uiInitialized())
const ifaceMonitorOpts = createNetworkInterfaceMonitor.mock.calls[0][0]

expect(ifaceMonitorOpts.onInterfaceChange).toEqual(expect.any(Function))
const { onInterfaceChange } = ifaceMonitorOpts

onInterfaceChange([
Fixtures.mockNetworkInterface,
Fixtures.mockNetworkInterfaceV6,
])

return flush().then(() => {
expect(dispatch).toHaveBeenCalledWith(
SystemInfo.networkInterfacesChanged([
Fixtures.mockNetworkInterface,
Fixtures.mockNetworkInterfaceV6,
])
)
})
})

it('stops monitoring on app quit', () => {
handler(uiInitialized())

Expand All @@ -107,7 +156,8 @@ describe('app-shell::system-info module action tests', () => {

expect(typeof appQuitHandler).toBe('function')
appQuitHandler()
expect(monitor.stop).toHaveBeenCalled()
expect(usbMonitor.stop).toHaveBeenCalled()
expect(ifaceMonitor.stop).toHaveBeenCalled()
})

describe('on windows', () => {
Expand All @@ -125,20 +175,23 @@ describe('app-shell::system-info module action tests', () => {
expect(getWindowsDriverVersion).toHaveBeenCalledWith(realtek1)

expect(dispatch).toHaveBeenCalledWith(
SystemInfo.initialized([
{ ...realtek0, windowsDriverVersion: '1.2.3' },
notRealtek,
{ ...realtek1, windowsDriverVersion: '1.2.3' },
])
SystemInfo.initialized(
[
{ ...realtek0, windowsDriverVersion: '1.2.3' },
notRealtek,
{ ...realtek1, windowsDriverVersion: '1.2.3' },
],
[Fixtures.mockNetworkInterface, Fixtures.mockNetworkInterfaceV6]
)
)
})
})

it('should add Windows driver versions to Realtek devices on add', () => {
getAllDevices.mockResolvedValue([])
handler(uiInitialized())
const monitorOptions = createUsbDeviceMonitor.mock.calls[0][0]
const onDeviceAdd = monitorOptions?.onDeviceAdd ?? noop
const usbMonitorOptions = createUsbDeviceMonitor.mock.calls[0][0]
const onDeviceAdd = usbMonitorOptions?.onDeviceAdd ?? noop
onDeviceAdd(realtek0)

return flush().then(() => {
Expand Down
135 changes: 135 additions & 0 deletions app-shell/src/system-info/__tests__/network-interfaces.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// @flow
import os from 'os'
import noop from 'lodash/noop'

import {
getActiveInterfaces,
createNetworkInterfaceMonitor,
} from '../network-interfaces'

jest.mock('os')

const networkInterfaces: JestMockFn<
[],
{ [ifName: string]: Array<os$NetIFAddr>, ... }
> = os.networkInterfaces

const mockV4 = {
address: '192.168.1.17',
netmask: '255.255.255.0',
family: 'IPv4',
mac: 'f8:ff:c2:46:59:80',
internal: false,
cidr: '192.168.1.17/24',
}

const mockV6 = {
address: 'fe80::8e0:61a3:8bde:7385',
netmask: 'ffff:ffff:ffff:ffff::',
family: 'IPv6',
mac: 'f8:ff:c2:46:59:80',
internal: false,
cidr: 'fe80::8e0:61a3:8bde:7385/64',
scopeid: 6,
}

describe('system-info::network-interfaces', () => {
beforeEach(() => {
jest.useFakeTimers()
})

afterEach(() => {
jest.resetAllMocks()
jest.clearAllTimers()
jest.useRealTimers()
})

it('should return external network interfaces', () => {
networkInterfaces.mockReturnValue({
en0: [mockV4, mockV6],
en1: [mockV6],
lo0: [{ ...mockV4, internal: true }, { ...mockV6, internal: true }],
})

expect(getActiveInterfaces()).toEqual([
{ name: 'en0', ...mockV4 },
{ name: 'en0', ...mockV6 },
{ name: 'en1', ...mockV6 },
])
})

it('should be able to poll the attached network interfaces', () => {
networkInterfaces.mockReturnValue({})

const monitor = createNetworkInterfaceMonitor({
pollInterval: 30000,
onInterfaceChange: noop,
})

expect(networkInterfaces).toHaveBeenCalledTimes(1)
jest.advanceTimersByTime(30000)
expect(networkInterfaces).toHaveBeenCalledTimes(2)
jest.advanceTimersByTime(30000)
expect(networkInterfaces).toHaveBeenCalledTimes(3)

monitor.stop()
jest.advanceTimersByTime(30000)
expect(networkInterfaces).toHaveBeenCalledTimes(3)
})

it('should be able to signal interface changes', () => {
const handleInterfaceChange = jest.fn()

networkInterfaces.mockReturnValue({})

createNetworkInterfaceMonitor({
pollInterval: 30000,
onInterfaceChange: handleInterfaceChange,
})

networkInterfaces.mockReturnValueOnce({
en0: [mockV4, mockV6],
})
jest.advanceTimersByTime(30000)
expect(handleInterfaceChange).toHaveBeenCalledWith([
{ name: 'en0', ...mockV4 },
{ name: 'en0', ...mockV6 },
])
handleInterfaceChange.mockClear()

networkInterfaces.mockReturnValueOnce({
en0: [mockV4, mockV6],
})
jest.advanceTimersByTime(30000)
expect(handleInterfaceChange).toHaveBeenCalledTimes(0)
handleInterfaceChange.mockClear()

networkInterfaces.mockReturnValueOnce({
en0: [mockV4, mockV6],
en1: [mockV4],
})
jest.advanceTimersByTime(30000)
expect(handleInterfaceChange).toHaveBeenCalledWith([
{ name: 'en0', ...mockV4 },
{ name: 'en0', ...mockV6 },
{ name: 'en1', ...mockV4 },
])
handleInterfaceChange.mockClear()
})

it('should be able to stop monitoring interface changes', () => {
const handleInterfaceChange = jest.fn()

networkInterfaces.mockReturnValue({})

const monitor = createNetworkInterfaceMonitor({
pollInterval: 30000,
onInterfaceChange: handleInterfaceChange,
})

networkInterfaces.mockReturnValueOnce({ en0: [mockV4] })
monitor.stop()
jest.advanceTimersByTime(30000)
expect(handleInterfaceChange).toHaveBeenCalledTimes(0)
})
})
Loading

0 comments on commit 7d64efa

Please sign in to comment.