diff --git a/web/storagenode/src/app/store/modules/node.ts b/web/storagenode/src/app/store/modules/node.ts index 7c3323a5f..ccd9d9221 100644 --- a/web/storagenode/src/app/store/modules/node.ts +++ b/web/storagenode/src/app/store/modules/node.ts @@ -91,16 +91,12 @@ export function makeNodeModule(api: SNOApi) { state.satellites = nodeInfo.satellites || []; - state.info.status = StatusOffline; - state.info.startedAt = nodeInfo.startedAt; state.info.lastPinged = nodeInfo.lastPinged; const minutesPassed = Duration.difference(new Date(), new Date(nodeInfo.lastPinged)) / millisecondsInSecond / secondsInMinute; - if (minutesPassed < statusThreshHoldMinutes) { - state.info.status = StatusOnline; - } + state.info.status = minutesPassed < statusThreshHoldMinutes ? StatusOnline : StatusOffline; }, [SELECT_SATELLITE](state: any, satelliteInfo: Satellite): void { const selectedSatellite = state.satellites.find(satellite => satelliteInfo.id === satellite.id); @@ -122,7 +118,7 @@ export function makeNodeModule(api: SNOApi) { }, [SELECT_ALL_SATELLITES](state: any, satelliteInfo: Satellites): void { state.selectedSatellite = { - id: null, + id: '', disqualified: null, joinDate: satelliteInfo.joinDate, }; diff --git a/web/storagenode/src/storagenode/satellite.ts b/web/storagenode/src/storagenode/satellite.ts index ea691a467..1be793d1b 100644 --- a/web/storagenode/src/storagenode/satellite.ts +++ b/web/storagenode/src/storagenode/satellite.ts @@ -6,18 +6,18 @@ */ export class Satellite { public constructor( - public id: string, - public storageDaily: Stamp[], - public bandwidthDaily: BandwidthUsed[], - public egressDaily: EgressUsed[], - public ingressDaily: IngressUsed[], - public storageSummary: number, - public bandwidthSummary: number, - public egressSummary: number, - public ingressSummary: number, - public audit: Metric, - public uptime: Metric, - public joinDate: Date, + public id: string = '', + public storageDaily: Stamp[] = [], + public bandwidthDaily: BandwidthUsed[] = [], + public egressDaily: EgressUsed[] = [], + public ingressDaily: IngressUsed[] = [], + public storageSummary: number = 0, + public bandwidthSummary: number = 0, + public egressSummary: number = 0, + public ingressSummary: number = 0, + public audit: Metric = new Metric(), + public uptime: Metric = new Metric(), + public joinDate: Date = new Date(), ) {} } @@ -28,7 +28,7 @@ export class Stamp { public atRestTotal: number; public intervalStart: Date; - public constructor(atRestTotal: number, intervalStart: Date) { + public constructor(atRestTotal: number = 0, intervalStart: Date = new Date()) { this.atRestTotal = atRestTotal; this.intervalStart = intervalStart; } @@ -52,11 +52,11 @@ export class Stamp { */ export class Metric { public constructor( - public totalCount: number, - public successCount: number, - public alpha: number, - public beta: number, - public score: number, + public totalCount: number = 0, + public successCount: number = 0, + public alpha: number = 0, + public beta: number = 0, + public score: number = 0, ) {} } @@ -65,9 +65,9 @@ export class Metric { */ export class Egress { public constructor( - public audit: number, - public repair: number, - public usage: number, + public audit: number = 0, + public repair: number = 0, + public usage: number = 0, ) {} } @@ -76,8 +76,8 @@ export class Egress { */ export class Ingress { public constructor( - public repair: number, - public usage: number, + public repair: number = 0, + public usage: number = 0, ) {} } @@ -120,7 +120,8 @@ export class BandwidthUsed { export class EgressUsed { public constructor( public egress: Egress, - public intervalStart: Date) {} + public intervalStart: Date, + ) {} /** * Used to summarize all egress usage data @@ -150,7 +151,8 @@ export class EgressUsed { export class IngressUsed { public constructor( public ingress: Ingress, - public intervalStart: Date) {} + public intervalStart: Date, + ) {} /** * Used to summarize all ingress usage data @@ -179,14 +181,14 @@ export class IngressUsed { */ export class Satellites { public constructor( - public storageDaily: Stamp[], - public bandwidthDaily: BandwidthUsed[], - public egressDaily: EgressUsed[], - public ingressDaily: IngressUsed[], - public storageSummary: number, - public bandwidthSummary: number, - public egressSummary: number, - public ingressSummary: number, - public joinDate: Date, + public storageDaily: Stamp[] = [], + public bandwidthDaily: BandwidthUsed[] = [], + public egressDaily: EgressUsed[] = [], + public ingressDaily: IngressUsed[] = [], + public storageSummary: number = 0, + public bandwidthSummary: number = 0, + public egressSummary: number = 0, + public ingressSummary: number = 0, + public joinDate: Date = new Date(), ) {} } diff --git a/web/storagenode/tests/unit/store/node.spec.ts b/web/storagenode/tests/unit/store/node.spec.ts new file mode 100644 index 000000000..5c64b85e9 --- /dev/null +++ b/web/storagenode/tests/unit/store/node.spec.ts @@ -0,0 +1,328 @@ +// Copyright (C) 2020 Storj Labs, Inc. +// See LICENSE for copying information. + +import Vuex from 'vuex'; + +import { makeNodeModule, NODE_ACTIONS, NODE_MUTATIONS, StatusOnline } from '@/app/store/modules/node'; +import { SNOApi } from '@/storagenode/api/storagenode'; +import { + BandwidthInfo, + Dashboard, + DiskSpaceInfo, + SatelliteInfo, +} from '@/storagenode/dashboard'; +import { + BandwidthUsed, + Egress, + EgressUsed, + Ingress, + IngressUsed, + Metric, + Satellite, + Satellites, + Stamp, +} from '@/storagenode/satellite'; +import { createLocalVue } from '@vue/test-utils'; + +const Vue = createLocalVue(); + +const nodeApi = new SNOApi(); +const nodeModule = makeNodeModule(nodeApi); + +Vue.use(Vuex); + +const store = new Vuex.Store({ modules: { node: nodeModule } }); + +const state = store.state as any; + +describe('mutations', () => { + beforeEach(() => { + createLocalVue().use(Vuex); + }); + + it('set dashboard info', () => { + const dashboardInfo = new Dashboard( + '1', + '2', + [ + new SatelliteInfo('3', 'url1', null, null), + new SatelliteInfo('4', 'url2', new Date(2020, 1, 1), new Date(2020, 0, 1)), + ], + new DiskSpaceInfo(99, 100), + new BandwidthInfo(50), + new Date(), + new Date(2019, 3, 1), + '0.1.1', + '0.2.2', + false, + ); + + store.commit(NODE_MUTATIONS.POPULATE_STORE, dashboardInfo); + + expect(state.node.info.id).toBe(dashboardInfo.nodeID); + expect(state.node.utilization.bandwidth.used).toBe(dashboardInfo.bandwidth.used); + expect(state.node.utilization.diskSpace.remaining).toBe(dashboardInfo.diskSpace.remaining); + expect(state.node.satellites.length).toBe(dashboardInfo.satellites.length); + expect(state.node.disqualifiedSatellites.length).toBe(1); + expect(state.node.suspendedSatellites.length).toBe(1); + expect(state.node.info.status).toBe(StatusOnline); + }); + + it('selects single satellite', () => { + const satelliteInfo = new Satellite( + '3', + [new Stamp()], + [], + [], + [], + 111, + 222, + 50, + 70, + new Metric(1, 1, 1, 0, 1), + new Metric(2, 1, 1, 0, 1), + new Date(2019, 3, 1), + ); + + store.commit(NODE_MUTATIONS.SELECT_SATELLITE, satelliteInfo); + + expect(state.node.selectedSatellite.id).toBe(satelliteInfo.id); + expect(state.node.checks.audit).toBe(100); + expect(state.node.checks.uptime).toBe(50); + }); + + it('don`t selects wrong satellite', () => { + const satelliteInfo = new Satellite(); + + store.commit(NODE_MUTATIONS.SELECT_SATELLITE, satelliteInfo); + + expect(state.node.selectedSatellite.id).toBe('3'); + }); + + it('selects all satellites', () => { + const satelliteInfo = new Satellites(); + + store.commit(NODE_MUTATIONS.SELECT_ALL_SATELLITES, satelliteInfo); + + expect(state.node.selectedSatellite.id).toBe(''); + }); + + it('sets daily data', () => { + const satelliteInfo = new Satellite( + '3', + [new Stamp(), new Stamp()], + [ + new BandwidthUsed( + new Egress(), + new Ingress(), + new Date(), + ), + new BandwidthUsed( + new Egress(), + new Ingress(), + new Date(), + ), + ], + [ + new EgressUsed(new Egress(), new Date()), + new EgressUsed(new Egress(), new Date()), + ], + [ + new IngressUsed(new Ingress(), new Date()), + new IngressUsed(new Ingress(), new Date()), + ], + 111, + 222, + 50, + 70, + new Metric(1, 1, 1, 0, 1), + new Metric(2, 1, 1, 0, 1), + new Date(2019, 3, 1), + ); + + store.commit(NODE_MUTATIONS.SET_DAILY_DATA, satelliteInfo); + + expect(state.node.bandwidthChartData.length).toBe(2); + expect(state.node.egressChartData.length).toBe(2); + expect(state.node.ingressChartData.length).toBe(2); + expect(state.node.storageChartData.length).toBe(2); + expect(state.node.bandwidthSummary).toBe(satelliteInfo.bandwidthSummary); + expect(state.node.egressSummary).toBe(satelliteInfo.egressSummary); + expect(state.node.ingressSummary).toBe(satelliteInfo.ingressSummary); + expect(state.node.storageSummary).toBe(satelliteInfo.storageSummary); + }); +}); + +describe('actions', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('throws error on failed node info fetch', async () => { + jest.spyOn(nodeApi, 'dashboard').mockImplementation(() => { throw new Error(); }); + + try { + await store.dispatch(NODE_ACTIONS.GET_NODE_INFO); + expect(true).toBe(false); + } catch (error) { + expect(state.node.info.id).toBe('1'); + } + }); + + it('success get node info', async () => { + jest.spyOn(nodeApi, 'dashboard').mockReturnValue( + Promise.resolve( + new Dashboard( + '1', + '2', + [ + new SatelliteInfo('3', 'url1', null, null), + new SatelliteInfo('4', 'url2', new Date(2020, 1, 1), new Date(2020, 0, 1)), + ], + new DiskSpaceInfo(99, 100), + new BandwidthInfo(50), + new Date(), + new Date(2019, 3, 1), + '0.1.1', + '0.2.2', + false, + ), + ), + ); + + await store.dispatch(NODE_ACTIONS.GET_NODE_INFO); + + expect(state.node.info.id).toBe('1'); + expect(state.node.utilization.bandwidth.used).toBe(50); + expect(state.node.utilization.diskSpace.remaining).toBe(1); + expect(state.node.satellites.length).toBe(2); + expect(state.node.disqualifiedSatellites.length).toBe(1); + expect(state.node.suspendedSatellites.length).toBe(1); + expect(state.node.info.status).toBe(StatusOnline); + }); + + it('fetch satellite info throws error on api call fail', async () => { + jest.spyOn(nodeApi, 'satellite').mockImplementation(() => { throw new Error(); }); + + try { + await store.dispatch(NODE_ACTIONS.SELECT_SATELLITE, '3'); + expect(true).toBe(false); + } catch (e) { + expect(state.node.selectedSatellite.id).toBe(''); + } + }); + + it('success fetch single satellite info', async () => { + jest.spyOn(nodeApi, 'satellite').mockReturnValue( + Promise.resolve( + new Satellite( + '4', + [new Stamp(), new Stamp()], + [ + new BandwidthUsed( + new Egress(), + new Ingress(), + new Date(), + ), + ], + [ + new EgressUsed(new Egress(), new Date()), + ], + [ + new IngressUsed(new Ingress(), new Date()), + ], + 1111, + 2221, + 501, + 701, + new Metric(1, 1, 1, 0, 1), + new Metric(2, 1, 1, 0, 1), + new Date(2019, 3, 1), + ), + ), + ); + + await store.dispatch(NODE_ACTIONS.SELECT_SATELLITE, '4'); + + expect(state.node.selectedSatellite.id).toBe('4'); + expect(state.node.bandwidthChartData.length).toBe(1); + expect(state.node.egressChartData.length).toBe(1); + expect(state.node.ingressChartData.length).toBe(1); + expect(state.node.storageChartData.length).toBe(2); + expect(state.node.bandwidthSummary).toBe(2221); + expect(state.node.egressSummary).toBe(501); + expect(state.node.ingressSummary).toBe(701); + expect(state.node.storageSummary).toBe(1111); + }); + + it('fetch all satellites info throws error on api call fail', async () => { + jest.spyOn(nodeApi, 'satellites').mockImplementation( + () => { throw new Error(); }, + ); + + try { + await store.dispatch(NODE_ACTIONS.SELECT_SATELLITE); + expect(true).toBe(false); + } catch (e) { + expect(state.node.selectedSatellite.id).toBe('4'); + } + }); + + it('success fetch all satellites info', async () => { + jest.spyOn(nodeApi, 'satellites').mockReturnValue( + Promise.resolve(new Satellites()), + ); + + await store.dispatch(NODE_ACTIONS.SELECT_SATELLITE); + + expect(state.node.selectedSatellite.id).toBe(''); + }); +}); + +describe('getters', () => { + it('getter monthsOnNetwork returns correct value', () => { + const dashboardInfo = new Dashboard( + '1', + '2', + [ + new SatelliteInfo('3', 'url1', null, null), + new SatelliteInfo('4', 'url2', new Date(2020, 1, 1), new Date(2020, 0, 1)), + ], + new DiskSpaceInfo(99, 100), + new BandwidthInfo(50), + new Date(), + new Date(2019, 3, 1), + '0.1.1', + '0.2.2', + false, + ); + const now = new Date(); + const testJoinAt = new Date(now.getTime() - 1000000); + + const satelliteInfo = new Satellite( + '3', + [new Stamp()], + [], + [], + [], + 111, + 222, + 50, + 70, + new Metric(1, 1, 1, 0, 1), + new Metric(2, 1, 1, 0, 1), + testJoinAt, + ); + + store.commit(NODE_MUTATIONS.POPULATE_STORE, dashboardInfo); + store.commit(NODE_MUTATIONS.SELECT_SATELLITE, satelliteInfo); + + expect(store.getters.monthsOnNetwork).toBe(1); + + satelliteInfo.joinDate = new Date(testJoinAt.getTime() - 9e9); + + store.commit(NODE_MUTATIONS.SELECT_SATELLITE, satelliteInfo); + + expect(store.getters.monthsOnNetwork).toBe(4); + }); +});