diff --git a/src/device-api/actions.ts b/src/device-api/actions.ts index 8c5764f9..a64c1e90 100644 --- a/src/device-api/actions.ts +++ b/src/device-api/actions.ts @@ -19,7 +19,7 @@ import type { Service } from '../compose/service'; import { getApp } from '../device-state/db-format'; import * as TargetState from '../api-binder/poll'; import log from '../lib/supervisor-console'; -import blink = require('../lib/blink'); +import { getBlink } from '../lib/blink'; import * as constants from '../lib/constants'; import { InternalInconsistencyError, @@ -56,7 +56,8 @@ export const runHealthchecks = async ( * - POST /v1/blink */ const DEFAULT_IDENTIFY_DURATION = 15000; -export const identify = (ms: number = DEFAULT_IDENTIFY_DURATION) => { +export const identify = async (ms: number = DEFAULT_IDENTIFY_DURATION) => { + const blink = await getBlink(); eventTracker.track('Device blink'); blink.pattern.start(); setTimeout(blink.pattern.stop, ms); diff --git a/src/device-api/index.ts b/src/device-api/index.ts index 2c99d945..7318855b 100644 --- a/src/device-api/index.ts +++ b/src/device-api/index.ts @@ -43,8 +43,8 @@ export class SupervisorAPI { this.api.use(middleware.auth); - this.api.post('/v1/blink', (_req, res) => { - actions.identify(); + this.api.post('/v1/blink', async (_req, res) => { + await actions.identify(); return res.sendStatus(200); }); diff --git a/src/lib/blink.ts b/src/lib/blink.ts index 71505ed9..f915df80 100644 --- a/src/lib/blink.ts +++ b/src/lib/blink.ts @@ -1,5 +1,18 @@ -import blinking = require('blinking'); +import blinking from 'blinking'; +import memoizee from 'memoizee'; +export type Blink = ReturnType; + +import { exists } from './fs-utils'; import { ledFile } from './constants'; -export = blinking(ledFile); +export const getBlink = memoizee( + async (): Promise => { + if (!(await exists(ledFile))) { + return blinking('/dev/null'); + } + + return blinking(ledFile); + }, + { promise: true }, +); diff --git a/src/network.ts b/src/network.ts index 037d7fc7..75f34de0 100644 --- a/src/network.ts +++ b/src/network.ts @@ -8,7 +8,7 @@ import * as constants from './lib/constants'; import { isEEXIST } from './lib/errors'; import { checkFalsey } from './lib/validation'; -import blink = require('./lib/blink'); +import { getBlink } from './lib/blink'; import log from './lib/supervisor-console'; @@ -85,6 +85,7 @@ export const startConnectivityCheck = _.once( const parsedUrl = url.parse(apiEndpoint); const port = parseInt(parsedUrl.port!, 10); + const blink = await getBlink(); customMonitor( { diff --git a/test/integration/device-api/actions.spec.ts b/test/integration/device-api/actions.spec.ts index ba9a058c..11504491 100644 --- a/test/integration/device-api/actions.spec.ts +++ b/test/integration/device-api/actions.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import type { SinonStub } from 'sinon'; -import { stub } from 'sinon'; +import { stub, spy, useFakeTimers } from 'sinon'; import Docker from 'dockerode'; import request from 'supertest'; import { setTimeout } from 'timers/promises'; @@ -17,6 +17,8 @@ import { pathOnRoot } from '~/lib/host-utils'; import { exec } from '~/lib/fs-utils'; import * as lockfile from '~/lib/lockfile'; import { cleanupDocker } from '~/test-lib/docker-helper'; +import { getBlink } from '~/lib/blink'; +import type { Blink } from '~/lib/blink'; export async function dbusSend( dest: string, @@ -1268,6 +1270,30 @@ describe('updates target state cache', () => { }); }); +describe('identifies device', () => { + let blink: Blink; + before(async () => { + blink = await getBlink(); + }); + + // This suite doesn't test that the blink submodule writes to the correct + // led file location on host. That test should be part of the blink module. + it('directs device to blink for set duration', async () => { + const blinkStartSpy = spy(blink.pattern, 'start'); + const blinkStopSpy = spy(blink.pattern, 'stop'); + const clock = useFakeTimers(); + + await actions.identify(); + expect(blinkStartSpy.callCount).to.equal(1); + clock.tick(15000); + expect(blinkStopSpy.callCount).to.equal(1); + + blinkStartSpy.restore(); + blinkStopSpy.restore(); + clock.restore(); + }); +}); + describe('patches host config', () => { // Stub external dependencies let hostConfigPatch: SinonStub; diff --git a/test/unit/device-api/actions.spec.ts b/test/unit/device-api/actions.spec.ts index e0d8ae1e..7cbeb9f7 100644 --- a/test/unit/device-api/actions.spec.ts +++ b/test/unit/device-api/actions.spec.ts @@ -1,10 +1,9 @@ import { expect } from 'chai'; import type { SinonStub } from 'sinon'; -import { spy, useFakeTimers, stub } from 'sinon'; +import { stub } from 'sinon'; import * as hostConfig from '~/src/host-config'; import * as actions from '~/src/device-api/actions'; -import blink = require('~/lib/blink'); describe('device-api/actions', () => { describe('runs healthchecks', () => { @@ -27,25 +26,6 @@ describe('device-api/actions', () => { }); }); - describe('identifies device', () => { - // This suite doesn't test that the blink submodule writes to the correct - // led file location on host. That test should be part of the blink module. - it('directs device to blink for set duration', async () => { - const blinkStartSpy = spy(blink.pattern, 'start'); - const blinkStopSpy = spy(blink.pattern, 'stop'); - const clock = useFakeTimers(); - - actions.identify(); - expect(blinkStartSpy.callCount).to.equal(1); - clock.tick(15000); - expect(blinkStopSpy.callCount).to.equal(1); - - blinkStartSpy.restore(); - blinkStopSpy.restore(); - clock.restore(); - }); - }); - describe('gets host config', () => { // Stub external dependencies // TODO: host-config module integration tests