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-test
import { 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.ts
npm run prepublishOnly
The 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