Skip to content

Commit 9b86f37

Browse files
committed
Moved installer handler unit and functional tests into own classes.
1 parent cdfe3f0 commit 9b86f37

43 files changed

Lines changed: 3022 additions & 2004 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.vortex/CLAUDE.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,34 @@ File::runTaskDirectory($this->config->get(Config::TMP));
586586
3. **Complex Logic Loss**: Don't oversimplify complex transformations - use callback signature when needed
587587
4. **Test Order Dependencies**: Some tests depend on specific file/directory states from previous handlers
588588
589+
## Installer Test Architecture
590+
591+
### Handler-Specific Test Classes
592+
593+
The installer tests have been refactored to use a modular, handler-focused architecture that improves maintainability and test execution flexibility.
594+
595+
**Abstract Base Class**: `AbstractInstallTestCase` provides shared test logic for all installer test scenarios, including:
596+
- Common setup and teardown procedures
597+
- Core `testInstall()` method with data provider integration
598+
- Fixture management and assertion helpers
599+
- Version replacement utilities
600+
601+
**Handler Test Organization**: Each installer handler has its own dedicated test class in the `Handlers/` namespace that extends the abstract base class. This approach provides:
602+
603+
- **Focused Testing**: Each test class covers scenarios specific to one handler or feature area
604+
- **Better Maintainability**: Smaller, focused data providers that are easier to understand and modify
605+
- **Improved Filtering**: Granular test execution capabilities using PHPUnit filters
606+
- **Scalable Architecture**: Easy to add new handler tests following established patterns
607+
608+
**Key Benefits**:
609+
- Run all handler tests: `--filter "Handlers\\\\"`
610+
- Run specific handler: `--filter "HandlerNameInstallTest"`
611+
- Run specific scenarios: `--filter "HandlerNameInstallTest.*scenario_pattern"`
612+
- Consistent structure across all handler test classes
613+
- Clear separation between test logic (in base class) and test data (in handler classes)
614+
615+
**Usage with Fixture Updates**: The `UPDATE_FIXTURES=1` mechanism works seamlessly with the new architecture, allowing systematic fixture updates across all handler test scenarios.
616+
589617
## Resources
590618
591619
- **Documentation**: `.vortex/docs/` and https://www.vortextemplate.com
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DrevOps\Installer\Tests\Functional\Handlers;
6+
7+
use DrevOps\VortexInstaller\Tests\Functional\FunctionalTestCase;
8+
use DrevOps\VortexInstaller\Utils\File;
9+
use Laravel\SerializableClosure\SerializableClosure;
10+
use PHPUnit\Framework\Attributes\DataProvider;
11+
use PHPUnit\Framework\Attributes\RunInSeparateProcess;
12+
13+
/**
14+
* Abstract base class for installer tests.
15+
*
16+
* Provides common test logic for all installer test scenarios.
17+
* Run with `UPDATE_FIXTURES=1` to update test fixtures.
18+
*/
19+
abstract class AbstractInstallTestCase extends FunctionalTestCase {
20+
21+
protected function setUp(): void {
22+
parent::setUp();
23+
24+
// Use a two-words name for the sut directory.
25+
static::$sut = File::mkdir(static::$workspace . DIRECTORY_SEPARATOR . 'star_wars');
26+
27+
// Change the current working directory to the 'system under test'.
28+
chdir(static::$sut);
29+
}
30+
31+
#[DataProvider('dataProviderInstall')]
32+
#[RunInSeparateProcess]
33+
public function testInstall(
34+
?SerializableClosure $before = NULL,
35+
?SerializableClosure $after = NULL,
36+
array $expected = [],
37+
): void {
38+
static::$fixtures = static::locationsFixtureDir();
39+
40+
if ($before instanceof SerializableClosure) {
41+
$before = static::cu($before);
42+
$before($this);
43+
}
44+
45+
$this->runNonInteractiveInstall();
46+
47+
$expected = empty($expected) ? ['Welcome to the Vortex non-interactive installer'] : $expected;
48+
$this->assertApplicationOutputContains($expected);
49+
50+
$baseline = File::dir(static::$fixtures . '/../' . self::BASELINE_DIR);
51+
static::replaceVersions(static::$sut);
52+
$this->assertDirectoryEqualsPatchedBaseline(static::$sut, $baseline, static::$fixtures);
53+
54+
$this->assertCommon();
55+
56+
if ($after instanceof SerializableClosure) {
57+
$after = static::cu($after);
58+
$after($this);
59+
}
60+
}
61+
62+
abstract public static function dataProviderInstall(): array;
63+
64+
protected function assertCommon(): void {
65+
$this->assertDirectoryEqualsDirectory(static::$root . '/scripts/vortex', static::$sut . '/scripts/vortex', 'Vortex scripts were not modified.');
66+
if (file_exists(static::$root . '/scripts/vortex.yml')) {
67+
$this->assertFileEquals(static::$root . '/tests/behat/fixtures/image.jpg', static::$sut . '/tests/behat/fixtures/image.jpg', 'Binary files were not modified.');
68+
}
69+
70+
$this->assertYamlFileIsValid('.ahoy.yml');
71+
$this->assertJsonFileIsValid('composer.json');
72+
}
73+
74+
protected static function defaultAnswers(): array {
75+
return [
76+
'namespace' => 'YodasHut',
77+
'project' => 'force-crystal',
78+
'author' => 'Luke Skywalker',
79+
'use_php' => static::TUI_DEFAULT,
80+
'use_php_command' => static::TUI_DEFAULT,
81+
'php_command_name' => static::TUI_DEFAULT,
82+
'use_php_command_build' => static::TUI_DEFAULT,
83+
'use_php_script' => static::TUI_DEFAULT,
84+
'use_nodejs' => static::TUI_DEFAULT,
85+
'use_shell' => static::TUI_DEFAULT,
86+
'use_release_drafter' => static::TUI_DEFAULT,
87+
'use_pr_autoassign' => static::TUI_DEFAULT,
88+
'use_funding' => static::TUI_DEFAULT,
89+
'use_pr_template' => static::TUI_DEFAULT,
90+
'use_renovate' => static::TUI_DEFAULT,
91+
'use_docs' => static::TUI_DEFAULT,
92+
'remove_self' => static::TUI_DEFAULT,
93+
];
94+
}
95+
96+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DrevOps\Installer\Tests\Functional\Handlers;
6+
7+
use DrevOps\VortexInstaller\Prompts\Handlers\AiCodeInstructions;
8+
use DrevOps\VortexInstaller\Prompts\PromptManager;
9+
use DrevOps\VortexInstaller\Utils\Env;
10+
use PHPUnit\Framework\Attributes\CoversClass;
11+
12+
#[CoversClass(AiCodeInstructions::class)]
13+
class AiCodeInstructionsInstallTest extends AbstractInstallTestCase {
14+
15+
public static function dataProviderInstall(): array {
16+
return [
17+
'ai instructions, claude' => [
18+
static::cw(fn() => Env::put(PromptManager::makeEnvName(AiCodeInstructions::id()), AiCodeInstructions::CLAUDE)),
19+
],
20+
21+
'ai instructions, none' => [
22+
static::cw(fn() => Env::put(PromptManager::makeEnvName(AiCodeInstructions::id()), AiCodeInstructions::NONE)),
23+
],
24+
];
25+
}
26+
27+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DrevOps\Installer\Tests\Functional\Handlers;
6+
7+
use DrevOps\VortexInstaller\Prompts\Handlers\AssignAuthorPr;
8+
use DrevOps\VortexInstaller\Prompts\Handlers\CiProvider;
9+
use DrevOps\VortexInstaller\Prompts\Handlers\CodeProvider;
10+
use DrevOps\VortexInstaller\Prompts\Handlers\DatabaseDownloadSource;
11+
use DrevOps\VortexInstaller\Prompts\Handlers\DatabaseImage;
12+
use DrevOps\VortexInstaller\Prompts\Handlers\DependencyUpdatesProvider;
13+
use DrevOps\VortexInstaller\Prompts\Handlers\DeployType;
14+
use DrevOps\VortexInstaller\Prompts\Handlers\Domain;
15+
use DrevOps\VortexInstaller\Prompts\Handlers\HostingProvider;
16+
use DrevOps\VortexInstaller\Prompts\Handlers\Internal;
17+
use DrevOps\VortexInstaller\Prompts\Handlers\LabelMergeConflictsPr;
18+
use DrevOps\VortexInstaller\Prompts\Handlers\MachineName;
19+
use DrevOps\VortexInstaller\Prompts\Handlers\ModulePrefix;
20+
use DrevOps\VortexInstaller\Prompts\Handlers\Name;
21+
use DrevOps\VortexInstaller\Prompts\Handlers\Org;
22+
use DrevOps\VortexInstaller\Prompts\Handlers\OrgMachineName;
23+
use DrevOps\VortexInstaller\Prompts\Handlers\PreserveDocsOnboarding;
24+
use DrevOps\VortexInstaller\Prompts\Handlers\PreserveDocsProject;
25+
use DrevOps\VortexInstaller\Prompts\Handlers\Profile;
26+
use DrevOps\VortexInstaller\Prompts\Handlers\ProvisionType;
27+
use DrevOps\VortexInstaller\Prompts\Handlers\Services;
28+
use DrevOps\VortexInstaller\Prompts\Handlers\Theme;
29+
use DrevOps\VortexInstaller\Prompts\Handlers\Timezone;
30+
use DrevOps\VortexInstaller\Prompts\Handlers\Webroot;
31+
use DrevOps\VortexInstaller\Prompts\PromptManager;
32+
use DrevOps\VortexInstaller\Utils\Config;
33+
use DrevOps\VortexInstaller\Utils\Downloader;
34+
use DrevOps\VortexInstaller\Utils\Git;
35+
use DrevOps\VortexInstaller\Utils\Tui;
36+
use PHPUnit\Framework\Attributes\CoversClass;
37+
38+
#[CoversClass(AssignAuthorPr::class)]
39+
#[CoversClass(CiProvider::class)]
40+
#[CoversClass(CodeProvider::class)]
41+
#[CoversClass(DatabaseDownloadSource::class)]
42+
#[CoversClass(DatabaseImage::class)]
43+
#[CoversClass(DependencyUpdatesProvider::class)]
44+
#[CoversClass(DeployType::class)]
45+
#[CoversClass(Domain::class)]
46+
#[CoversClass(HostingProvider::class)]
47+
#[CoversClass(Internal::class)]
48+
#[CoversClass(LabelMergeConflictsPr::class)]
49+
#[CoversClass(MachineName::class)]
50+
#[CoversClass(ModulePrefix::class)]
51+
#[CoversClass(Name::class)]
52+
#[CoversClass(Org::class)]
53+
#[CoversClass(OrgMachineName::class)]
54+
#[CoversClass(PreserveDocsOnboarding::class)]
55+
#[CoversClass(PreserveDocsProject::class)]
56+
#[CoversClass(Profile::class)]
57+
#[CoversClass(ProvisionType::class)]
58+
#[CoversClass(Services::class)]
59+
#[CoversClass(Theme::class)]
60+
#[CoversClass(Timezone::class)]
61+
#[CoversClass(Webroot::class)]
62+
#[CoversClass(PromptManager::class)]
63+
#[CoversClass(Downloader::class)]
64+
#[CoversClass(Config::class)]
65+
#[CoversClass(Git::class)]
66+
#[CoversClass(Tui::class)]
67+
class BaselineInstallTest extends AbstractInstallTestCase {
68+
69+
public static function dataProviderInstall(): array {
70+
return [
71+
static::BASELINE_DATASET => [
72+
NULL,
73+
NULL,
74+
['Welcome to the Vortex non-interactive installer'],
75+
],
76+
77+
'non-interactive' => [
78+
NULL,
79+
NULL,
80+
['Welcome to the Vortex non-interactive installer'],
81+
],
82+
];
83+
}
84+
85+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DrevOps\Installer\Tests\Functional\Handlers;
6+
7+
use DrevOps\VortexInstaller\Prompts\Handlers\AiCodeInstructions;
8+
use DrevOps\VortexInstaller\Prompts\Handlers\CiProvider;
9+
use DrevOps\VortexInstaller\Prompts\PromptManager;
10+
use DrevOps\VortexInstaller\Utils\Env;
11+
use PHPUnit\Framework\Attributes\CoversClass;
12+
13+
#[CoversClass(CiProvider::class)]
14+
class CiProviderInstallTest extends AbstractInstallTestCase {
15+
16+
public static function dataProviderInstall(): array {
17+
return [
18+
'ciprovider, gha' => [
19+
static::cw(function (): void {
20+
Env::put(PromptManager::makeEnvName(CiProvider::id()), CiProvider::GITHUB_ACTIONS);
21+
Env::put(PromptManager::makeEnvName(AiCodeInstructions::id()), AiCodeInstructions::CLAUDE);
22+
}),
23+
],
24+
25+
'ciprovider, circleci' => [
26+
static::cw(function (): void {
27+
Env::put(PromptManager::makeEnvName(CiProvider::id()), CiProvider::CIRCLECI);
28+
Env::put(PromptManager::makeEnvName(AiCodeInstructions::id()), AiCodeInstructions::CLAUDE);
29+
}),
30+
],
31+
];
32+
}
33+
34+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DrevOps\Installer\Tests\Functional\Handlers;
6+
7+
use DrevOps\VortexInstaller\Prompts\Handlers\CodeProvider;
8+
use DrevOps\VortexInstaller\Prompts\PromptManager;
9+
use DrevOps\VortexInstaller\Tests\Functional\FunctionalTestCase;
10+
use DrevOps\VortexInstaller\Utils\Env;
11+
use PHPUnit\Framework\Attributes\CoversClass;
12+
13+
#[CoversClass(CodeProvider::class)]
14+
class CodeProviderInstallTest extends AbstractInstallTestCase {
15+
16+
public static function dataProviderInstall(): array {
17+
return [
18+
'code provider, github' => [
19+
static::cw(fn() => Env::put(PromptManager::makeEnvName(CodeProvider::id()), CodeProvider::GITHUB)),
20+
static::cw(function (FunctionalTestCase $test): void {
21+
$test->assertFileDoesNotExist(static::$sut . '/.github/PULL_REQUEST_TEMPLATE.dist.md');
22+
$test->assertFileContainsString('Checklist before requesting a review', static::$sut . '/.github/PULL_REQUEST_TEMPLATE.md');
23+
}),
24+
],
25+
26+
'code provider, other' => [
27+
static::cw(fn() => Env::put(PromptManager::makeEnvName(CodeProvider::id()), CodeProvider::OTHER)),
28+
static::cw(function (FunctionalTestCase $test): void {
29+
$test->assertDirectoryDoesNotExist(static::$sut . '/.github');
30+
}),
31+
],
32+
];
33+
}
34+
35+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DrevOps\Installer\Tests\Functional\Handlers;
6+
7+
use DrevOps\VortexInstaller\Prompts\Handlers\AiCodeInstructions;
8+
use DrevOps\VortexInstaller\Prompts\Handlers\DatabaseDownloadSource;
9+
use DrevOps\VortexInstaller\Prompts\Handlers\DatabaseImage;
10+
use DrevOps\VortexInstaller\Prompts\PromptManager;
11+
use DrevOps\VortexInstaller\Utils\Env;
12+
use PHPUnit\Framework\Attributes\CoversClass;
13+
14+
#[CoversClass(DatabaseDownloadSource::class)]
15+
#[CoversClass(DatabaseImage::class)]
16+
class DatabaseDownloadSourceInstallTest extends AbstractInstallTestCase {
17+
18+
public static function dataProviderInstall(): array {
19+
return [
20+
'db download source, url' => [
21+
static::cw(fn() => Env::put(PromptManager::makeEnvName(DatabaseDownloadSource::id()), DatabaseDownloadSource::URL)),
22+
],
23+
24+
'db download source, ftp' => [
25+
static::cw(fn() => Env::put(PromptManager::makeEnvName(DatabaseDownloadSource::id()), DatabaseDownloadSource::FTP)),
26+
],
27+
28+
'db download source, acquia' => [
29+
static::cw(fn() => Env::put(PromptManager::makeEnvName(DatabaseDownloadSource::id()), DatabaseDownloadSource::ACQUIA)),
30+
],
31+
32+
'db download source, lagoon' => [
33+
static::cw(fn() => Env::put(PromptManager::makeEnvName(DatabaseDownloadSource::id()), DatabaseDownloadSource::LAGOON)),
34+
],
35+
36+
'db download source, container_registry' => [
37+
static::cw(function (): void {
38+
Env::put(PromptManager::makeEnvName(DatabaseDownloadSource::id()), DatabaseDownloadSource::CONTAINER_REGISTRY);
39+
Env::put(PromptManager::makeEnvName(DatabaseImage::id()), 'the_empire/star_wars:latest');
40+
Env::put(PromptManager::makeEnvName(AiCodeInstructions::id()), AiCodeInstructions::CLAUDE);
41+
}),
42+
],
43+
];
44+
}
45+
46+
}

0 commit comments

Comments
 (0)