Files
BetterDesk/web-nodejs/tests/clientBuilderService.test.js
UNITRONIX d57fe3dc49 feat(generator): add generic agent releases and QR activation
Adds local verification and distribution of signed generic incoming-only agent releases, mobile-friendly QR activation, and the supporting generator interfaces. Includes the current staged console, documentation, localization, and builder changes.\n\nThanks: INSOLVE (Honorary); Marco Jakobs (@jacotec); MyNameisStitch (@MyNameisStitch); Redspin (@playerumpknow)
2026-10-11 00:43:26 +02:00

155 lines
6.8 KiB
JavaScript

'use strict';
const fs = require('fs');
const os = require('os');
const path = require('path');
describe('clientBuilderService', () => {
let dataDir;
let builder;
beforeEach(() => {
jest.resetModules();
dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'betterdesk-client-builder-'));
jest.doMock('../config/config', () => ({ dataDir }));
builder = require('../services/clientBuilderService');
});
afterEach(() => {
jest.dontMock('../config/config');
fs.rmSync(dataDir, { recursive: true, force: true });
});
test('creates stable, distinct generation keys', () => {
const fingerprint = 'a'.repeat(64);
const first = builder.generationKey(fingerprint, '1'.repeat(40));
const same = builder.generationKey(fingerprint, '1'.repeat(40));
const changed = builder.generationKey(fingerprint, '2'.repeat(40));
expect(first).toMatch(/^[a-f0-9]{64}$/);
expect(same).toBe(first);
expect(changed).not.toBe(first);
});
test('rejects unsafe repository and ref values', () => {
expect(() => builder.normalizeRepo('https://github.com/example/repo')).toThrow(
'betterdesk_client_repo_invalid'
);
expect(() => builder.normalizeRepo('example/repo/extra')).toThrow(
'betterdesk_client_repo_invalid'
);
expect(() => builder.clientRef('master\r\nTOKEN=leak')).toThrow(
'betterdesk_client_ref_invalid'
);
});
test('writes state atomically outside the project checkout', async () => {
const state = await builder.writeState({
repo: 'UNITRONIX/BetterDesk-Client',
ref: 'master',
installedCommit: 'a'.repeat(40),
status: 'ready',
});
expect(state.installedCommit).toBe('a'.repeat(40));
expect(fs.existsSync(builder.statePath())).toBe(true);
expect(builder.statePath()).toContain(path.join('modules', 'betterdesk-client-builder'));
expect(fs.readdirSync(builder.dataRoot()).some((name) => name.endsWith('.tmp'))).toBe(false);
});
test('generates an instance signing seed without exposing it in state', () => {
const key = builder.ensureSigningKeySync();
const seedPath = path.join(
dataDir,
'modules',
'betterdesk-support-generator',
'custom-client-signing.seed'
);
expect(key.generated).toBe(true);
expect(key.publicKey).toMatch(/^[A-Za-z0-9+/]+=*$/);
expect(fs.readFileSync(seedPath, 'utf8').trim()).toMatch(/^[A-Za-z0-9+/]+=*$/);
expect(JSON.stringify(builder.readState())).not.toContain(key.seed);
});
test('loads the installer environment without overriding explicit process values', () => {
const envDir = fs.mkdtempSync(path.join(os.tmpdir(), 'betterdesk-build-env-'));
const envFile = path.join(envDir, 'build.env');
const previousFile = process.env.BETTERDESK_BUILD_ENV_FILE;
const previousCargo = process.env.BETTERDESK_CLIENT_CARGO;
const previousVcpkg = process.env.VCPKG_ROOT;
try {
fs.writeFileSync(envFile, [
'BETTERDESK_CLIENT_CARGO=/opt/cargo/bin/cargo',
'VCPKG_ROOT=/opt/vcpkg',
].join('\n'));
process.env.BETTERDESK_BUILD_ENV_FILE = envFile;
process.env.BETTERDESK_CLIENT_CARGO = '/explicit/cargo';
delete process.env.VCPKG_ROOT;
jest.resetModules();
const reloaded = require('../services/clientBuilderService');
expect(process.env.BETTERDESK_CLIENT_CARGO).toBe('/explicit/cargo');
expect(process.env.VCPKG_ROOT).toBe('/opt/vcpkg');
expect(reloaded.getBuildCapabilities).toBeDefined();
} finally {
if (previousFile === undefined) delete process.env.BETTERDESK_BUILD_ENV_FILE;
else process.env.BETTERDESK_BUILD_ENV_FILE = previousFile;
if (previousCargo === undefined) delete process.env.BETTERDESK_CLIENT_CARGO;
else process.env.BETTERDESK_CLIENT_CARGO = previousCargo;
if (previousVcpkg === undefined) delete process.env.VCPKG_ROOT;
else process.env.VCPKG_ROOT = previousVcpkg;
fs.rmSync(envDir, { recursive: true, force: true });
}
});
test('reports only explicitly configured targets for a remote provider', () => {
const previousCommand = process.env.BETTERDESK_CLIENT_BUILD_COMMAND;
const previousTargets = process.env.BETTERDESK_CLIENT_PROVIDER_TARGETS_JSON;
try {
process.env.BETTERDESK_CLIENT_BUILD_COMMAND = 'trusted-remote-provider';
process.env.BETTERDESK_CLIENT_PROVIDER_TARGETS_JSON = JSON.stringify({
targets: [
{ platform: 'windows', arch: 'x64', format: 'portable' },
{ platform: 'linux', arch: 'arm64', format: 'deb' },
{ platform: 'not-supported', arch: 'x64', format: 'portable' },
],
});
expect(builder.getBuildCapabilities()).toMatchObject({
mode: 'remote',
provider: 'command',
buildReady: true,
targets: [
{ platform: 'windows', arch: 'x64', format: 'portable' },
{ platform: 'linux', arch: 'arm64', format: 'deb' },
],
});
} finally {
if (previousCommand === undefined) delete process.env.BETTERDESK_CLIENT_BUILD_COMMAND;
else process.env.BETTERDESK_CLIENT_BUILD_COMMAND = previousCommand;
if (previousTargets === undefined) delete process.env.BETTERDESK_CLIENT_PROVIDER_TARGETS_JSON;
else process.env.BETTERDESK_CLIENT_PROVIDER_TARGETS_JSON = previousTargets;
}
});
test('does not advertise a remote provider without target capabilities', () => {
const previousCommand = process.env.BETTERDESK_CLIENT_BUILD_COMMAND;
const previousTargets = process.env.BETTERDESK_CLIENT_PROVIDER_TARGETS_JSON;
try {
process.env.BETTERDESK_CLIENT_BUILD_COMMAND = 'trusted-remote-provider';
delete process.env.BETTERDESK_CLIENT_PROVIDER_TARGETS_JSON;
expect(builder.getBuildCapabilities()).toMatchObject({
mode: 'remote',
buildReady: false,
targets: [],
reason: 'provider_targets_missing',
});
} finally {
if (previousCommand === undefined) delete process.env.BETTERDESK_CLIENT_BUILD_COMMAND;
else process.env.BETTERDESK_CLIENT_BUILD_COMMAND = previousCommand;
if (previousTargets === undefined) delete process.env.BETTERDESK_CLIENT_PROVIDER_TARGETS_JSON;
else process.env.BETTERDESK_CLIENT_PROVIDER_TARGETS_JSON = previousTargets;
}
});
});