com web/bugs: Add automatic fixtures generator importer: .gitignore README.md composer.json composer.lock config/container.php config/parameters.php
scripts/console sql/fixtures.sql src/Command/InsertFixturesCommand.php src/Fixtures/AppFixtures.php tests/Unit/Command/InsertFixturesCommandTest.php
| From: | Peter Kokot | Date: | Wed, 22 May 2019 01:15:30 +0000 |
| Subject: | com web/bugs: Add automatic fixtures generator importer: .gitignore README.md composer.json composer.lock config/container.php config/parameters.php scripts/console sql/fixtures.sql src/Command/InsertFixturesCommand.php src/Fixtures/AppFixtures.php tests/Unit/Command/InsertFixturesCommandTest.php |
||
| Groups: | php.webmaster | ||
| Request: | Send a blank email to php-webmaster+get-28060@lists.php.net to get a copy of this message | ||
Commit: 5cdac95adc49562291a873c8704de3ceb0d9d398
Author: Peter Kokot <peterkokot@gmail.com> Mon, 6 May 2019 10:53:06 +0200
Parents: 9d47b05f2db8b32039ab940db0083d6f4ce3370e
Branches: master
Link: http://git.php.net/?p=web/bugs.git;a=commitdiff;h=5cdac95adc49562291a873c8704de3ceb0d9d398
Log:
Add automatic fixtures generator importer
When developing site locally, developer needs to have data sets to run
manual tests, see how the app is built, and similar. The new additional
command line command using Symfony/Console component creates a main
scripts/console entry point for running Commands. Initial set of demo
fixtures data is generated using the faker library and includes most
data needed to use and test application manually in the local
environment.
Changed paths:
M .gitignore
M README.md
M composer.json
M composer.lock
M config/container.php
M config/parameters.php
A scripts/console
D sql/fixtures.sql
A src/Command/InsertFixturesCommand.php
A src/Fixtures/AppFixtures.php
A tests/Unit/Command/InsertFixturesCommandTest.php
diff --git a/.gitignore b/.gitignore index 8d0441e..fe3c3eb 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,7 @@ # Local specific PHPUnit configuration /phpunit.xml -.phpunit.result.cache +/.phpunit.result.cache # Transient and temporary generated files /var/ diff --git a/README.md b/README.md index d3421a2..f767e56 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,13 @@ Modify `local_config.php` according to your local development environment. * Database: -Create a new MySQL/MariaDB database using `sql/database.sql`, create database -schema `sql/schema.sql` and insert fixtures using `sql/fixtures.sql`. +Create a new MySQL, MariaDB, Percona Server, or equivalent database using +`sql/database.sql`, create database schema `sql/schema.sql` and insert demo data +fixtures by running: + +```bash +php scripts/console app:fixtures:insert +``` ## Tests @@ -42,9 +47,10 @@ Source code of this application is structured in the following directories: ├─ prepend.php # Autoloader, DB connection, container, app initialization └─ ... └─ scripts/ # Command line development tools and scripts + ├─ console # Application's main script for running CLI commands ├─ cron/ # Various systems scripts to run periodically on the server └─ ... - ├─ sql/ # Database schema and fixtures + ├─ sql/ # Database schema └─ src/ # Application source code classes ├─ Horde/ # https://www.horde.org/libraries/Horde_Text_Diff └─ ... diff --git a/composer.json b/composer.json index abec91d..4d02265 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,9 @@ }, "require-dev": { "ext-pdo_sqlite": "*", - "phpunit/phpunit": "^8.1.5" + "fzaninotto/faker": "^1.8", + "phpunit/phpunit": "^8.1.5", + "symfony/console": "^4.2" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 8111618..898739c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "994d11b989a10210dc468c7046539905", + "content-hash": "e90209efe832cd89e50a5f742fd551a8", "packages": [], "packages-dev": [ { @@ -64,6 +64,56 @@ "time": "2019-03-17T17:37:11+00:00" }, { + "name": "fzaninotto/faker", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/f72816b43e74063c8b10357394b6bba8cb1c10de", + "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "ext-intl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7", + "squizlabs/php_codesniffer": "^1.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "time": "2018-07-12T10:23:15+00:00" + }, + { "name": "myclabs/deep-copy", "version": "1.9.1", "source": { @@ -1332,6 +1382,149 @@ "time": "2016-10-03T07:35:21+00:00" }, { + "name": "symfony/console", + "version": "v4.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "e2840bb38bddad7a0feaf85931e38fdcffdb2f81" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/e2840bb38bddad7a0feaf85931e38fdcffdb2f81", + "reference": "e2840bb38bddad7a0feaf85931e38fdcffdb2f81", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/contracts": "^1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2019-04-08T14:23:48+00:00" + }, + { + "name": "symfony/contracts", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/contracts.git", + "reference": "d3636025e8253c6144358ec0a62773cae588395b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/contracts/zipball/d3636025e8253c6144358ec0a62773cae588395b", + "reference": "d3636025e8253c6144358ec0a62773cae588395b", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "require-dev": { + "psr/cache": "^1.0", + "psr/container": "^1.0", + "symfony/polyfill-intl-idn": "^1.10" + }, + "suggest": { + "psr/cache": "When using the Cache contracts", + "psr/container": "When using the Service contracts", + "symfony/cache-contracts-implementation": "", + "symfony/event-dispatcher-implementation": "", + "symfony/http-client-contracts-implementation": "", + "symfony/service-contracts-implementation": "", + "symfony/translation-contracts-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\": "" + }, + "exclude-from-classmap": [ + "**/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A set of abstractions extracted out of the Symfony components", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-04-27T14:29:50+00:00" + }, + { "name": "symfony/polyfill-ctype", "version": "v1.11.0", "source": { @@ -1390,6 +1583,65 @@ "time": "2019-02-06T07:57:58+00:00" }, { + "name": "symfony/polyfill-mbstring", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { "name": "theseer/tokenizer", "version": "1.1.2", "source": { diff --git a/config/container.php b/config/container.php index 5cf2979..59e3045 100644 --- a/config/container.php +++ b/config/container.php @@ -81,4 +81,25 @@ $container->set(App\Utils\Uploader::class, function ($c) { return new App\Utils\Uploader(); }); +$container->set(App\Fixtures\AppFixtures::class, function ($c) { + return new App\Fixtures\AppFixtures($c->get(\PDO::class), Faker\Factory::create(), $c->get('bug_statuses')); +}); + +$container->set(App\Command\InsertFixturesCommand::class, function ($c) { + $insertFixturesCommand = new App\Command\InsertFixturesCommand( + $c->get(\PDO::class), + $c->get(App\Fixtures\AppFixtures::class), + $c->get('env') + ); + + return $insertFixturesCommand; +}); + +$container->set(Symfony\Component\Console\Application::class, function ($c) { + $application = new Symfony\Component\Console\Application(); + $application->add($c->get(App\Command\InsertFixturesCommand::class)); + + return $application; +}); + return $container; diff --git a/config/parameters.php b/config/parameters.php index 9ff0d78..0cf1b72 100644 --- a/config/parameters.php +++ b/config/parameters.php @@ -70,4 +70,24 @@ return [ * Templates directory. */ 'templates_dir' => __DIR__.'/../templates', + + /** + * Bug statuses. + */ + 'bug_statuses' => [ + 'Open' => 'Opn', + 'Not a bug' => 'Nab', + 'Feedback' => 'Fbk', + 'No Feedback' => 'NoF', + 'Wont fix' => 'Wfx', + 'Duplicate' => 'Dup', + 'Critical' => 'Ctl', + 'Assigned' => 'Asn', + 'Analyzed' => 'Ana', + 'Verified' => 'Ver', + 'Suspended' => 'Sus', + 'Closed' => 'Csd', + 'Spam' => 'Spm', + 'Re-Opened' => 'ReO', + ], ]; diff --git a/scripts/console b/scripts/console new file mode 100755 index 0000000..6c2f44f --- /dev/null +++ b/scripts/console @@ -0,0 +1,15 @@ +#!/usr/bin/env php +<?php + +/** + * Main command line console entry application. + * + * SYNOPSIS: + * php scripts/console [command] [<options>] [arguments] + */ + +require_once __DIR__.'/../include/prepend.php'; + +use Symfony\Component\Console\Application; + +$container->get(Application::class)->run(); diff --git a/sql/fixtures.sql b/sql/fixtures.sql deleted file mode 100644 index c4f3bf7..0000000 --- a/sql/fixtures.sql +++ /dev/null @@ -1,4 +0,0 @@ --- Default pseudo packages (common for all projects) - -INSERT INTO bugdb_pseudo_packages SET id = '1', parent = '0', name = 'Web Site', long_name = 'Web Site', project = ''; -INSERT INTO bugdb_pseudo_packages SET id = '2', parent = '1', name = 'Bug System', long_name = 'Bug System', project = ''; diff --git a/src/Command/InsertFixturesCommand.php b/src/Command/InsertFixturesCommand.php new file mode 100644 index 0000000..b833f2c --- /dev/null +++ b/src/Command/InsertFixturesCommand.php @@ -0,0 +1,100 @@ +<?php + +namespace App\Command; + +use App\Fixtures\AppFixtures; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ConfirmationQuestion; + +/** + * Command line command for inserting development data fixtures. + */ +class InsertFixturesCommand extends Command +{ + /** + * Database handler. + * @var \PDO + */ + private $dbh; + + /** + * Application data fixtures. + * @var AppFixtures + */ + private $fixtures; + + /** + * Current application environment. + * @var string + */ + private $environment; + + /** + * Class constructor. + */ + public function __construct(\PDO $dbh, AppFixtures $fixtures, string $environment) + { + $this->dbh = $dbh; + $this->fixtures = $fixtures; + $this->environment = $environment; + + parent::__construct(); + } + + /** + * Configure command. + */ + protected function configure() + { + $this->setName('app:fixtures:insert') + ->setDescription('Insert database fixtures.') + ->setHelp('This command inserts demo data fixtures in the database.') + ; + } + + /** + * Run the command, for example, bin/console app:generate-fixtures. It can + * be executed only in development environment as a safety measure to not + * delete something important. + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + if ('dev' !== $this->environment) { + $output->writeln('This command can be executed only in development.'); + + return; + } + + $helper = $this->getHelper('question'); + $question = new ConfirmationQuestion( + 'This will erase entire database. Are you sure you want to continue? [y/N]', + false + ); + + if (!$helper->ask($input, $output, $question)) { + $output->writeln('Exiting...'); + + return; + } + + // Delete all existing categories and add new ones. + $output->writeln('Deleting existing categories'); + $this->dbh->query('DELETE FROM bugdb_pseudo_packages'); + $output->writeln('Adding new categories'); + $this->fixtures->insertCategories(); + + // Delete all current bug reports and add new ones. + $output->writeln('Deleting existing bug reports'); + $this->dbh->query('DELETE FROM bugdb'); + $output->writeln('Adding new bug reports'); + $this->fixtures->insertBugs(); + + // Delete all current reasons and add new ones. + $output->writeln('Deleting existing reasons'); + $this->dbh->query('DELETE FROM bugdb_resolves'); + $output->writeln('Adding new reasons'); + $this->fixtures->insertReasons(); + } +} diff --git a/src/Fixtures/AppFixtures.php b/src/Fixtures/AppFixtures.php new file mode 100644 index 0000000..509f987 --- /dev/null +++ b/src/Fixtures/AppFixtures.php @@ -0,0 +1,645 @@ +<?php + +namespace App\Fixtures; + +use App\Database; +use App\Entity\Category; +use App\Entity\Package; +use Faker\Generator; + +/** + * Data fixtures for database. It uses Faker library for generating fixtures + * data. + */ +class AppFixtures +{ + /** + * Database handler. + * @var \PDO + */ + private $dbh; + + /** + * Faker utility. + * @var Generator + */ + private $faker; + + /** + * Bug statuses. + * @var array + */ + private $bugStatuses; + + /** + * Number of generated users. + */ + private const BUGS_COUNT = 1000; + + /** + * Class constructor to set injected dependencies. + */ + public function __construct(\PDO $dbh, Generator $faker, array $bugStatuses) + { + $this->dbh = $dbh; + $this->faker = $faker; + $this->bugStatuses = $bugStatuses; + } + + /** + * Insert data in the bugs categories table. + */ + public function insertCategories() + { + $sql = "INSERT INTO bugdb_pseudo_packages ( + `parent`, + `name`, + `long_name`, + `project`, + `list_email`, + `disabled` + ) VALUES ( + :parent, + :name, + :long_name, + :project, + :list_email, + :disabled + ) + "; + + $catIds = []; + + foreach ($this->getCategories() as $category => $data) { + $statement = $this->dbh->prepare($sql); + $parent = isset($data['parent']) ? $catIds[$data['parent']] : 0; + + $statement->execute([ + ':parent' => $parent, + ':name' => $category, + ':long_name' => $data['long_name'] ?? $category, + ':project' => $data['project'] ?? '', + ':list_email' => $data['list_email'] ?? '', + ':disabled' => $data['disabled'] ?? 0, + ]); + + $catIds[$category] = $this->dbh->lastInsertId(); + } + } + + /** + * Insert fixtures in users table. + */ + public function insertBugs() + { + $sql = "INSERT INTO bugdb ( + `package_name`, + `bug_type`, + `email`, + `reporter_name`, + `sdesc`, + `ldesc`, + `php_version`, + `php_os`, + `status`, + `ts1`, + `ts2`, + `assign`, + `passwd`, + `registered`, + `block_user_comment`, + `cve_id`, + `private`, + `visitor_ip` + ) VALUES ( + :package_name, + :bug_type, + :email, + :reporter_name, + :sdesc, + :ldesc, + :php_version, + :php_os, + :status, + :ts1, + :ts2, + :assign, + :passwd, + :registered, + :block_user_comment, + :cve_id, + :private, + INET6_ATON(:visitor_ip) + ) + "; + + $bugs = $this->getBugs(); + + foreach ($bugs as $data) { + $statement = $this->dbh->prepare($sql); + + $statement->execute([ + ':package_name' => $data['package_name'], + ':bug_type' => $data['bug_type'], + ':email' => $data['email'], + ':reporter_name' => $data['reporter_name'], + ':sdesc' => $data['sdesc'], + ':ldesc' => $data['ldesc'], + ':php_version' => $data['php_version'], + ':php_os' => $data['php_os'], + ':status' => $data['status'], + ':ts1' => date('Y-m-d H:i:s'), + ':ts2' => null, + ':assign' => $data['assign'], + ':passwd' => $data['passwd'], + ':registered' => $data['registered'], + ':block_user_comment' => $data['block_user_comment'], + ':cve_id' => $data['cve_id'], + ':private' => $data['private'], + ':visitor_ip' => $data['visitor_ip'], + ]); + } + } + + /** + * Insert data in the bugs reasons table. + */ + public function insertReasons() + { + $sql = "INSERT INTO bugdb_resolves ( + `name`, + `status`, + `title`, + `message`, + `project`, + `package_name`, + `webonly` + ) VALUES ( + :name, + :status, + :title, + :message, + :project, + :package_name, + :webonly + ) + "; + + foreach ($this->getReasons() as $data) { + $statement = $this->dbh->prepare($sql); + + $statement->execute([ + ':name' => $data['name'], + ':status' => $data['status'], + ':title' => $data['title'], + ':message' => $data['message'], + ':project' => $data['project'], + ':package_name' => $data['package_name'], + ':webonly' => $data['webonly'], + ]); + } + } + + /** + * Get generated demo bug reports data with each bug having password set to + * "password". + */ + private function getBugs() + { + $bugs = []; + + // Demo secret password is always password for all bug reports + $password = bugs_get_hash('password'); + + $categories = $this->dbh->query("SELECT id, name FROM bugdb_pseudo_packages")->fetchAll(\PDO::FETCH_KEY_PAIR); + + // More random bug reports + for ($i = 0; $i < self::BUGS_COUNT; $i++) { + $username = $this->faker->unique()->userName; + $username = substr($username, 0, 16); + $username = str_replace('.', '', $username); + + $bugs[] = [ + 'package_name' => $categories[array_rand($categories, 1)], + 'bug_type' => '', + 'email' => $username.'@example.com', + 'reporter_name' => $this->faker->firstName.' '.$this->faker->lastName, + 'sdesc' => $this->faker->text(80), + 'ldesc' => $this->faker->paragraph, + 'php_version' => '7.'.rand(0, 4).'.'.rand(0, 30), + 'php_os' => $this->faker->text(32), + 'status' => array_rand($this->bugStatuses), + 'ts1' => '', + 'ts2' => '', + 'assign' => '', + 'passwd' => $password, + 'registered' => 0, + 'block_user_comment' => 'N', + 'cve_id' => '', + 'private' => 'N', + 'visitor_ip' => $this->faker->ipv4, + ]; + } + + return $bugs; + } + + /** + * Categories. + */ + private function getCategories() + { + return [ + 'General Issues' => [], + 'PDO related' => [], + 'Compile Issues' => [], + 'Configuration Issues' => [], + 'Web Server problem' => [], + 'Calendar problems' => [], + 'Compression related' => [], + 'Directory/Filesystem functions' => [], + 'Database Functions' => [], + 'Data Exchange functions' => [], + 'Extensibility Functions' => [], + 'Graphics related' => [], + 'Languages/Translation' => [], + 'Mail related' => [], + 'Encryption and hash functions' => [], + 'Network functions' => [], + 'PDF functions' => [], + 'Programming Data Structures' => [], + 'Regular expressions' => [], + 'Spelling functions' => [], + 'XML functions' => [], + 'Unicode Issues' => [], + 'Unknown/Other functions' => [], + 'PECL' => [], + 'phpdbg' => [], + 'PHP Language Specification' => [], + + // General Issues + 'Doc Build (PhD) problem' => [ + 'parent' => 'General Issues', + ], + 'Documentation problem' => [ + 'parent' => 'General Issues', + ], + 'Documentation translation problem' => [ + 'parent' => 'General Issues', + ], + 'Filter relate' => [ + 'parent' => 'General Issues', + ], + 'Online Documentation Editor problem' => [ + 'parent' => 'General Issues', + ], + 'Opcache' => [ + 'parent' => 'General Issues', + ], + 'Output Control' => [ + 'parent' => 'General Issues', + ], + 'Performance problem' => [ + 'parent' => 'General Issues', + ], + 'PHAR related' => [ + 'parent' => 'General Issues', + ], + 'PHP-GTK related' => [ + 'parent' => 'General Issues', + ], + 'Systems problem' => [ + 'parent' => 'General Issues', + ], + 'Website problem' => [ + 'parent' => 'General Issues', + ], + 'Reflection related' => [ + 'parent' => 'General Issues', + ], + 'Reproducible crash' => [ + 'parent' => 'General Issues', + ], + 'Scripting Engine problem' => [ + 'parent' => 'General Issues', + ], + 'Session related' => [ + 'parent' => 'General Issues', + ], + 'SPL related' => [ + 'parent' => 'General Issues', + ], + 'Streams related' => [ + 'parent' => 'General Issues', + ], + 'Testing related' => [ + 'parent' => 'General Issues', + ], + + // PDO related + 'PDO Core' => [ + 'parent' => 'PDO related', + ], + 'PDO DBlib' => [ + 'parent' => 'PDO related', + ], + 'PDO Firebird' => [ + 'parent' => 'PDO related', + ], + 'PDO MySQL' => [ + 'parent' => 'PDO related', + ], + 'PDO OCI' => [ + 'parent' => 'PDO related', + ], + 'PDO ODBC' => [ + 'parent' => 'PDO related', + ], + 'PDO PgSQL' => [ + 'parent' => 'PDO related', + ], + 'PDO SQLite' => [ + 'parent' => 'PDO related', + ], + + // Compile Issues + 'Compile Failure' => [ + 'parent' => 'Compile Issues', + ], + 'Compile Warning' => [ + 'parent' => 'Compile Issues', + ], + + // Configuration Issues + 'Dynamic loading' => [ + 'parent' => 'Configuration Issues', + ], + 'PHP options/info functions' => [ + 'parent' => 'Configuration Issues', + ], + 'Safe Mode/open_basedir related' => [ + 'parent' => 'Configuration Issues', + ], + 'Windows Installer related' => [ + 'parent' => 'Configuration Issues', + ], + + // TODO add more + ]; + } + + private function getReasons(): array + { + return [ + [ + 'name' => 'trysnapshot54', + 'status' => 'Feedback', + 'title' => 'Try a snapshot (PHP 5.4)', + 'message' => 'Please try using this snapshot: + + http://snaps.php.net/php5.4-latest.tar.gz + + For Windows: + + http://windows.php.net/snapshots/', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'trysnapshot55', + 'status' => 'Feedback', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'trysnapshottrunk', + 'status' => 'Feedback', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'fixed', + 'status' => 'Closed', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'fixed', + 'status' => 'Closed', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'fixed', + 'status' => 'Closed', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'fixed', + 'status' => 'Closed', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'fixed', + 'status' => 'Closed', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'fixed', + 'status' => 'Closed', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'fixed', + 'status' => 'Closed', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'alreadyfixed', + 'status' => 'Closed', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'needtrace', + 'status' => 'Feedback', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'needscript', + 'status' => 'Feedback', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'oldversion', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'support', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'nofeedback', + 'status' => 'No Feedback', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 1, + ], + [ + 'name' => 'notwrong', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'notenoughinfo', + 'status' => 'Feedback', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'submittedtwice', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'globals', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'php4', + 'status' => 'Wont fix', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'dst', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'isapi', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'gnused', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'float', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'nozend', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'mysqlcfg', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + ]; + } +} diff --git a/tests/Unit/Command/InsertFixturesCommandTest.php b/tests/Unit/Command/InsertFixturesCommandTest.php new file mode 100644 index 0000000..bbbacc6 --- /dev/null +++ b/tests/Unit/Command/InsertFixturesCommandTest.php @@ -0,0 +1,65 @@ +<?php declare(strict_types=1); + +/** + * Test for insert fixtures command. + */ + +namespace App\Tests\Command; + +use App\Command\InsertFixturesCommand; +use App\Fixtures\AppFixtures; +use Faker\Factory; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Tester\CommandTester; + +use PHPUnit\Framework\TestCase; + +class InsertFixturesCommandTest extends TestCase +{ + private $dbh; + + public function setUp(): void + { + $this->dbh = new \PDO('sqlite::memory:'); + } + + public function testExecute(): void + { + $insertFixturesCommand = new InsertFixturesCommand( + $this->dbh, + new AppFixtures($this->dbh, Factory::create(), []), + 'dev' + ); + + $application = new Application(); + $application->add($insertFixturesCommand); + + $command = $application->find('app:fixtures:insert'); + $commandTester = new CommandTester($command); + $commandTester->setInputs(['n']); + $exitCode = $commandTester->execute(['command' => $command->getName()]); + + $this->assertRegExp('/.../', $commandTester->getDisplay()); + $this->assertSame(0, $exitCode); + } + + public function testExecuteInProdEnv(): void + { + $insertFixturesCommand = new InsertFixturesCommand( + $this->dbh, + new AppFixtures($this->dbh, Factory::create(), []), + 'prod' + ); + + $application = new Application(); + $application->add($insertFixturesCommand); + + $command = $application->find('app:fixtures:insert'); + $commandTester = new CommandTester($command); + $commandTester->setInputs(['n']); + $exitCode = $commandTester->execute(['command' => $command->getName()]); + + $this->assertRegExp('/This command can be executed only in dev/', $commandTester->getDisplay()); + $this->assertSame(0, $exitCode); + } +}
diff --git a/.gitignore b/.gitignore index 8d0441e..fe3c3eb 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,7 @@ # Local specific PHPUnit configuration /phpunit.xml -.phpunit.result.cache +/.phpunit.result.cache # Transient and temporary generated files /var/ diff --git a/README.md b/README.md index d3421a2..f767e56 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,13 @@ Modify `local_config.php` according to your local development environment. * Database: -Create a new MySQL/MariaDB database using `sql/database.sql`, create database -schema `sql/schema.sql` and insert fixtures using `sql/fixtures.sql`. +Create a new MySQL, MariaDB, Percona Server, or equivalent database using +`sql/database.sql`, create database schema `sql/schema.sql` and insert demo data +fixtures by running: + +```bash +php scripts/console app:fixtures:insert +``` ## Tests @@ -42,9 +47,10 @@ Source code of this application is structured in the following directories: ├─ prepend.php # Autoloader, DB connection, container, app initialization └─ ... └─ scripts/ # Command line development tools and scripts + ├─ console # Application's main script for running CLI commands ├─ cron/ # Various systems scripts to run periodically on the server └─ ... - ├─ sql/ # Database schema and fixtures + ├─ sql/ # Database schema └─ src/ # Application source code classes ├─ Horde/ # https://www.horde.org/libraries/Horde_Text_Diff └─ ... diff --git a/composer.json b/composer.json index abec91d..4d02265 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,9 @@ }, "require-dev": { "ext-pdo_sqlite": "*", - "phpunit/phpunit": "^8.1.5" + "fzaninotto/faker": "^1.8", + "phpunit/phpunit": "^8.1.5", + "symfony/console": "^4.2" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 8111618..898739c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "994d11b989a10210dc468c7046539905", + "content-hash": "e90209efe832cd89e50a5f742fd551a8", "packages": [], "packages-dev": [ { @@ -64,6 +64,56 @@ "time": "2019-03-17T17:37:11+00:00" }, { + "name": "fzaninotto/faker", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/f72816b43e74063c8b10357394b6bba8cb1c10de", + "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "ext-intl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7", + "squizlabs/php_codesniffer": "^1.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "time": "2018-07-12T10:23:15+00:00" + }, + { "name": "myclabs/deep-copy", "version": "1.9.1", "source": { @@ -1332,6 +1382,149 @@ "time": "2016-10-03T07:35:21+00:00" }, { + "name": "symfony/console", + "version": "v4.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "e2840bb38bddad7a0feaf85931e38fdcffdb2f81" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/e2840bb38bddad7a0feaf85931e38fdcffdb2f81", + "reference": "e2840bb38bddad7a0feaf85931e38fdcffdb2f81", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/contracts": "^1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2019-04-08T14:23:48+00:00" + }, + { + "name": "symfony/contracts", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/contracts.git", + "reference": "d3636025e8253c6144358ec0a62773cae588395b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/contracts/zipball/d3636025e8253c6144358ec0a62773cae588395b", + "reference": "d3636025e8253c6144358ec0a62773cae588395b", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "require-dev": { + "psr/cache": "^1.0", + "psr/container": "^1.0", + "symfony/polyfill-intl-idn": "^1.10" + }, + "suggest": { + "psr/cache": "When using the Cache contracts", + "psr/container": "When using the Service contracts", + "symfony/cache-contracts-implementation": "", + "symfony/event-dispatcher-implementation": "", + "symfony/http-client-contracts-implementation": "", + "symfony/service-contracts-implementation": "", + "symfony/translation-contracts-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\": "" + }, + "exclude-from-classmap": [ + "**/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A set of abstractions extracted out of the Symfony components", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-04-27T14:29:50+00:00" + }, + { "name": "symfony/polyfill-ctype", "version": "v1.11.0", "source": { @@ -1390,6 +1583,65 @@ "time": "2019-02-06T07:57:58+00:00" }, { + "name": "symfony/polyfill-mbstring", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { "name": "theseer/tokenizer", "version": "1.1.2", "source": { diff --git a/config/container.php b/config/container.php index 5cf2979..59e3045 100644 --- a/config/container.php +++ b/config/container.php @@ -81,4 +81,25 @@ $container->set(App\Utils\Uploader::class, function ($c) { return new App\Utils\Uploader(); }); +$container->set(App\Fixtures\AppFixtures::class, function ($c) { + return new App\Fixtures\AppFixtures($c->get(\PDO::class), Faker\Factory::create(), $c->get('bug_statuses')); +}); + +$container->set(App\Command\InsertFixturesCommand::class, function ($c) { + $insertFixturesCommand = new App\Command\InsertFixturesCommand( + $c->get(\PDO::class), + $c->get(App\Fixtures\AppFixtures::class), + $c->get('env') + ); + + return $insertFixturesCommand; +}); + +$container->set(Symfony\Component\Console\Application::class, function ($c) { + $application = new Symfony\Component\Console\Application(); + $application->add($c->get(App\Command\InsertFixturesCommand::class)); + + return $application; +}); + return $container; diff --git a/config/parameters.php b/config/parameters.php index 9ff0d78..0cf1b72 100644 --- a/config/parameters.php +++ b/config/parameters.php @@ -70,4 +70,24 @@ return [ * Templates directory. */ 'templates_dir' => __DIR__.'/../templates', + + /** + * Bug statuses. + */ + 'bug_statuses' => [ + 'Open' => 'Opn', + 'Not a bug' => 'Nab', + 'Feedback' => 'Fbk', + 'No Feedback' => 'NoF', + 'Wont fix' => 'Wfx', + 'Duplicate' => 'Dup', + 'Critical' => 'Ctl', + 'Assigned' => 'Asn', + 'Analyzed' => 'Ana', + 'Verified' => 'Ver', + 'Suspended' => 'Sus', + 'Closed' => 'Csd', + 'Spam' => 'Spm', + 'Re-Opened' => 'ReO', + ], ]; diff --git a/scripts/console b/scripts/console new file mode 100755 index 0000000..6c2f44f --- /dev/null +++ b/scripts/console @@ -0,0 +1,15 @@ +#!/usr/bin/env php +<?php + +/** + * Main command line console entry application. + * + * SYNOPSIS: + * php scripts/console [command] [<options>] [arguments] + */ + +require_once __DIR__.'/../include/prepend.php'; + +use Symfony\Component\Console\Application; + +$container->get(Application::class)->run(); diff --git a/sql/fixtures.sql b/sql/fixtures.sql deleted file mode 100644 index c4f3bf7..0000000 --- a/sql/fixtures.sql +++ /dev/null @@ -1,4 +0,0 @@ --- Default pseudo packages (common for all projects) - -INSERT INTO bugdb_pseudo_packages SET id = '1', parent = '0', name = 'Web Site', long_name = 'Web Site', project = ''; -INSERT INTO bugdb_pseudo_packages SET id = '2', parent = '1', name = 'Bug System', long_name = 'Bug System', project = ''; diff --git a/src/Command/InsertFixturesCommand.php b/src/Command/InsertFixturesCommand.php new file mode 100644 index 0000000..b833f2c --- /dev/null +++ b/src/Command/InsertFixturesCommand.php @@ -0,0 +1,100 @@ +<?php + +namespace App\Command; + +use App\Fixtures\AppFixtures; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ConfirmationQuestion; + +/** + * Command line command for inserting development data fixtures. + */ +class InsertFixturesCommand extends Command +{ + /** + * Database handler. + * @var \PDO + */ + private $dbh; + + /** + * Application data fixtures. + * @var AppFixtures + */ + private $fixtures; + + /** + * Current application environment. + * @var string + */ + private $environment; + + /** + * Class constructor. + */ + public function __construct(\PDO $dbh, AppFixtures $fixtures, string $environment) + { + $this->dbh = $dbh; + $this->fixtures = $fixtures; + $this->environment = $environment; + + parent::__construct(); + } + + /** + * Configure command. + */ + protected function configure() + { + $this->setName('app:fixtures:insert') + ->setDescription('Insert database fixtures.') + ->setHelp('This command inserts demo data fixtures in the database.') + ; + } + + /** + * Run the command, for example, bin/console app:generate-fixtures. It can + * be executed only in development environment as a safety measure to not + * delete something important. + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + if ('dev' !== $this->environment) { + $output->writeln('This command can be executed only in development.'); + + return; + } + + $helper = $this->getHelper('question'); + $question = new ConfirmationQuestion( + 'This will erase entire database. Are you sure you want to continue? [y/N]', + false + ); + + if (!$helper->ask($input, $output, $question)) { + $output->writeln('Exiting...'); + + return; + } + + // Delete all existing categories and add new ones. + $output->writeln('Deleting existing categories'); + $this->dbh->query('DELETE FROM bugdb_pseudo_packages'); + $output->writeln('Adding new categories'); + $this->fixtures->insertCategories(); + + // Delete all current bug reports and add new ones. + $output->writeln('Deleting existing bug reports'); + $this->dbh->query('DELETE FROM bugdb'); + $output->writeln('Adding new bug reports'); + $this->fixtures->insertBugs(); + + // Delete all current reasons and add new ones. + $output->writeln('Deleting existing reasons'); + $this->dbh->query('DELETE FROM bugdb_resolves'); + $output->writeln('Adding new reasons'); + $this->fixtures->insertReasons(); + } +} diff --git a/src/Fixtures/AppFixtures.php b/src/Fixtures/AppFixtures.php new file mode 100644 index 0000000..509f987 --- /dev/null +++ b/src/Fixtures/AppFixtures.php @@ -0,0 +1,645 @@ +<?php + +namespace App\Fixtures; + +use App\Database; +use App\Entity\Category; +use App\Entity\Package; +use Faker\Generator; + +/** + * Data fixtures for database. It uses Faker library for generating fixtures + * data. + */ +class AppFixtures +{ + /** + * Database handler. + * @var \PDO + */ + private $dbh; + + /** + * Faker utility. + * @var Generator + */ + private $faker; + + /** + * Bug statuses. + * @var array + */ + private $bugStatuses; + + /** + * Number of generated users. + */ + private const BUGS_COUNT = 1000; + + /** + * Class constructor to set injected dependencies. + */ + public function __construct(\PDO $dbh, Generator $faker, array $bugStatuses) + { + $this->dbh = $dbh; + $this->faker = $faker; + $this->bugStatuses = $bugStatuses; + } + + /** + * Insert data in the bugs categories table. + */ + public function insertCategories() + { + $sql = "INSERT INTO bugdb_pseudo_packages ( + `parent`, + `name`, + `long_name`, + `project`, + `list_email`, + `disabled` + ) VALUES ( + :parent, + :name, + :long_name, + :project, + :list_email, + :disabled + ) + "; + + $catIds = []; + + foreach ($this->getCategories() as $category => $data) { + $statement = $this->dbh->prepare($sql); + $parent = isset($data['parent']) ? $catIds[$data['parent']] : 0; + + $statement->execute([ + ':parent' => $parent, + ':name' => $category, + ':long_name' => $data['long_name'] ?? $category, + ':project' => $data['project'] ?? '', + ':list_email' => $data['list_email'] ?? '', + ':disabled' => $data['disabled'] ?? 0, + ]); + + $catIds[$category] = $this->dbh->lastInsertId(); + } + } + + /** + * Insert fixtures in users table. + */ + public function insertBugs() + { + $sql = "INSERT INTO bugdb ( + `package_name`, + `bug_type`, + `email`, + `reporter_name`, + `sdesc`, + `ldesc`, + `php_version`, + `php_os`, + `status`, + `ts1`, + `ts2`, + `assign`, + `passwd`, + `registered`, + `block_user_comment`, + `cve_id`, + `private`, + `visitor_ip` + ) VALUES ( + :package_name, + :bug_type, + :email, + :reporter_name, + :sdesc, + :ldesc, + :php_version, + :php_os, + :status, + :ts1, + :ts2, + :assign, + :passwd, + :registered, + :block_user_comment, + :cve_id, + :private, + INET6_ATON(:visitor_ip) + ) + "; + + $bugs = $this->getBugs(); + + foreach ($bugs as $data) { + $statement = $this->dbh->prepare($sql); + + $statement->execute([ + ':package_name' => $data['package_name'], + ':bug_type' => $data['bug_type'], + ':email' => $data['email'], + ':reporter_name' => $data['reporter_name'], + ':sdesc' => $data['sdesc'], + ':ldesc' => $data['ldesc'], + ':php_version' => $data['php_version'], + ':php_os' => $data['php_os'], + ':status' => $data['status'], + ':ts1' => date('Y-m-d H:i:s'), + ':ts2' => null, + ':assign' => $data['assign'], + ':passwd' => $data['passwd'], + ':registered' => $data['registered'], + ':block_user_comment' => $data['block_user_comment'], + ':cve_id' => $data['cve_id'], + ':private' => $data['private'], + ':visitor_ip' => $data['visitor_ip'], + ]); + } + } + + /** + * Insert data in the bugs reasons table. + */ + public function insertReasons() + { + $sql = "INSERT INTO bugdb_resolves ( + `name`, + `status`, + `title`, + `message`, + `project`, + `package_name`, + `webonly` + ) VALUES ( + :name, + :status, + :title, + :message, + :project, + :package_name, + :webonly + ) + "; + + foreach ($this->getReasons() as $data) { + $statement = $this->dbh->prepare($sql); + + $statement->execute([ + ':name' => $data['name'], + ':status' => $data['status'], + ':title' => $data['title'], + ':message' => $data['message'], + ':project' => $data['project'], + ':package_name' => $data['package_name'], + ':webonly' => $data['webonly'], + ]); + } + } + + /** + * Get generated demo bug reports data with each bug having password set to + * "password". + */ + private function getBugs() + { + $bugs = []; + + // Demo secret password is always password for all bug reports + $password = bugs_get_hash('password'); + + $categories = $this->dbh->query("SELECT id, name FROM bugdb_pseudo_packages")->fetchAll(\PDO::FETCH_KEY_PAIR); + + // More random bug reports + for ($i = 0; $i < self::BUGS_COUNT; $i++) { + $username = $this->faker->unique()->userName; + $username = substr($username, 0, 16); + $username = str_replace('.', '', $username); + + $bugs[] = [ + 'package_name' => $categories[array_rand($categories, 1)], + 'bug_type' => '', + 'email' => $username.'@example.com', + 'reporter_name' => $this->faker->firstName.' '.$this->faker->lastName, + 'sdesc' => $this->faker->text(80), + 'ldesc' => $this->faker->paragraph, + 'php_version' => '7.'.rand(0, 4).'.'.rand(0, 30), + 'php_os' => $this->faker->text(32), + 'status' => array_rand($this->bugStatuses), + 'ts1' => '', + 'ts2' => '', + 'assign' => '', + 'passwd' => $password, + 'registered' => 0, + 'block_user_comment' => 'N', + 'cve_id' => '', + 'private' => 'N', + 'visitor_ip' => $this->faker->ipv4, + ]; + } + + return $bugs; + } + + /** + * Categories. + */ + private function getCategories() + { + return [ + 'General Issues' => [], + 'PDO related' => [], + 'Compile Issues' => [], + 'Configuration Issues' => [], + 'Web Server problem' => [], + 'Calendar problems' => [], + 'Compression related' => [], + 'Directory/Filesystem functions' => [], + 'Database Functions' => [], + 'Data Exchange functions' => [], + 'Extensibility Functions' => [], + 'Graphics related' => [], + 'Languages/Translation' => [], + 'Mail related' => [], + 'Encryption and hash functions' => [], + 'Network functions' => [], + 'PDF functions' => [], + 'Programming Data Structures' => [], + 'Regular expressions' => [], + 'Spelling functions' => [], + 'XML functions' => [], + 'Unicode Issues' => [], + 'Unknown/Other functions' => [], + 'PECL' => [], + 'phpdbg' => [], + 'PHP Language Specification' => [], + + // General Issues + 'Doc Build (PhD) problem' => [ + 'parent' => 'General Issues', + ], + 'Documentation problem' => [ + 'parent' => 'General Issues', + ], + 'Documentation translation problem' => [ + 'parent' => 'General Issues', + ], + 'Filter relate' => [ + 'parent' => 'General Issues', + ], + 'Online Documentation Editor problem' => [ + 'parent' => 'General Issues', + ], + 'Opcache' => [ + 'parent' => 'General Issues', + ], + 'Output Control' => [ + 'parent' => 'General Issues', + ], + 'Performance problem' => [ + 'parent' => 'General Issues', + ], + 'PHAR related' => [ + 'parent' => 'General Issues', + ], + 'PHP-GTK related' => [ + 'parent' => 'General Issues', + ], + 'Systems problem' => [ + 'parent' => 'General Issues', + ], + 'Website problem' => [ + 'parent' => 'General Issues', + ], + 'Reflection related' => [ + 'parent' => 'General Issues', + ], + 'Reproducible crash' => [ + 'parent' => 'General Issues', + ], + 'Scripting Engine problem' => [ + 'parent' => 'General Issues', + ], + 'Session related' => [ + 'parent' => 'General Issues', + ], + 'SPL related' => [ + 'parent' => 'General Issues', + ], + 'Streams related' => [ + 'parent' => 'General Issues', + ], + 'Testing related' => [ + 'parent' => 'General Issues', + ], + + // PDO related + 'PDO Core' => [ + 'parent' => 'PDO related', + ], + 'PDO DBlib' => [ + 'parent' => 'PDO related', + ], + 'PDO Firebird' => [ + 'parent' => 'PDO related', + ], + 'PDO MySQL' => [ + 'parent' => 'PDO related', + ], + 'PDO OCI' => [ + 'parent' => 'PDO related', + ], + 'PDO ODBC' => [ + 'parent' => 'PDO related', + ], + 'PDO PgSQL' => [ + 'parent' => 'PDO related', + ], + 'PDO SQLite' => [ + 'parent' => 'PDO related', + ], + + // Compile Issues + 'Compile Failure' => [ + 'parent' => 'Compile Issues', + ], + 'Compile Warning' => [ + 'parent' => 'Compile Issues', + ], + + // Configuration Issues + 'Dynamic loading' => [ + 'parent' => 'Configuration Issues', + ], + 'PHP options/info functions' => [ + 'parent' => 'Configuration Issues', + ], + 'Safe Mode/open_basedir related' => [ + 'parent' => 'Configuration Issues', + ], + 'Windows Installer related' => [ + 'parent' => 'Configuration Issues', + ], + + // TODO add more + ]; + } + + private function getReasons(): array + { + return [ + [ + 'name' => 'trysnapshot54', + 'status' => 'Feedback', + 'title' => 'Try a snapshot (PHP 5.4)', + 'message' => 'Please try using this snapshot: + + http://snaps.php.net/php5.4-latest.tar.gz + + For Windows: + + http://windows.php.net/snapshots/', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'trysnapshot55', + 'status' => 'Feedback', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'trysnapshottrunk', + 'status' => 'Feedback', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'fixed', + 'status' => 'Closed', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'fixed', + 'status' => 'Closed', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'fixed', + 'status' => 'Closed', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'fixed', + 'status' => 'Closed', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'fixed', + 'status' => 'Closed', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'fixed', + 'status' => 'Closed', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'fixed', + 'status' => 'Closed', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'alreadyfixed', + 'status' => 'Closed', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'needtrace', + 'status' => 'Feedback', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'needscript', + 'status' => 'Feedback', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'oldversion', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'support', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'nofeedback', + 'status' => 'No Feedback', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 1, + ], + [ + 'name' => 'notwrong', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'notenoughinfo', + 'status' => 'Feedback', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'submittedtwice', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'globals', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'php4', + 'status' => 'Wont fix', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'dst', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'isapi', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'gnused', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'float', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'nozend', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + [ + 'name' => 'mysqlcfg', + 'status' => 'Not a bug', + 'title' => '', + 'message' => '', + 'project' => 'php', + 'package_name' => '', + 'webonly' => 0, + ], + ]; + } +} diff --git a/tests/Unit/Command/InsertFixturesCommandTest.php b/tests/Unit/Command/InsertFixturesCommandTest.php new file mode 100644 index 0000000..bbbacc6 --- /dev/null +++ b/tests/Unit/Command/InsertFixturesCommandTest.php @@ -0,0 +1,65 @@ +<?php declare(strict_types=1); + +/** + * Test for insert fixtures command. + */ + +namespace App\Tests\Command; + +use App\Command\InsertFixturesCommand; +use App\Fixtures\AppFixtures; +use Faker\Factory; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Tester\CommandTester; + +use PHPUnit\Framework\TestCase; + +class InsertFixturesCommandTest extends TestCase +{ + private $dbh; + + public function setUp(): void + { + $this->dbh = new \PDO('sqlite::memory:'); + } + + public function testExecute(): void + { + $insertFixturesCommand = new InsertFixturesCommand( + $this->dbh, + new AppFixtures($this->dbh, Factory::create(), []), + 'dev' + ); + + $application = new Application(); + $application->add($insertFixturesCommand); + + $command = $application->find('app:fixtures:insert'); + $commandTester = new CommandTester($command); + $commandTester->setInputs(['n']); + $exitCode = $commandTester->execute(['command' => $command->getName()]); + + $this->assertRegExp('/.../', $commandTester->getDisplay()); + $this->assertSame(0, $exitCode); + } + + public function testExecuteInProdEnv(): void + { + $insertFixturesCommand = new InsertFixturesCommand( + $this->dbh, + new AppFixtures($this->dbh, Factory::create(), []), + 'prod' + ); + + $application = new Application(); + $application->add($insertFixturesCommand); + + $command = $application->find('app:fixtures:insert'); + $commandTester = new CommandTester($command); + $commandTester->setInputs(['n']); + $exitCode = $commandTester->execute(['command' => $command->getName()]); + + $this->assertRegExp('/This command can be executed only in dev/', $commandTester->getDisplay()); + $this->assertSame(0, $exitCode); + } +}