test(02-01): add failing test stubs for all four generators
- rclone-conf.test.ts: azureblob/s3/s3-compatible fixtures, error cases - intune-install.test.ts: configPath, includeInstall, UTF-8 write, credential safety - intune-detection.test.ts: configPath, exit codes, stdout only (no STDERR contamination) - rmm-script.test.ts: configPath, includeInstall, ErrorActionPreference=Stop
This commit is contained in:
@@ -0,0 +1,80 @@
|
|||||||
|
// src/generators/intune-detection.test.ts
|
||||||
|
// Tests for buildIntuneDetection — Intune PowerShell detection script generator.
|
||||||
|
// RED: imports will fail until Plan 02-03 creates intune-detection.ts.
|
||||||
|
|
||||||
|
import { buildIntuneDetection } from './intune-detection';
|
||||||
|
import { INITIAL_STATE } from '../store/types';
|
||||||
|
import type { WizardState } from '../store/types';
|
||||||
|
|
||||||
|
// --- Fixtures ---
|
||||||
|
|
||||||
|
const machineWideState: WizardState = {
|
||||||
|
...INITIAL_STATE,
|
||||||
|
remote: {
|
||||||
|
name: 'my-azure',
|
||||||
|
backendType: 'azureblob',
|
||||||
|
params: { account: 'mystorageaccount', key: 'BASE64KEY', sas_url: '' },
|
||||||
|
},
|
||||||
|
deployment: {
|
||||||
|
...INITIAL_STATE.deployment,
|
||||||
|
configPath: 'machine-wide',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const userProfileState: WizardState = {
|
||||||
|
...machineWideState,
|
||||||
|
deployment: {
|
||||||
|
...machineWideState.deployment,
|
||||||
|
configPath: 'user-profile',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Tests ---
|
||||||
|
|
||||||
|
describe('buildIntuneDetection — config path', () => {
|
||||||
|
it('machine-wide: contains C:\\ProgramData\\rclone', () => {
|
||||||
|
const output = buildIntuneDetection(machineWideState);
|
||||||
|
expect(output).toContain('C:\\ProgramData\\rclone');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('user-profile: contains %APPDATA%\\rclone', () => {
|
||||||
|
const output = buildIntuneDetection(userProfileState);
|
||||||
|
expect(output).toContain('%APPDATA%\\rclone');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('buildIntuneDetection — exit codes', () => {
|
||||||
|
it('contains exit 0 (detected / compliant)', () => {
|
||||||
|
const output = buildIntuneDetection(machineWideState);
|
||||||
|
expect(output).toContain('exit 0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('contains exit 1 (not detected / non-compliant)', () => {
|
||||||
|
const output = buildIntuneDetection(machineWideState);
|
||||||
|
expect(output).toContain('exit 1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('buildIntuneDetection — STDOUT signal', () => {
|
||||||
|
it('uses Write-Output for STDOUT signal (Intune reads stdout, not stderr)', () => {
|
||||||
|
const output = buildIntuneDetection(machineWideState);
|
||||||
|
expect(output).toContain('Write-Output');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('buildIntuneDetection — no STDERR contamination', () => {
|
||||||
|
it('does NOT contain $ErrorActionPreference (avoids STDERR contamination in detection scripts)', () => {
|
||||||
|
const output = buildIntuneDetection(machineWideState);
|
||||||
|
expect(output).not.toContain('$ErrorActionPreference');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does NOT contain Write-Error', () => {
|
||||||
|
const output = buildIntuneDetection(machineWideState);
|
||||||
|
expect(output).not.toContain('Write-Error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does NOT contain Write-Host', () => {
|
||||||
|
const output = buildIntuneDetection(machineWideState);
|
||||||
|
expect(output).not.toContain('Write-Host');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
// src/generators/intune-install.test.ts
|
||||||
|
// Tests for buildIntuneInstall — Intune PowerShell install script generator.
|
||||||
|
// RED: imports will fail until Plan 02-02 creates intune-install.ts.
|
||||||
|
|
||||||
|
import { buildIntuneInstall } from './intune-install';
|
||||||
|
import { INITIAL_STATE } from '../store/types';
|
||||||
|
import type { WizardState } from '../store/types';
|
||||||
|
|
||||||
|
// --- Fixtures ---
|
||||||
|
|
||||||
|
const machineWideState: WizardState = {
|
||||||
|
...INITIAL_STATE,
|
||||||
|
remote: {
|
||||||
|
name: 'my-azure',
|
||||||
|
backendType: 'azureblob',
|
||||||
|
params: { account: 'mystorageaccount', key: 'BASE64KEY', sas_url: '' },
|
||||||
|
},
|
||||||
|
deployment: {
|
||||||
|
...INITIAL_STATE.deployment,
|
||||||
|
configPath: 'machine-wide',
|
||||||
|
includeInstall: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const userProfileState: WizardState = {
|
||||||
|
...machineWideState,
|
||||||
|
deployment: {
|
||||||
|
...machineWideState.deployment,
|
||||||
|
configPath: 'user-profile',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const withInstallState: WizardState = {
|
||||||
|
...machineWideState,
|
||||||
|
deployment: {
|
||||||
|
...machineWideState.deployment,
|
||||||
|
includeInstall: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const withoutInstallState: WizardState = {
|
||||||
|
...machineWideState,
|
||||||
|
deployment: {
|
||||||
|
...machineWideState.deployment,
|
||||||
|
includeInstall: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Tests ---
|
||||||
|
|
||||||
|
describe('buildIntuneInstall — config path', () => {
|
||||||
|
it('machine-wide: contains C:\\ProgramData\\rclone', () => {
|
||||||
|
const output = buildIntuneInstall(machineWideState);
|
||||||
|
expect(output).toContain('C:\\ProgramData\\rclone');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('user-profile: contains %APPDATA%\\rclone', () => {
|
||||||
|
const output = buildIntuneInstall(userProfileState);
|
||||||
|
expect(output).toContain('%APPDATA%\\rclone');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('buildIntuneInstall — rclone install download', () => {
|
||||||
|
it('includeInstall=true: output contains rclone download URL', () => {
|
||||||
|
const output = buildIntuneInstall(withInstallState);
|
||||||
|
expect(output).toContain('https://downloads.rclone.org/rclone-current-windows-amd64.zip');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includeInstall=false: output does NOT contain downloads.rclone.org', () => {
|
||||||
|
const output = buildIntuneInstall(withoutInstallState);
|
||||||
|
expect(output).not.toContain('downloads.rclone.org');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('buildIntuneInstall — script structure', () => {
|
||||||
|
it('uses [System.IO.File]::WriteAllText for UTF-8 no-BOM write', () => {
|
||||||
|
const output = buildIntuneInstall(machineWideState);
|
||||||
|
expect(output).toContain('[System.IO.File]::WriteAllText');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('exits with exit 0 on success', () => {
|
||||||
|
const output = buildIntuneInstall(machineWideState);
|
||||||
|
expect(output).toContain('exit 0');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('buildIntuneInstall — credential safety', () => {
|
||||||
|
it('does NOT interpolate credential variables inside config content (no $account inside here-string)', () => {
|
||||||
|
const output = buildIntuneInstall(machineWideState);
|
||||||
|
// The config content block should contain literal values, not PS variable interpolation
|
||||||
|
// Find the WriteAllText call and verify the content arg has no $account or $key variables
|
||||||
|
// We check that there is no $account or $key inside the config block
|
||||||
|
expect(output).not.toMatch(/\$account\b/);
|
||||||
|
expect(output).not.toMatch(/\$key\b/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
// src/generators/rclone-conf.test.ts
|
||||||
|
// Tests for buildRcloneConf — rclone INI config generator.
|
||||||
|
// RED: imports will fail until Plan 02-02 creates rclone-conf.ts.
|
||||||
|
|
||||||
|
import { buildRcloneConf } from './rclone-conf';
|
||||||
|
import { INITIAL_STATE } from '../store/types';
|
||||||
|
import type { WizardState } from '../store/types';
|
||||||
|
|
||||||
|
// --- Fixtures ---
|
||||||
|
|
||||||
|
const azureState: WizardState = {
|
||||||
|
...INITIAL_STATE,
|
||||||
|
remote: {
|
||||||
|
name: 'my-azure',
|
||||||
|
backendType: 'azureblob',
|
||||||
|
params: { account: 'mystorageaccount', key: 'BASE64KEY', sas_url: '' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const s3State: WizardState = {
|
||||||
|
...INITIAL_STATE,
|
||||||
|
remote: {
|
||||||
|
name: 'my-s3',
|
||||||
|
backendType: 's3',
|
||||||
|
params: {
|
||||||
|
provider: 'AWS',
|
||||||
|
access_key_id: 'AKID',
|
||||||
|
secret_access_key: 'SKEY',
|
||||||
|
region: 'us-east-1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const s3CompatibleState: WizardState = {
|
||||||
|
...INITIAL_STATE,
|
||||||
|
remote: {
|
||||||
|
name: 'my-wasabi',
|
||||||
|
backendType: 's3-compatible',
|
||||||
|
params: {
|
||||||
|
provider: 'Other',
|
||||||
|
access_key_id: 'WCID',
|
||||||
|
secret_access_key: 'WSKEY',
|
||||||
|
endpoint: 'https://s3.wasabisys.com',
|
||||||
|
region: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Tests ---
|
||||||
|
|
||||||
|
describe('buildRcloneConf — azureblob', () => {
|
||||||
|
it('contains the remote section header', () => {
|
||||||
|
const output = buildRcloneConf(azureState);
|
||||||
|
expect(output).toContain('[my-azure]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('contains type = azureblob', () => {
|
||||||
|
const output = buildRcloneConf(azureState);
|
||||||
|
expect(output).toContain('type = azureblob');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('contains account value', () => {
|
||||||
|
const output = buildRcloneConf(azureState);
|
||||||
|
expect(output).toContain('account = mystorageaccount');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('contains key value', () => {
|
||||||
|
const output = buildRcloneConf(azureState);
|
||||||
|
expect(output).toContain('key = BASE64KEY');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('omits sas_url when empty', () => {
|
||||||
|
const output = buildRcloneConf(azureState);
|
||||||
|
expect(output).not.toContain('sas_url');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('buildRcloneConf — s3', () => {
|
||||||
|
it('contains the remote section header', () => {
|
||||||
|
const output = buildRcloneConf(s3State);
|
||||||
|
expect(output).toContain('[my-s3]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('contains type = s3', () => {
|
||||||
|
const output = buildRcloneConf(s3State);
|
||||||
|
expect(output).toContain('type = s3');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('contains provider = AWS', () => {
|
||||||
|
const output = buildRcloneConf(s3State);
|
||||||
|
expect(output).toContain('provider = AWS');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('contains access_key_id', () => {
|
||||||
|
const output = buildRcloneConf(s3State);
|
||||||
|
expect(output).toContain('access_key_id = AKID');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('contains secret_access_key', () => {
|
||||||
|
const output = buildRcloneConf(s3State);
|
||||||
|
expect(output).toContain('secret_access_key = SKEY');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('contains region', () => {
|
||||||
|
const output = buildRcloneConf(s3State);
|
||||||
|
expect(output).toContain('region = us-east-1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('buildRcloneConf — s3-compatible', () => {
|
||||||
|
it('uses type = s3 (not type = s3-compatible)', () => {
|
||||||
|
const output = buildRcloneConf(s3CompatibleState);
|
||||||
|
expect(output).toContain('type = s3');
|
||||||
|
expect(output).not.toContain('type = s3-compatible');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('contains provider = Other', () => {
|
||||||
|
const output = buildRcloneConf(s3CompatibleState);
|
||||||
|
expect(output).toContain('provider = Other');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('contains endpoint', () => {
|
||||||
|
const output = buildRcloneConf(s3CompatibleState);
|
||||||
|
expect(output).toContain('endpoint = https://s3.wasabisys.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('omits region when empty', () => {
|
||||||
|
const output = buildRcloneConf(s3CompatibleState);
|
||||||
|
expect(output).not.toContain('region');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('buildRcloneConf — error cases', () => {
|
||||||
|
it('throws when backendType is null', () => {
|
||||||
|
const state: WizardState = {
|
||||||
|
...INITIAL_STATE,
|
||||||
|
remote: { name: 'test', backendType: null, params: {} },
|
||||||
|
};
|
||||||
|
expect(() => buildRcloneConf(state)).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when remote name is empty string', () => {
|
||||||
|
const state: WizardState = {
|
||||||
|
...INITIAL_STATE,
|
||||||
|
remote: { name: '', backendType: 'azureblob', params: { account: 'acc', key: 'k', sas_url: '' } },
|
||||||
|
};
|
||||||
|
expect(() => buildRcloneConf(state)).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
// src/generators/rmm-script.test.ts
|
||||||
|
// Tests for buildRmmScript — RMM (NinjaRMM / Datto etc.) PowerShell script generator.
|
||||||
|
// RED: imports will fail until Plan 02-04 creates rmm-script.ts.
|
||||||
|
|
||||||
|
import { buildRmmScript } from './rmm-script';
|
||||||
|
import { INITIAL_STATE } from '../store/types';
|
||||||
|
import type { WizardState } from '../store/types';
|
||||||
|
|
||||||
|
// --- Fixtures ---
|
||||||
|
|
||||||
|
const machineWideState: WizardState = {
|
||||||
|
...INITIAL_STATE,
|
||||||
|
remote: {
|
||||||
|
name: 'my-azure',
|
||||||
|
backendType: 'azureblob',
|
||||||
|
params: { account: 'mystorageaccount', key: 'BASE64KEY', sas_url: '' },
|
||||||
|
},
|
||||||
|
deployment: {
|
||||||
|
...INITIAL_STATE.deployment,
|
||||||
|
configPath: 'machine-wide',
|
||||||
|
includeInstall: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const userProfileState: WizardState = {
|
||||||
|
...machineWideState,
|
||||||
|
deployment: {
|
||||||
|
...machineWideState.deployment,
|
||||||
|
configPath: 'user-profile',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const withInstallState: WizardState = {
|
||||||
|
...machineWideState,
|
||||||
|
deployment: {
|
||||||
|
...machineWideState.deployment,
|
||||||
|
includeInstall: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const withoutInstallState: WizardState = {
|
||||||
|
...machineWideState,
|
||||||
|
deployment: {
|
||||||
|
...machineWideState.deployment,
|
||||||
|
includeInstall: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Tests ---
|
||||||
|
|
||||||
|
describe('buildRmmScript — config path', () => {
|
||||||
|
it('machine-wide: contains C:\\ProgramData\\rclone', () => {
|
||||||
|
const output = buildRmmScript(machineWideState);
|
||||||
|
expect(output).toContain('C:\\ProgramData\\rclone');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('user-profile: contains %APPDATA%\\rclone', () => {
|
||||||
|
const output = buildRmmScript(userProfileState);
|
||||||
|
expect(output).toContain('%APPDATA%\\rclone');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('buildRmmScript — rclone install download', () => {
|
||||||
|
it('includeInstall=true: output contains rclone download URL', () => {
|
||||||
|
const output = buildRmmScript(withInstallState);
|
||||||
|
expect(output).toContain('https://downloads.rclone.org/rclone-current-windows-amd64.zip');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includeInstall=false: output does NOT contain downloads.rclone.org', () => {
|
||||||
|
const output = buildRmmScript(withoutInstallState);
|
||||||
|
expect(output).not.toContain('downloads.rclone.org');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('buildRmmScript — script structure', () => {
|
||||||
|
it("uses \$ErrorActionPreference = 'Stop' (RMM scripts should halt on error)", () => {
|
||||||
|
const output = buildRmmScript(machineWideState);
|
||||||
|
expect(output).toContain("$ErrorActionPreference = 'Stop'");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses [System.IO.File]::WriteAllText for UTF-8 no-BOM write', () => {
|
||||||
|
const output = buildRmmScript(machineWideState);
|
||||||
|
expect(output).toContain('[System.IO.File]::WriteAllText');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user