A comprehensive testing framework for Codify plugins. This package provides utilities to test the complete lifecycle of Codify plugins including initialization, validation, planning, applying, importing, and destroying resources.
npm install --save-dev @codifycli/plugin-testimport { PluginTester } from '@codifycli/plugin-test';
import { ResourceConfig, ResourceOs } from '@codifycli/schemas';
// Define the resources you want to test
const configs: ResourceConfig[] = [
{
type: 'homebrew',
os: [ResourceOs.MACOS]
}
];
// Run a full lifecycle test
await PluginTester.fullTest('/path/to/your/plugin/index.js', configs, {
validatePlan: (plans) => {
// Assert that the plan is correct
expect(plans[0].operation).toBe('create');
},
validateApply: (plans) => {
// Verify the resource was created successfully
}
});- Complete Lifecycle Testing: Test all plugin operations (initialize, validate, plan, apply, import, destroy)
- IPC Communication: Spawns plugins as child processes and communicates via IPC, just like the Codify CLI
- OS Filtering: Automatically filters and runs tests based on OS compatibility
- Interactive Command Execution: Execute commands in PTY for realistic testing
- Security Testing: Validates that plugins properly request elevated permissions
- Modify Testing: Test resource modification scenarios
The main API for testing plugins.
Runs a complete plugin lifecycle test including validate, plan, apply, import, modify (optional), and destroy.
Parameters:
pluginPath(string): Absolute path to the plugin entry pointconfigs(ResourceConfig[]): Array of resource configurations to testoptions(object, optional):skipUninstall(boolean): Skip the destroy phaseskipImport(boolean): Skip the import phasevalidatePlan(function): Callback to validate plan resultsvalidateApply(function): Callback to validate apply resultsvalidateDestroy(function): Callback to validate destroy resultsvalidateImport(function): Callback to validate import resultstestModify(object): Configuration for testing modificationsmodifiedConfigs(ResourceConfig[]): Modified configurationsvalidateModify(function): Callback to validate modify results
Example:
await PluginTester.fullTest('/path/to/plugin', configs, {
validatePlan: (plans) => {
expect(plans).toHaveLength(1);
expect(plans[0].operation).toBe('create');
},
testModify: {
modifiedConfigs: [
{ type: 'my-resource', newParam: 'updated-value' }
],
validateModify: (plans) => {
expect(plans[0].operation).toBe('modify');
}
}
});Tests only the installation flow (validate → plan → apply).
Parameters:
pluginPath(string): Absolute path to the plugin entry pointconfigs(ResourceConfig[]): Array of resource configurations to install
Example:
await PluginTester.install('/path/to/plugin', [
{ type: 'homebrew', os: [ResourceOs.MACOS] }
]);Tests the destroy operation for resources.
Parameters:
pluginPath(string): Absolute path to the plugin entry pointconfigs(ResourceConfig[]): Array of resource configurations to destroyoptions(object, optional):validateDestroy(function): Callback to validate destroy results
Example:
await PluginTester.uninstall('/path/to/plugin', configs, {
validateDestroy: (plans) => {
expect(plans[0].operation).toBe('destroy');
}
});Low-level API for managing individual plugin processes.
Example:
import { PluginProcess } from '@codifycli/plugin-test';
const plugin = new PluginProcess('/path/to/plugin');
try {
// Initialize the plugin
const initResult = await plugin.initialize();
// Validate configs
const validateResult = await plugin.validate({
configs: [{ core: { type: 'my-resource' }, parameters: {} }]
});
// Create a plan
const plan = await plugin.plan({
core: { type: 'my-resource' },
desired: { param: 'value' },
isStateful: false,
state: undefined
});
// Apply the plan
await plugin.apply({ planId: plan.planId });
} finally {
plugin.kill();
}Utility functions for common testing scenarios.
Sends an IPC message to a plugin process and waits for the response.
TestUtils.getShell(): Returns the current shell ('bash' or 'zsh')TestUtils.getPrimaryShellRc(): Returns the path to the primary shell RC fileTestUtils.getSourceCommand(): Returns the source command for the shell RC fileTestUtils.getShellCommand(command): Returns a command that sources the shell RC file firstTestUtils.getInteractiveCommand(command): Returns an interactive shell command
TestUtils.isMacOS(): Returns true if running on macOSTestUtils.isLinux(): Returns true if running on Linux
TestUtils.ensureHomebrewInstalledOnMacOs(pluginPath): Ensures Homebrew is installedTestUtils.ensureXcodeInstalledOnMacOs(pluginPath): Ensures Xcode tools are installed
Execute a command interactively in a PTY (for testing).
Parameters:
command(string): Command to executeoptions(object, optional):cwd(string): Working directoryenv(object): Environment variablesinteractive(boolean): Run in interactive mode (default: true)requiresRoot(boolean): Run with sudostdin(boolean): Enable stdin passthroughthrows(boolean): Throw on non-zero exit code
Example:
import { testSpawn } from '@codifycli/plugin-test';
const result = await testSpawn('which brew');
if (result.status === SpawnStatus.SUCCESS) {
console.log('Homebrew is installed at:', result.data);
}Tests automatically filter by OS using the os field:
const configs = [
{ type: 'homebrew', os: [ResourceOs.MACOS] }, // Only runs on macOS
{ type: 'apt', os: [ResourceOs.LINUX] }, // Only runs on Linux
{ type: 'git' } // Runs on all platforms
];
await PluginTester.fullTest('/path/to/plugin', configs);Test that your plugin properly handles resource modifications:
await PluginTester.fullTest('/path/to/plugin', [
{ type: 'my-resource', version: '1.0.0' }
], {
testModify: {
modifiedConfigs: [
{ type: 'my-resource', version: '2.0.0' }
],
validateModify: (plans) => {
expect(plans[0].operation).toBe('modify');
expect(plans[0].changes).toContain('version');
}
}
});Test multiple resources in a single test run:
const configs = [
{ type: 'homebrew', name: 'homebrew-main' },
{ type: 'homebrew-package', name: 'wget', formula: 'wget' },
{ type: 'homebrew-package', name: 'curl', formula: 'curl' }
];
await PluginTester.fullTest('/path/to/plugin', configs, {
validateApply: (plans) => {
expect(plans).toHaveLength(3);
}
});Ensure prerequisites are installed before running tests:
import { TestUtils, PluginTester } from '@codifycli/plugin-test';
// Ensure Homebrew is installed before testing Homebrew packages
await TestUtils.ensureHomebrewInstalledOnMacOs('/path/to/core-plugin');
await PluginTester.fullTest('/path/to/plugin', [
{ type: 'homebrew-package', formula: 'wget' }
]);DEBUG: Enable plugin debug mode with breakpoint at--inspect-brk=9221VITE_CODIFY_TEST_JWT: JWT token for tests that require Codify credentials
npm testnpx vitest src/test-utils.test.tsnpm run prepublishOnlyThe testing framework spawns your plugin as a child process (using Node's fork()) and communicates with it via IPC messages, exactly how the Codify CLI does. This ensures your tests accurately reflect real-world plugin behavior.
When your plugin requests elevated permissions or needs to execute commands, the framework intercepts these requests and handles them automatically, allowing you to test plugin behavior in a controlled environment.
- Node.js >= 18.0.0
- Works on macOS, Linux, and Windows
ISC