Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/commands/mysql.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
<yellow>{$cli_name} {$subcommand}</yellow>
HELP;

echo colorize( $help );

return;
}

Expand Down
55 changes: 55 additions & 0 deletions tests/Cli/AirplaneModeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace StellarWP\Slic\Test\Cli;

class AirplaneModeTest extends BaseTestCase {

public function test_airplane_mode_on(): void {
$this->setUpPluginsDir();

$output = $this->slicExec( 'airplane-mode on', $this->dockerMockEnv() );

$this->assertStringContainsString(
'Airplane mode plugin installed',
$output,
'Turning airplane mode on should confirm the plugin was installed.'
);
$this->assertStringNotContainsString(
'Error',
$output,
'Turning airplane mode on should not produce errors.'
);
}

public function test_airplane_mode_off(): void {
$this->setUpPluginsDir();

$output = $this->slicExec( 'airplane-mode off', $this->dockerMockEnv() );

$this->assertStringContainsString(
'Airplane mode plugin removed',
$output,
'Turning airplane mode off should confirm the plugin was removed.'
);
$this->assertStringNotContainsString(
'Error',
$output,
'Turning airplane mode off should not produce errors.'
);
}

public function test_airplane_mode_help_shows_usage(): void {
$output = $this->slicExec( 'airplane-mode help' );

$this->assertStringContainsString(
'USAGE',
$output,
'Running airplane-mode help should display usage information.'
);
$this->assertStringContainsString(
'SUMMARY',
$output,
'Running airplane-mode help should display a summary.'
);
}
}
111 changes: 107 additions & 4 deletions tests/Cli/BaseTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,27 @@ abstract class BaseTestCase extends TestCase {
private array $createdStackIds = [];

private static string $dockerMockBin = '';
private static string $gitMockDir = '';
private string $slicCacheDir = '';

/**
* Temporary directories created during the test, removed in tearDown.
*
* @var string[]
*/
private array $tempDirs = [];

public static function setUpBeforeClass(): void {
parent::setUpBeforeClass();
self::$dockerMockBin = dirname( __DIR__ ) . '/_support/bin/docker-mock';
self::$gitMockDir = dirname( __DIR__ ) . '/_support/bin/git-mock-dir';
}

public function setUp(): void {
parent::setUp();
$this->initialDir = getcwd();
$this->slicCacheDir = sys_get_temp_dir() . '/slic-test-cache-' . uniqid( '', true );
mkdir( $this->slicCacheDir . '/completions', 0777, true );
}

public function tearDown(): void {
Expand All @@ -39,19 +51,46 @@ public function tearDown(): void {
);
}

// Remove the temporary cache directory.
if ( $this->slicCacheDir !== '' && is_dir( $this->slicCacheDir ) ) {
$this->removeDirectory( $this->slicCacheDir );
}

// Remove any temporary directories created during the test.
foreach ( $this->tempDirs as $dir ) {
if ( is_dir( $dir ) ) {
$this->removeDirectory( $dir );
}
}

parent::tearDown();
}

/**
* Execute a slic command and return the output.
*
* When `$stdin` is provided, `proc_open()` is used to pipe the content to the process's stdin
* and `SLIC_INTERACTIVE` is set to `'1'`. This path is intended for small payloads only; for
* commands that produce more than ~64 KB of combined output, the sequential read of stdout then
* stderr could deadlock.
*
* @param string $command The command to execute, escaped if required.
* @param array<string,string> $env Optional environment variables to set for the command.
* @param string|null $stdin Optional stdin content to pipe to the process.
*
* @return string The command output.
* @return string The command output (stdout and stderr merged).
*/
protected function slicExec( string $command, array $env = [] ): string {
protected function slicExec( string $command, array $env = [], ?string $stdin = null ): string {
$env['NO_COLOR'] = '1';
$env['SLIC_INTERACTIVE'] = $stdin !== null ? '1' : '0';
// Provide a dummy SSH_AUTH_SOCK to prevent setup_id() from exiting in CI.
if ( ! isset( $env['SSH_AUTH_SOCK'] ) && empty( getenv( 'SSH_AUTH_SOCK' ) ) ) {
$env['SSH_AUTH_SOCK'] = '/tmp/fake-ssh-agent.sock';
}
// Use a temporary cache directory to avoid polluting the real cache.
if ( ! isset( $env['SLIC_CACHE_DIR'] ) && $this->slicCacheDir !== '' ) {
$env['SLIC_CACHE_DIR'] = $this->slicCacheDir;
}

$envString = '';
foreach ( $env as $key => $value ) {
Expand All @@ -60,8 +99,33 @@ protected function slicExec( string $command, array $env = [] ): string {

$commandString = $envString . 'php ' . escapeshellarg( dirname( __DIR__, 2 ) . '/slic.php' ) . ' ' . $command;

// Redirect stderr to stdout to capture all output.
return (string) shell_exec( $commandString . ' 2>&1' );
if ( $stdin !== null ) {
$descriptors = [
0 => [ 'pipe', 'r' ],
1 => [ 'pipe', 'w' ],
];
// Redirect stderr to stdout so output ordering matches the shell_exec path.
$process = proc_open( $commandString . ' 2>&1', $descriptors, $pipes );

if ( ! is_resource( $process ) ) {
$this->fail( "proc_open() failed for command: $commandString" );
}

$written = fwrite( $pipes[0], $stdin );
if ( $written === false || $written < strlen( $stdin ) ) {
fclose( $pipes[0] );
$this->fail( "fwrite() to stdin pipe failed or wrote only $written of " . strlen( $stdin ) . " bytes for command: $commandString" );
}
fclose( $pipes[0] );
$output = stream_get_contents( $pipes[1] );
fclose( $pipes[1] );
proc_close( $process );

return (string) $output;
}

// Close stdin to prevent interactive prompts from blocking, and redirect stderr to stdout.
return (string) shell_exec( $commandString . ' </dev/null 2>&1' );
}

/**
Expand All @@ -76,6 +140,19 @@ protected function dockerMockEnv(): array {
];
}

/**
* Returns env vars that replace git with a mock that always fails.
*
* Prevents real git clone calls from hanging on SSH authentication prompts.
*
* @return array<string,string>
*/
protected function gitMockEnv(): array {
return [
'PATH' => self::$gitMockDir . ':' . getenv( 'PATH' ),
];
}

/**
* Creates a temporary plugins directory with a plugin, chdirs into it, and runs `slic here`.
*
Expand All @@ -97,4 +174,30 @@ protected function setUpPluginsDir( string $pluginName = 'test-plugin' ): string

return $pluginsDir;
}

/**
* Creates a temporary directory that is automatically cleaned up in tearDown.
*
* @return string The absolute path to the created directory.
*/
protected function createTempDir(): string {
$dir = sys_get_temp_dir() . '/slic-test-' . uniqid( '', true );
mkdir( $dir, 0777, true );
$this->tempDirs[] = $dir;

return $dir;
}

private function removeDirectory( string $dir ): void {
$items = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator( $dir, \FilesystemIterator::SKIP_DOTS ),
\RecursiveIteratorIterator::CHILD_FIRST
);

foreach ( $items as $item ) {
$item->isDir() ? rmdir( $item->getPathname() ) : unlink( $item->getPathname() );
}

rmdir( $dir );
}
}
66 changes: 66 additions & 0 deletions tests/Cli/BuildPromptTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace StellarWP\Slic\Test\Cli;

class BuildPromptTest extends BaseTestCase {

public function test_build_prompt_on(): void {
$this->setUpPluginsDir();

$output = $this->slicExec( 'build-prompt on' );

$this->assertStringContainsString(
'Build Prompt status: on',
$output,
'Turning build-prompt on should confirm activation.'
);
}

public function test_build_prompt_off(): void {
$this->setUpPluginsDir();

$output = $this->slicExec( 'build-prompt off' );

$this->assertStringContainsString(
'Build Prompt status: off',
$output,
'Turning build-prompt off should confirm deactivation.'
);
}

public function test_build_prompt_status(): void {
$this->setUpPluginsDir();

// Set to on, then check status.
$this->slicExec( 'build-prompt on' );
$output = $this->slicExec( 'build-prompt status' );

$this->assertStringContainsString(
'Interactive status is: on',
$output,
'Status should report on after enabling build-prompt.'
);

// Set to off, then check status.
$this->slicExec( 'build-prompt off' );
$output = $this->slicExec( 'build-prompt status' );

$this->assertStringContainsString(
'Interactive status is: off',
$output,
'Status should report off after disabling build-prompt.'
);
}

public function test_build_prompt_default_state(): void {
$this->setUpPluginsDir();

$output = $this->slicExec( 'build-prompt status' );

$this->assertStringContainsString(
'Interactive status is: off',
$output,
'The default build-prompt state should be off.'
);
}
}
30 changes: 30 additions & 0 deletions tests/Cli/BuildStackTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace StellarWP\Slic\Test\Cli;

class BuildStackTest extends BaseTestCase {

public function test_build_stack_builds_all(): void {
$this->setUpPluginsDir();

$output = $this->slicExec( 'build-stack', $this->dockerMockEnv() );

$this->assertStringNotContainsString(
'Error',
$output,
'The build-stack command should not produce error output.'
);
}

public function test_build_stack_specific_service(): void {
$this->setUpPluginsDir();

$output = $this->slicExec( 'build-stack wordpress', $this->dockerMockEnv() );

$this->assertStringNotContainsString(
'Error',
$output,
'The build-stack command for a specific service should not produce error output.'
);
}
}
70 changes: 70 additions & 0 deletions tests/Cli/BuildSubdirTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

namespace StellarWP\Slic\Test\Cli;

class BuildSubdirTest extends BaseTestCase {

public function test_build_subdir_on(): void {
$this->setUpPluginsDir();

$this->slicExec( 'build-subdir off' );

$output = $this->slicExec( 'build-subdir on' );

$this->assertStringContainsString(
'Build Sub-directories status: on',
$output,
'Turning build-subdir on should confirm the on status.'
);
}

public function test_build_subdir_off(): void {
$this->setUpPluginsDir();

$this->slicExec( 'build-subdir on' );

$output = $this->slicExec( 'build-subdir off' );

$this->assertStringContainsString(
'Build Sub-directories status: off',
$output,
'Turning build-subdir off should confirm the off status.'
);
}

public function test_build_subdir_status(): void {
$this->setUpPluginsDir();

$this->slicExec( 'build-subdir on' );

$output = $this->slicExec( 'build-subdir status' );

$this->assertStringContainsString(
'Sub-directories build status is: on',
$output,
'The status subcommand should report the current on state.'
);

$this->slicExec( 'build-subdir off' );

$output = $this->slicExec( 'build-subdir status' );

$this->assertStringContainsString(
'Sub-directories build status is: off',
$output,
'The status subcommand should report the current off state.'
);
}

public function test_build_subdir_default_state(): void {
$this->setUpPluginsDir();

$output = $this->slicExec( 'build-subdir status' );

$this->assertMatchesRegularExpression(
'/Sub-directories build status is: (on|off)/',
$output,
'The default state should be reported as on or off.'
);
}
}
Loading
Loading