balena-supervisor/test/integration/config/splash-image.spec.ts

442 lines
12 KiB
TypeScript
Raw Normal View History

import { promises as fs } from 'fs';
import { testfs, TestFs } from 'mocha-pod';
import { expect } from 'chai';
import * as hostUtils from '~/lib/host-utils';
import { SplashImage } from '~/src/config/backends/splash-image';
import log from '~/lib/supervisor-console';
describe('config/splash-image', () => {
const backend = new SplashImage();
const defaultLogo =
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/wQDLA+84AAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg==';
const logo =
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=';
const uri = `data:image/png;base64,${logo}`;
describe('initialise', () => {
let tfs: TestFs.Enabled;
beforeEach(async () => {
tfs = await testfs(
{
[hostUtils.pathOnBoot('splash/balena-logo.png')]: Buffer.from(
defaultLogo,
'base64',
),
},
{ cleanup: [hostUtils.pathOnBoot('splash/balena-logo-default.png')] },
).enable();
});
afterEach(async () => {
await tfs.restore();
});
it('should make a copy of the existing boot image on initialise if not yet created', async () => {
await expect(
fs.access(hostUtils.pathOnBoot('splash/balena-logo-default.png')),
'logo copy should not exist before first initialization',
).to.be.rejected;
// Do the initialization
await backend.initialise();
// The copy should exist after the test and equal defaultLogo
await expect(
fs.access(hostUtils.pathOnBoot('splash/balena-logo-default.png')),
'logo copy should exist after initialization',
).to.not.be.rejected;
expect(
await fs.readFile(
hostUtils.pathOnBoot('splash/balena-logo-default.png'),
'base64',
),
).to.equal(defaultLogo);
});
it('should skip initialization if the default image already exists', async () => {
// Write a different logo as default
await fs.writeFile(
hostUtils.pathOnBoot('splash/balena-logo-default.png'),
Buffer.from(logo, 'base64'),
);
// Do the initialization
await backend.initialise();
// The copy should exist after the test and be equal to logo (it should not) have
// been changed
await expect(
fs.access(hostUtils.pathOnBoot('splash/balena-logo-default.png')),
'logo copy still exists after initialization',
).to.not.be.rejected;
expect(
await fs.readFile(
hostUtils.pathOnBoot('splash/balena-logo-default.png'),
'base64',
),
).to.equal(logo);
});
it('should warn about failed initialization if there is no default image on the device', async () => {
await fs.unlink(hostUtils.pathOnBoot('splash/balena-logo.png'));
// Do the initialization
await backend.initialise();
expect(log.warn).to.be.calledOnceWith(
'Could not initialise splash image backend',
);
});
});
describe('getBootConfig', () => {
it('should return an empty object if the current logo matches the default logo', async () => {
const tfs = await testfs({
// Both the logo and the copy resolve to the same value
[hostUtils.pathOnBoot('splash/resin-logo.png')]: Buffer.from(
logo,
'base64',
),
[hostUtils.pathOnBoot('splash/balena-logo-default.png')]: Buffer.from(
logo,
'base64',
),
}).enable();
// The default logo resolves to the same value as the current logo
expect(await backend.getBootConfig()).to.deep.equal({});
await tfs.restore();
});
it('should read the splash image from resin-logo.png if available', async () => {
const tfs = await testfs({
// resin logo and balena-logo-default are different which means the current logo
// is a custom image
[hostUtils.pathOnBoot('splash/resin-logo.png')]: Buffer.from(
logo,
'base64',
),
[hostUtils.pathOnBoot('splash/balena-logo-default.png')]: Buffer.from(
defaultLogo,
'base64',
),
}).enable();
expect(await backend.getBootConfig()).to.deep.equal({
image: uri,
});
await tfs.restore();
});
it('should read the splash image from balena-logo.png if available', async () => {
const tfs = await testfs({
// balena-logo and balena-logo-default are different which means the current logo
// is a custom image
[hostUtils.pathOnBoot('splash/balena-logo.png')]: Buffer.from(
logo,
'base64',
),
[hostUtils.pathOnBoot('splash/balena-logo-default.png')]: Buffer.from(
defaultLogo,
'base64',
),
}).enable();
expect(await backend.getBootConfig()).to.deep.equal({
image: uri,
});
await tfs.restore();
});
it('should read the splash image from balena-logo.png even if resin-logo.png exists', async () => {
const tfs = await testfs({
[hostUtils.pathOnBoot('splash/balena-logo.png')]: Buffer.from(
logo,
'base64',
),
// resin-logo has the same value as default, but it is ignored since balena-logo exists
[hostUtils.pathOnBoot('splash/resin-logo.png')]: Buffer.from(
defaultLogo,
'base64',
),
[hostUtils.pathOnBoot('splash/balena-logo-default.png')]: Buffer.from(
defaultLogo,
'base64',
),
}).enable();
expect(await backend.getBootConfig()).to.deep.equal({
// balena-logo is the value read
image: uri,
});
await tfs.restore();
});
it('should catch readDir errors', async () => {
// Remove the directory before the tests to cause getBootConfig to fail
await fs
.rm(hostUtils.pathOnBoot('splash'), { recursive: true, force: true })
.catch(() => {
/* noop */
});
expect(await backend.getBootConfig()).to.deep.equal({});
expect(log.warn).to.be.calledOnceWith('Failed to read splash image:');
});
it('should catch readFile errors', async () => {
await expect(
fs.access(hostUtils.pathOnBoot('splash/balena-logo.png')),
'logo does not exist before getting boot config',
).to.be.rejected;
await expect(
fs.access(hostUtils.pathOnBoot('splash/resin-logo.png')),
'logo does not exist before getting boot config',
).to.be.rejected;
expect(await backend.getBootConfig()).to.deep.equal({});
expect(log.warn).to.be.calledOnceWith('Failed to read splash image:');
});
});
describe('setBootConfig', () => {
it('should write the given image to resin-logo.png if set', async () => {
const tfs = await testfs({
[hostUtils.pathOnBoot('splash/resin-logo.png')]: Buffer.from(
defaultLogo,
'base64',
),
[hostUtils.pathOnBoot('splash/balena-logo-default.png')]: Buffer.from(
defaultLogo,
'base64',
),
}).enable();
await backend.setBootConfig({ image: uri });
// Since resin-logo already exists we use that to write
expect(
await fs.readFile(
hostUtils.pathOnBoot('splash/resin-logo.png'),
'base64',
),
).to.equal(logo);
await tfs.restore();
});
it('should write the given image to balena-logo.png if set', async () => {
const tfs = await testfs({
[hostUtils.pathOnBoot('splash/resin-logo.png')]: Buffer.from(
defaultLogo,
'base64',
),
[hostUtils.pathOnBoot('splash/balena-logo.png')]: Buffer.from(
defaultLogo,
'base64',
),
[hostUtils.pathOnBoot('splash/balena-logo-default.png')]: Buffer.from(
defaultLogo,
'base64',
),
}).enable();
await backend.setBootConfig({ image: uri });
// Resin logo should not have changed
expect(
await fs.readFile(
hostUtils.pathOnBoot('splash/resin-logo.png'),
'base64',
),
).to.equal(defaultLogo);
// balena-logo is used as the correct location
expect(
await fs.readFile(
hostUtils.pathOnBoot('splash/balena-logo.png'),
'base64',
),
).to.equal(logo);
await tfs.restore();
});
it('should accept just a base64 as an image', async () => {
const tfs = await testfs({
[hostUtils.pathOnBoot('splash/balena-logo.png')]: Buffer.from(
defaultLogo,
'base64',
),
[hostUtils.pathOnBoot('splash/balena-logo-default.png')]: Buffer.from(
defaultLogo,
'base64',
),
}).enable();
// We use just the base64 image instead of the data uri
await backend.setBootConfig({ image: logo });
// The file should have changed
expect(
await fs.readFile(
hostUtils.pathOnBoot('splash/balena-logo.png'),
'base64',
),
).to.equal(logo);
await tfs.restore();
});
it('should restore balena-logo.png if image arg is unset', async () => {
const tfs = await testfs({
[hostUtils.pathOnBoot('splash/balena-logo.png')]: Buffer.from(
logo,
'base64',
),
[hostUtils.pathOnBoot('splash/balena-logo-default.png')]: Buffer.from(
defaultLogo,
'base64',
),
}).enable();
// setBootConfig with an empty object should delete the iamge
await backend.setBootConfig({});
// The default should have been reverted to the default value
expect(
await fs.readFile(
hostUtils.pathOnBoot('splash/balena-logo.png'),
'base64',
),
).to.equal(defaultLogo);
await tfs.restore();
});
it('should restore resin-logo.png if image arg is unset', async () => {
const tfs = await testfs({
[hostUtils.pathOnBoot('splash/resin-logo.png')]: Buffer.from(
logo,
'base64',
),
[hostUtils.pathOnBoot('splash/balena-logo-default.png')]: Buffer.from(
defaultLogo,
'base64',
),
}).enable();
// setBootConfig with an empty object should delete the iamge
await backend.setBootConfig({});
// The default should have been reverted to the default value
expect(
await fs.readFile(
hostUtils.pathOnBoot('splash/resin-logo.png'),
'base64',
),
).to.equal(defaultLogo);
await tfs.restore();
});
it('should restore balena-logo.png if image arg is empty', async () => {
const tfs = await testfs({
[hostUtils.pathOnBoot('splash/balena-logo.png')]: Buffer.from(
logo,
'base64',
),
[hostUtils.pathOnBoot('splash/balena-logo-default.png')]: Buffer.from(
defaultLogo,
'base64',
),
}).enable();
// setBootConfig with an empty image should also restore the default
await backend.setBootConfig({ image: '' });
// The default should have been reverted to the default value
expect(
await fs.readFile(
hostUtils.pathOnBoot('splash/balena-logo.png'),
'base64',
),
).to.equal(defaultLogo);
await tfs.restore();
});
// TODO: note that ignoring a value on the backend will cause the supervisor
// to go into a loop trying to apply target state, as the current will never match
// the target. The image needs to be validated cloud side, and as a last line of defense,
// when receiving the target state
it('should ignore the value if arg is not a valid base64 string', async () => {
const tfs = await testfs({
[hostUtils.pathOnBoot('splash/balena-logo.png')]: Buffer.from(
defaultLogo,
'base64',
),
[hostUtils.pathOnBoot('splash/balena-logo-default.png')]: Buffer.from(
defaultLogo,
'base64',
),
}).enable();
// We pass something that is not an image
await backend.setBootConfig({ image: 'somestring' });
// The file should NOT have changed
expect(
await fs.readFile(
hostUtils.pathOnBoot('splash/balena-logo.png'),
'base64',
),
).to.equal(defaultLogo);
await tfs.restore();
});
it('should ignore the value if image is not a valid PNG file', async () => {
const tfs = await testfs({
[hostUtils.pathOnBoot('splash/balena-logo.png')]: Buffer.from(
defaultLogo,
'base64',
),
[hostUtils.pathOnBoot('splash/balena-logo-default.png')]: Buffer.from(
defaultLogo,
'base64',
),
}).enable();
// We pass something that is not a PNG
await backend.setBootConfig({ image: 'aGVsbG8=' });
// The file should NOT have changed
expect(
await fs.readFile(
hostUtils.pathOnBoot('splash/balena-logo.png'),
'base64',
),
).to.equal(defaultLogo);
await tfs.restore();
});
});
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;
});
});
});