balena-supervisor/test/43-splash-image.spec.ts
Felipe Lalanne 4aa8090a56 Add support for BALENA_HOST_SPLASH_IMAGE config
Setting this this variable to a base64 encoded string will replace the splash
image on the device by rewriting `/mnt/boot/splash/balena-logo.png`.
This will also make a copy of the default balena logo so the splash can
be restored if the variable is removed.

Change-type: minor
Signed-off-by: Felipe Lalanne <felipe@balena.io>
2021-01-06 15:11:31 -03:00

307 lines
9.5 KiB
TypeScript

import { fs, child_process } from 'mz';
import { SinonStub, stub } from 'sinon';
import { expect } from './lib/chai-config';
import * as fsUtils from '../src/lib/fs-utils';
import { SplashImage } from '../src/config/backends/splash-image';
import log from '../src/lib/supervisor-console';
describe('Splash image configuration', () => {
const backend = new SplashImage();
const defaultLogo =
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/wQDLA+84AAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg==';
const logo =
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=';
const uri = `data:image/png;base64,${logo}`;
let readDirStub: SinonStub;
let readFileStub: SinonStub;
let writeFileAtomicStub: SinonStub;
beforeEach(() => {
// Setup stubs
writeFileAtomicStub = stub(fsUtils, 'writeFileAtomic').resolves();
stub(child_process, 'exec').resolves();
readFileStub = stub(fs, 'readFile').resolves(
Buffer.from(logo, 'base64') as any,
);
readFileStub
.withArgs('test/data/mnt/boot/splash/balena-logo-default.png')
.resolves(Buffer.from(defaultLogo, 'base64') as any);
readDirStub = stub(fs, 'readdir').resolves(['balena-logo.png']);
});
afterEach(() => {
// Restore stubs
writeFileAtomicStub.restore();
(child_process.exec as SinonStub).restore();
readFileStub.restore();
readDirStub.restore();
});
describe('initialise', () => {
it('should make a copy of the existing boot image on initialise if not yet created', async () => {
stub(fs, 'exists').resolves(false);
// Do the initialization
await backend.initialise();
expect(fs.readFile).to.be.calledOnceWith(
'test/data/mnt/boot/splash/balena-logo.png',
);
// Should make a copy
expect(writeFileAtomicStub).to.be.calledOnceWith(
'test/data/mnt/boot/splash/balena-logo-default.png',
Buffer.from(logo, 'base64'),
);
(fs.exists as SinonStub).restore();
});
it('should skip initialization if the default image already exists', async () => {
stub(fs, 'exists').resolves(true);
// Do the initialization
await backend.initialise();
expect(fs.exists).to.be.calledOnceWith(
'test/data/mnt/boot/splash/balena-logo-default.png',
);
expect(fs.readFile).to.not.have.been.called;
(fs.exists as SinonStub).restore();
});
it('should fail initialization if there is no default image on the device', async () => {
stub(fs, 'exists').resolves(false);
readDirStub.resolves([]);
readFileStub.rejects();
stub(log, 'warn');
// Do the initialization
await backend.initialise();
expect(readDirStub).to.be.calledOnce;
expect(fs.readFile).to.have.been.calledOnce;
expect(log.warn).to.be.calledOnce;
(log.warn as SinonStub).restore();
});
});
describe('getBootConfig', () => {
it('should return an empty object if the current logo matches the default logo', async () => {
readDirStub.resolves(['resin-logo.png']);
// The default logo resolves to the same value as the current logo
readFileStub
.withArgs('test/data/mnt/boot/splash/balena-logo-default.png')
.resolves(logo);
expect(await backend.getBootConfig()).to.deep.equal({});
expect(readDirStub).to.be.calledOnce;
expect(readFileStub).to.be.calledTwice;
expect(readFileStub).to.be.calledWith(
'test/data/mnt/boot/splash/balena-logo-default.png',
);
expect(readFileStub).to.be.calledWith(
'test/data/mnt/boot/splash/resin-logo.png',
);
});
it('should read the splash image from resin-logo.png if available', async () => {
readDirStub.resolves(['resin-logo.png']);
expect(await backend.getBootConfig()).to.deep.equal({
image: uri,
});
expect(readDirStub).to.be.calledOnce;
expect(readFileStub).to.be.calledTwice;
expect(readFileStub).to.be.calledWith(
'test/data/mnt/boot/splash/balena-logo-default.png',
);
expect(readFileStub).to.be.calledWith(
'test/data/mnt/boot/splash/resin-logo.png',
);
});
it('should read the splash image from balena-logo.png if available', async () => {
readDirStub.resolves(['balena-logo.png']);
expect(await backend.getBootConfig()).to.deep.equal({
image: uri,
});
expect(readDirStub).to.be.calledOnce;
expect(readFileStub).to.be.calledTwice;
expect(readFileStub).to.be.calledWith(
'test/data/mnt/boot/splash/balena-logo-default.png',
);
expect(readFileStub).to.be.calledWith(
'test/data/mnt/boot/splash/balena-logo.png',
);
});
it('should read the splash image from balena-logo.png even if resin-logo.png exists', async () => {
readDirStub.resolves(['balena-logo.png', 'resin-logo.png']);
expect(await backend.getBootConfig()).to.deep.equal({
image: uri,
});
expect(readFileStub).to.be.calledTwice;
expect(readFileStub).to.be.calledWith(
'test/data/mnt/boot/splash/balena-logo-default.png',
);
expect(readFileStub).to.be.calledWith(
'test/data/mnt/boot/splash/balena-logo.png',
);
});
it('should catch readDir errors', async () => {
stub(log, 'warn');
readDirStub.rejects();
expect(await backend.getBootConfig()).to.deep.equal({});
expect(readDirStub).to.be.calledOnce;
expect(log.warn).to.be.calledOnce;
(log.warn as SinonStub).restore();
});
it('should catch readFile errors', async () => {
stub(log, 'warn');
readDirStub.resolves([]);
readFileStub.rejects();
expect(await backend.getBootConfig()).to.deep.equal({});
expect(log.warn).to.be.calledOnce;
(log.warn as SinonStub).restore();
});
});
describe('setBootConfig', () => {
it('should write the given image to resin-logo.png if set', async () => {
readDirStub.resolves(['resin-logo.png']);
await backend.setBootConfig({ image: uri });
expect(readDirStub).to.be.calledOnce;
expect(writeFileAtomicStub).to.be.calledOnceWith(
'test/data/mnt/boot/splash/resin-logo.png',
Buffer.from(logo, 'base64'),
);
});
it('should write the given image to balena-logo.png if set', async () => {
readDirStub.resolves(['balena-logo.png']);
await backend.setBootConfig({ image: uri });
expect(readDirStub).to.be.calledOnce;
expect(writeFileAtomicStub).to.be.calledOnceWith(
'test/data/mnt/boot/splash/balena-logo.png',
Buffer.from(logo, 'base64'),
);
});
it('should write the given image to balena-logo.png by default', async () => {
readDirStub.resolves([]);
await backend.setBootConfig({ image: uri });
expect(readDirStub).to.be.calledOnce;
expect(writeFileAtomicStub).to.be.calledOnceWith(
'test/data/mnt/boot/splash/balena-logo.png',
Buffer.from(logo, 'base64'),
);
});
it('should accept just a base64 as an image', async () => {
readDirStub.resolves(['balena-logo.png']);
await backend.setBootConfig({ image: logo });
expect(readDirStub).to.be.calledOnce;
expect(writeFileAtomicStub).to.be.calledOnceWith(
'test/data/mnt/boot/splash/balena-logo.png',
Buffer.from(logo, 'base64'),
);
});
it('should restore balena-logo.png if image arg is unset', async () => {
readDirStub.resolves(['balena-logo.png']);
await backend.setBootConfig({});
expect(readDirStub).to.be.calledOnce;
expect(readFileStub).to.be.calledOnceWith(
'test/data/mnt/boot/splash/balena-logo-default.png',
);
expect(writeFileAtomicStub).to.be.calledOnceWith(
'test/data/mnt/boot/splash/balena-logo.png',
Buffer.from(defaultLogo, 'base64'),
);
});
it('should restore resin-logo.png if image arg is unset', async () => {
readDirStub.resolves(['resin-logo.png']);
await backend.setBootConfig({});
expect(readDirStub).to.be.calledOnce;
expect(readFileStub).to.be.calledOnceWith(
'test/data/mnt/boot/splash/balena-logo-default.png',
);
expect(writeFileAtomicStub).to.be.calledOnceWith(
'test/data/mnt/boot/splash/resin-logo.png',
Buffer.from(defaultLogo, 'base64'),
);
});
it('should restore balena-logo.png if image arg is empty', async () => {
readDirStub.resolves(['balena-logo.png']);
await backend.setBootConfig({ image: '' });
expect(readDirStub).to.be.calledOnce;
expect(readFileStub).to.be.calledOnceWith(
'test/data/mnt/boot/splash/balena-logo-default.png',
);
expect(writeFileAtomicStub).to.be.calledOnceWith(
'test/data/mnt/boot/splash/balena-logo.png',
Buffer.from(defaultLogo, 'base64'),
);
});
it('should restore resin-logo.png if image arg is empty', async () => {
readDirStub.resolves(['resin-logo.png']);
await backend.setBootConfig({ image: '' });
expect(readDirStub).to.be.calledOnce;
expect(readFileStub).to.be.calledOnceWith(
'test/data/mnt/boot/splash/balena-logo-default.png',
);
expect(writeFileAtomicStub).to.be.calledOnceWith(
'test/data/mnt/boot/splash/resin-logo.png',
Buffer.from(defaultLogo, 'base64'),
);
});
it('should throw if arg is not a valid base64 string', async () => {
expect(backend.setBootConfig({ image: 'somestring' })).to.be.rejected;
expect(writeFileAtomicStub).to.not.be.called;
});
it('should throw if image is not a valid PNG file', async () => {
expect(backend.setBootConfig({ image: 'aGVsbG8=' })).to.be.rejected;
expect(writeFileAtomicStub).to.not.be.called;
});
});
describe('isBootConfigVar', () => {
it('Accepts any case variable names', () => {
expect(backend.isBootConfigVar('HOST_SPLASH_IMAGE')).to.be.true;
expect(backend.isBootConfigVar('HOST_SPLASH_image')).to.be.true;
expect(backend.isBootConfigVar('HOST_SPLASH_Image')).to.be.true;
expect(backend.isBootConfigVar('HOST_SPLASH_ImAgE')).to.be.true;
});
});
});