ae2860c3b5
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 6s
Generic: Repo Health / Access control (push) Successful in 9s
Universal: PR Check / Validate PR (pull_request) Failing after 10s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 22s
Universal: Auto Version Bump / Version Bump (push) Failing after 23s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 1m13s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 1m17s
Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
168 lines
5.8 KiB
PHP
168 lines
5.8 KiB
PHP
<?php
|
|
|
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace MokoStandards\Tests\Unit;
|
|
|
|
use MokoEnterprise\CliFramework;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* Unit tests for CliFramework base class.
|
|
*
|
|
* @covers \MokoEnterprise\CliFramework
|
|
*/
|
|
class CliFrameworkTest extends TestCase
|
|
{
|
|
// ── Exit code constants ──────────────────────────────────────────────
|
|
|
|
public function testExitCodeConstants(): void
|
|
{
|
|
$this->assertSame(0, CliFramework::EXIT_SUCCESS);
|
|
$this->assertSame(1, CliFramework::EXIT_FAILURE);
|
|
$this->assertSame(2, CliFramework::EXIT_USAGE);
|
|
$this->assertSame(3, CliFramework::EXIT_NOT_FOUND);
|
|
$this->assertSame(4, CliFramework::EXIT_PERMISSION);
|
|
}
|
|
|
|
// ── JSON output ─────────────────────────────────────────────────────
|
|
|
|
public function testJsonOutputProducesValidJson(): void
|
|
{
|
|
$tool = $this->createTool(function ($self) {
|
|
return $self->jsonOutput('pass', ['key' => 'value'], [], ['soft warning']);
|
|
});
|
|
|
|
$output = $this->captureOutput($tool);
|
|
|
|
$decoded = json_decode($output, true);
|
|
$this->assertNotNull($decoded, 'jsonOutput must produce valid JSON');
|
|
$this->assertSame('pass', $decoded['status']);
|
|
$this->assertSame(0, $decoded['exit_code']);
|
|
$this->assertSame(['key' => 'value'], $decoded['data']);
|
|
$this->assertSame([], $decoded['errors']);
|
|
$this->assertSame(['soft warning'], $decoded['warnings']);
|
|
$this->assertArrayHasKey('duration_ms', $decoded['metadata']);
|
|
$this->assertArrayHasKey('timestamp', $decoded['metadata']);
|
|
}
|
|
|
|
public function testJsonOutputFailStatus(): void
|
|
{
|
|
$tool = $this->createTool(function ($self) {
|
|
return $self->jsonOutput('fail', null, ['something broke']);
|
|
});
|
|
|
|
$output = $this->captureOutput($tool);
|
|
$decoded = json_decode($output, true);
|
|
|
|
$this->assertSame('fail', $decoded['status']);
|
|
$this->assertSame(1, $decoded['exit_code']);
|
|
$this->assertSame(['something broke'], $decoded['errors']);
|
|
}
|
|
|
|
public function testJsonOutputCustomExitCode(): void
|
|
{
|
|
$tool = $this->createTool(function ($self) {
|
|
return $self->jsonOutput('error', null, ['not found'], [], 3);
|
|
});
|
|
|
|
$output = $this->captureOutput($tool);
|
|
$decoded = json_decode($output, true);
|
|
|
|
$this->assertSame(3, $decoded['exit_code']);
|
|
}
|
|
|
|
// ── Table rendering ─────────────────────────────────────────────────
|
|
|
|
public function testTableRendersHeadersAndRows(): void
|
|
{
|
|
$tool = $this->createTool(function ($self) {
|
|
$self->table(['Name', 'Status'], [
|
|
['foo', 'ok'],
|
|
['bar', 'fail'],
|
|
]);
|
|
return 0;
|
|
});
|
|
|
|
// Table output is suppressed by --quiet, so run without it.
|
|
$_SERVER['argv'] = ['test', '--no-color'];
|
|
ob_start();
|
|
$tool->execute();
|
|
$output = ob_get_clean() ?: '';
|
|
|
|
$this->assertStringContainsString('Name', $output);
|
|
$this->assertStringContainsString('Status', $output);
|
|
$this->assertStringContainsString('foo', $output);
|
|
$this->assertStringContainsString('bar', $output);
|
|
$this->assertStringContainsString('+', $output); // separator
|
|
}
|
|
|
|
// ── Helpers ──────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Create a testable CliFramework subclass with a custom run() callback.
|
|
*/
|
|
private function createTool(callable $runCallback): CliFramework
|
|
{
|
|
return new class ($runCallback) extends CliFramework {
|
|
private $callback;
|
|
|
|
public function __construct(callable $callback)
|
|
{
|
|
$this->callback = $callback;
|
|
parent::__construct('test-tool', '1.0.0');
|
|
}
|
|
|
|
protected function configure(): void
|
|
{
|
|
$this->setDescription('Test tool');
|
|
}
|
|
|
|
protected function run(): int
|
|
{
|
|
return ($this->callback)($this);
|
|
}
|
|
|
|
// Expose protected methods for testing.
|
|
public function jsonOutput(
|
|
string $status,
|
|
mixed $data = null,
|
|
array $errors = [],
|
|
array $warnings = [],
|
|
int $exitCode = -1
|
|
): int {
|
|
return parent::jsonOutput($status, $data, $errors, $warnings, $exitCode);
|
|
}
|
|
|
|
public function table(array $headers, array $rows): void
|
|
{
|
|
parent::table($headers, $rows);
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Capture stdout from a tool's run method via execute().
|
|
*
|
|
* The banner and other output is included, so JSON tests should
|
|
* extract the JSON portion (last complete JSON object) from the output.
|
|
*/
|
|
private function captureOutput(CliFramework $tool): string
|
|
{
|
|
// Suppress banner by simulating --quiet, but jsonOutput still writes.
|
|
$_SERVER['argv'] = ['test', '--quiet'];
|
|
ob_start();
|
|
$tool->execute();
|
|
$output = ob_get_clean() ?: '';
|
|
// Extract JSON object from output (may have log lines before it).
|
|
if (preg_match('/\{[\s\S]*\}\s*$/m', $output, $m)) {
|
|
return $m[0];
|
|
}
|
|
return $output;
|
|
}
|
|
}
|