Skip to content
Merged
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
5 changes: 5 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,15 @@ services:
App\Controller\:
resource: '../src/Controller/'
tags: [ 'controller.service_arguments' ]
bind:
$environment: '%kernel.environment%'

# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones

App\PHPDocker\Zip\Archiver:
shared: false

# Instantiate some third party libraries we need for autowiring our own services
Michelf\MarkdownExtra: ~
Symfony\Component\Yaml\Dumper: ~
Expand Down
163 changes: 162 additions & 1 deletion features/generator.feature
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,38 @@ Feature:
When I press "Generate project archive"
Then the response code should be 200
And I should receive a zip file named "phpdocker.zip"
# And show last response

Scenario: Default zip contains all expected files
Given I am on "/"
When I press "Generate project archive"
Then I should receive a zip file named "phpdocker.zip"
And the zip should contain the file "docker-compose.yml"
And the zip should contain the file "phpdocker/php-fpm/Dockerfile"
And the zip should contain the file "phpdocker/php-fpm/php-ini-overrides.ini"
And the zip should contain the file "phpdocker/nginx/nginx.conf"
And the zip should contain the file "phpdocker/README.md"
And the zip should contain the file "phpdocker/README.html"

Scenario: Webserver and php-fpm are always present in docker-compose.yml
Given I am on "/"
When I press "Generate project archive"
Then I should receive a zip file named "phpdocker.zip"
And the zip file "docker-compose.yml" should contain "webserver:"
And the zip file "docker-compose.yml" should contain "php-fpm:"

Scenario: PHP 8.2 is reflected in Dockerfile
Given I am on "/"
When I select "8.2" from "project_phpOptions_version"
And I press "Generate project archive"
Then I should receive a zip file named "phpdocker.zip"
And the zip file "phpdocker/php-fpm/Dockerfile" should contain "phpdockerio/php:8.2-fpm"

Scenario: PHP 8.5 is reflected in Dockerfile
Given I am on "/"
When I select "8.5" from "project_phpOptions_version"
And I press "Generate project archive"
Then I should receive a zip file named "phpdocker.zip"
And the zip file "phpdocker/php-fpm/Dockerfile" should contain "phpdockerio/php:8.5-fpm"

Scenario: Check MySQL validation works
Given I am on "/"
Expand All @@ -31,6 +62,23 @@ Feature:
And the "#container_for_mysql_username" element should contain "This value should not be blank."
And the "#container_for_mysql_password" element should contain "This value should not be blank."

Scenario: Check MariaDB validation works
Given I am on "/"
When I check "MariaDB"
And I press "Generate project archive"
Then the "#container_for_mariadb_rootPassword" element should contain "This value should not be blank."
And the "#container_for_mariadb_databaseName" element should contain "This value should not be blank."
And the "#container_for_mariadb_username" element should contain "This value should not be blank."
And the "#container_for_mariadb_password" element should contain "This value should not be blank."

Scenario: Check PostgreSQL validation works
Given I am on "/"
When I check "Postgres"
And I press "Generate project archive"
Then the "#container_for_postgres_rootUser" element should contain "This value should not be blank."
And the "#container_for_postgres_rootPassword" element should contain "This value should not be blank."
And the "#container_for_postgres_databaseName" element should contain "This value should not be blank."

Scenario: MySQL config works correctly
Given I am on "/"
When I check "MySQL"
Expand All @@ -41,3 +89,116 @@ Feature:
When I press "Generate project archive"
Then the response code should be 200
And I should receive a zip file named "phpdocker.zip"
And the zip file "docker-compose.yml" should contain "mysql:"
And the zip file "docker-compose.yml" should contain "MYSQL_ROOT_PASSWORD=root pass"
And the zip file "docker-compose.yml" should contain "MYSQL_DATABASE=db name"
And the zip file "docker-compose.yml" should contain "MYSQL_USER=user"
And the zip file "docker-compose.yml" should contain "MYSQL_PASSWORD=pass"

Scenario: MariaDB config works correctly
Given I am on "/"
When I check "MariaDB"
And I fill in "project_mariadbOptions_rootPassword" with "root pass"
And I fill in "project_mariadbOptions_databaseName" with "db name"
And I fill in "project_mariadbOptions_username" with "user"
And I fill in "project_mariadbOptions_password" with "pass"
When I press "Generate project archive"
Then the response code should be 200
And I should receive a zip file named "phpdocker.zip"
And the zip file "docker-compose.yml" should contain "mariadb:"
And the zip file "docker-compose.yml" should contain "MYSQL_ROOT_PASSWORD=root pass"
And the zip file "docker-compose.yml" should contain "MYSQL_DATABASE=db name"
And the zip file "docker-compose.yml" should contain "MYSQL_USER=user"
And the zip file "docker-compose.yml" should contain "MYSQL_PASSWORD=pass"

Scenario: PostgreSQL config works correctly
Given I am on "/"
When I check "Postgres"
And I fill in "project_postgresOptions_rootUser" with "root user"
And I fill in "project_postgresOptions_rootPassword" with "root pass"
And I fill in "project_postgresOptions_databaseName" with "db name"
When I press "Generate project archive"
Then the response code should be 200
And I should receive a zip file named "phpdocker.zip"
And the zip file "docker-compose.yml" should contain "postgres:"
And the zip file "docker-compose.yml" should contain "POSTGRES_USER=root user"
And the zip file "docker-compose.yml" should contain "POSTGRES_PASSWORD=root pass"
And the zip file "docker-compose.yml" should contain "POSTGRES_DB=db name"

Scenario: Redis is included when enabled
Given I am on "/"
When I check "Redis"
And I press "Generate project archive"
Then I should receive a zip file named "phpdocker.zip"
And the zip file "docker-compose.yml" should contain "redis:"

Scenario: Memcached is included when enabled
Given I am on "/"
When I check "Memcached"
And I press "Generate project archive"
Then I should receive a zip file named "phpdocker.zip"
And the zip file "docker-compose.yml" should contain "memcached:"

Scenario: Mailhog is included when enabled
Given I am on "/"
When I check "Mailhog"
And I press "Generate project archive"
Then I should receive a zip file named "phpdocker.zip"
And the zip file "docker-compose.yml" should contain "mailhog:"

Scenario: Clickhouse is included when enabled
Given I am on "/"
When I check "Clickhouse"
And I press "Generate project archive"
Then I should receive a zip file named "phpdocker.zip"
And the zip file "docker-compose.yml" should contain "clickhouse:"

Scenario: Optional services are absent by default
Given I am on "/"
When I press "Generate project archive"
Then I should receive a zip file named "phpdocker.zip"
And the zip file "docker-compose.yml" should not contain "redis:"
And the zip file "docker-compose.yml" should not contain "memcached:"
And the zip file "docker-compose.yml" should not contain "mailhog:"
And the zip file "docker-compose.yml" should not contain "clickhouse:"
And the zip file "docker-compose.yml" should not contain "mysql:"
And the zip file "docker-compose.yml" should not contain "mariadb:"
And the zip file "docker-compose.yml" should not contain "postgres:"
And the zip file "docker-compose.yml" should not contain "elasticsearch:"

Scenario: All optional services enabled simultaneously
Given I am on "/"
When I check "Redis"
And I check "Memcached"
And I check "Mailhog"
And I check "Clickhouse"
And I check "MySQL"
And I fill in "project_mysqlOptions_rootPassword" with "root pass"
And I fill in "project_mysqlOptions_databaseName" with "db name"
And I fill in "project_mysqlOptions_username" with "user"
And I fill in "project_mysqlOptions_password" with "pass"
And I check "MariaDB"
And I fill in "project_mariadbOptions_rootPassword" with "root pass"
And I fill in "project_mariadbOptions_databaseName" with "db name"
And I fill in "project_mariadbOptions_username" with "user"
And I fill in "project_mariadbOptions_password" with "pass"
And I check "Postgres"
And I fill in "project_postgresOptions_rootUser" with "root user"
And I fill in "project_postgresOptions_rootPassword" with "root pass"
And I fill in "project_postgresOptions_databaseName" with "db name"
And I press "Generate project archive"
Then I should receive a zip file named "phpdocker.zip"
And the zip file "docker-compose.yml" should contain "redis:"
And the zip file "docker-compose.yml" should contain "memcached:"
And the zip file "docker-compose.yml" should contain "mailhog:"
And the zip file "docker-compose.yml" should contain "clickhouse:"
And the zip file "docker-compose.yml" should contain "mysql:"
And the zip file "docker-compose.yml" should contain "MYSQL_ROOT_PASSWORD=root pass"
And the zip file "docker-compose.yml" should contain "MYSQL_DATABASE=db name"
And the zip file "docker-compose.yml" should contain "MYSQL_USER=user"
And the zip file "docker-compose.yml" should contain "MYSQL_PASSWORD=pass"
And the zip file "docker-compose.yml" should contain "mariadb:"
And the zip file "docker-compose.yml" should contain "postgres:"
And the zip file "docker-compose.yml" should contain "POSTGRES_USER=root user"
And the zip file "docker-compose.yml" should contain "POSTGRES_PASSWORD=root pass"
And the zip file "docker-compose.yml" should contain "POSTGRES_DB=db name"
2 changes: 1 addition & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ parameters:
- src/
- tests/

checkMissingIterableValueType: false
ignoreErrors:
- identifier: missingType.iterableValue
- '#Access to an undefined property Symfony\\Component\\Validator\\Constraint::\$message#'
- '#Method App\\PHPDocker\\Project\\ServiceOptions\\Postgres::getChoices\(\) should return array<string, string> but returns array<int\|string, string>#'
14 changes: 9 additions & 5 deletions src/Controller/GeneratorController.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@
*/
class GeneratorController extends AbstractController
{
public function __construct(private readonly Generator $generator)
{
public function __construct(
private readonly Generator $generator,
private readonly string $environment,
) {
}

/**
Expand All @@ -55,12 +57,14 @@ public function create(Request $request): BinaryFileResponse|Response
// Generate zip file with docker project
$zipFile = $this->generator->generate($project);

// Generate file download & cleanup
// Generate file download & cleanup (keep file in test env so functional tests can read it)
$response = new BinaryFileResponse($zipFile->getTmpFilename());
$response
->prepare($request)
->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $zipFile->getFilename())
->deleteFileAfterSend(true);
->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $zipFile->getFilename());
if ($this->environment !== 'test') {
$response->deleteFileAfterSend(true);
}

return $response;
}
Expand Down
4 changes: 2 additions & 2 deletions templates/generator.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
<div id="mariadb-options" class="row">
{% for field in ['version', 'rootPassword', 'databaseName', 'username', 'password'] %}
{% if (attribute(form.mariadbOptions, field) is defined) %}
{{ form_row(attribute(form.mariadbOptions, field)) }}
<div id="container_for_mariadb_{{ field }}">{{ form_row(attribute(form.mariadbOptions, field)) }}</div>
{% endif %}
{% endfor %}
</div>
Expand All @@ -108,7 +108,7 @@
<div id="postgres-options" class="row">
{% for field in ['version', 'rootUser', 'rootPassword', 'databaseName'] %}
{% if (attribute(form.postgresOptions, field) is defined) %}
{{ form_row(attribute(form.postgresOptions, field)) }}
<div id="container_for_postgres_{{ field }}">{{ form_row(attribute(form.postgresOptions, field)) }}</div>
{% endif %}
{% endfor %}
</div>
Expand Down
66 changes: 56 additions & 10 deletions tests/Behat/DefaultContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

final class DefaultContext extends MinkContext
{
private ?ZipArchive $lastZip = null;
private ?string $lastZipTmpFile = null;

/**
* @Then /^the response code should be (\d+)$/
*/
Expand Down Expand Up @@ -56,20 +59,63 @@ public function iShouldReceiveAZipFileNamed(string $zipFilename)

Assertion::eqArraySubset($headers, $expectedZipHeaders);

Assertion::true($this->isZipFile($response));
$tmpFile = sprintf('%s', tempnam('/tmp', 'zip_test_'));
file_put_contents(filename: $tmpFile, data: $response);

$zip = new ZipArchive();
$result = $zip->open($tmpFile);

Assertion::true($result);

$this->lastZip = $zip;
$this->lastZipTmpFile = $tmpFile;
}

private function isZipFile(string $data): bool
/** @AfterScenario */
public function cleanUpZip(): void
{
$fn = sprintf('%s', tempnam('/tmp', 'zip_test_'));
file_put_contents(filename: $fn, data: $data);

try {
$zipFile = new ZipArchive();
if ($this->lastZip !== null) {
$this->lastZip->close();
$this->lastZip = null;
}

return $zipFile->open($fn);
} finally {
@unlink($fn);
if ($this->lastZipTmpFile !== null) {
@unlink($this->lastZipTmpFile);
$this->lastZipTmpFile = null;
}
}

/**
* @Then /^the zip should contain the file "([^"]*)"$/
*/
public function theZipShouldContainTheFile(string $path): void
{
Assertion::notNull($this->lastZip, 'No zip file available. Did you call "I should receive a zip file named" first?');
Assertion::true(
$this->lastZip->locateName($path) !== false,
sprintf('File "%s" not found in zip archive', $path),
);
}

/**
* @Then /^the zip file "([^"]*)" should contain "([^"]*)"$/
*/
public function theZipFileShouldContain(string $path, string $content): void
{
Assertion::notNull($this->lastZip, 'No zip file available. Did you call "I should receive a zip file named" first?');
$fileContent = $this->lastZip->getFromName($path);
Assertion::string($fileContent, sprintf('File "%s" not found in zip archive', $path));
Assertion::contains($fileContent, $content, sprintf('File "%s" does not contain "%s"', $path, $content));
}

/**
* @Then /^the zip file "([^"]*)" should not contain "([^"]*)"$/
*/
public function theZipFileShouldNotContain(string $path, string $content): void
{
Assertion::notNull($this->lastZip, 'No zip file available. Did you call "I should receive a zip file named" first?');
$fileContent = $this->lastZip->getFromName($path);
Assertion::string($fileContent, sprintf('File "%s" not found in zip archive', $path));
Assertion::notContains($fileContent, $content, sprintf('File "%s" should not contain "%s"', $path, $content));
}
}
Loading
Loading