diff --git a/composer.json b/composer.json index 2c3dcc0c..20dcc5d7 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,7 @@ "fakerphp/faker": "^1.23", "guzzlehttp/guzzle": "^7.8", "lion/command": "^3.1", - "lion/database": "^9.0", + "lion/database": "^9.2", "lion/files": "^6.0", "lion/helpers": "^3.2", "lion/mailer": "^6.0", diff --git a/composer.lock b/composer.lock index ce4129f2..ad2cf9ae 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": "a02fc1c306f62a6d6628a2c4771bd977", + "content-hash": "a6e321c77d65debcf18b5931a7d5df0f", "packages": [ { "name": "carbonphp/carbon-doctrine-types", @@ -943,16 +943,16 @@ }, { "name": "lion/database", - "version": "v9.2.2", + "version": "v9.2.3", "source": { "type": "git", "url": "https://github.com/lion-packages/database.git", - "reference": "f7506d008ebf33bc40dbbe123d34ef5abc1a78f6" + "reference": "f730b747088bb8e73adf50879edb81181777b536" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lion-packages/database/zipball/f7506d008ebf33bc40dbbe123d34ef5abc1a78f6", - "reference": "f7506d008ebf33bc40dbbe123d34ef5abc1a78f6", + "url": "https://api.github.com/repos/lion-packages/database/zipball/f730b747088bb8e73adf50879edb81181777b536", + "reference": "f730b747088bb8e73adf50879edb81181777b536", "shasum": "" }, "require": { @@ -978,7 +978,7 @@ "issues": "https://github.com/lion-packages/database/issues", "source": "https://github.com/lion-packages/database" }, - "time": "2024-04-06T14:20:47+00:00" + "time": "2024-04-16T18:51:54+00:00" }, { "name": "lion/dependency-injection", diff --git a/config/queue.php b/config/queue.php new file mode 100644 index 00000000..2afdbc40 --- /dev/null +++ b/config/queue.php @@ -0,0 +1,53 @@ +task_queue_data, true); + + try { + Mailer::account(env('MAIL_NAME')) + ->subject('Test Priority') + ->from($data->email, 'Sleon') + ->addAddress('jjerez@dev.com', 'Jjerez') + ->body($data->template) + ->priority(Priority::HIGH) + ->send(); + } catch (Exception $e) { + TaskQueue::edit($queue, TaskStatusEnum::FAILED); + + logger($e->getMessage(), LogTypeEnum::ERROR->value, [ + 'idtask_queue' => $queue->idtask_queue, + 'task_queue_type' => $queue->task_queue_type, + 'task_queue_data' => $queue->task_queue_data + ]); + } + } + ) +); diff --git a/lion b/lion index 33b125a2..d914cd24 100644 --- a/lion +++ b/lion @@ -18,6 +18,7 @@ require_once(__DIR__ . '/vendor/autoload.php'); use Dotenv\Dotenv; use Lion\Bundle\Commands\CommandHandler; use Lion\Database\Driver; +use Lion\Mailer\Mailer; /** * ----------------------------------------------------------------------------- @@ -36,27 +37,68 @@ Dotenv::createImmutable(__DIR__)->load(); * */ Driver::run([ - 'default' => $_ENV['DB_NAME'], + 'default' => env('DB_NAME'), 'connections' => [ - $_ENV['DB_NAME'] => [ - 'type' => $_ENV['DB_TYPE'], - 'host' => $_ENV['DB_HOST'], - 'port' => $_ENV['DB_PORT'], - 'dbname' => $_ENV['DB_NAME'], - 'user' => $_ENV['DB_USER'], - 'password' => $_ENV['DB_PASSWORD'] + env('DB_NAME') => [ + 'type' => env('DB_TYPE'), + 'host' => env('DB_HOST'), + 'port' => env('DB_PORT'), + 'dbname' => env('DB_NAME'), + 'user' => env('DB_USER'), + 'password' => env('DB_PASSWORD') ], - $_ENV['DB_NAME_TEST'] => [ - 'type' => $_ENV['DB_TYPE_TEST'], - 'host' => $_ENV['DB_HOST_TEST'], - 'port' => $_ENV['DB_PORT_TEST'], - 'dbname' => $_ENV['DB_NAME'], - 'user' => $_ENV['DB_USER_TEST'], - 'password' => $_ENV['DB_PASSWORD_TEST'] + env('DB_NAME_TEST') => [ + 'type' => env('DB_TYPE_TEST'), + 'host' => env('DB_HOST_TEST'), + 'port' => env('DB_PORT_TEST'), + 'dbname' => env('DB_NAME'), + 'user' => env('DB_USER_TEST'), + 'password' => env('DB_PASSWORD_TEST') ] ] ]); +/** + * ----------------------------------------------------------------------------- + * Start mail service + * ----------------------------------------------------------------------------- + * describe connections to establish connecting to multiple databases + * ----------------------------------------------------------------------------- + **/ + +Mailer::initialize([ + env('MAIL_NAME', 'lion-app') => [ + 'name' => env('MAIL_NAME', 'lion-app'), + 'type' => env('MAIL_TYPE', 'symfony'), + 'host' => env('MAIL_HOST', 'mailhog'), + 'username' => env('MAIL_USER_NAME', 'lion-app'), + 'password' => env('MAIL_PASSWORD', 'lion'), + 'port' => (int) env('MAIL_PORT', 1025), + 'encryption' => env('MAIL_ENCRYPTION', 'tls'), + 'debug' => env('MAIL_DEBUG', false) + ], + env('MAIL_NAME_SUPP', 'lion-app') => [ + 'name' => env('MAIL_NAME_SUPP', 'lion-app'), + 'type' => env('MAIL_TYPE_SUPP', 'symfony'), + 'host' => env('MAIL_HOST_SUPP', 'mailhog'), + 'username' => env('MAIL_USER_NAME_SUPP', 'lion-app'), + 'password' => env('MAIL_PASSWORD_SUPP', 'lion'), + 'port' => (int) env('MAIL_PORT_SUPP', 1025), + 'encryption' => env('MAIL_ENCRYPTION_SUPP', 'tls'), + 'debug' => env('MAIL_DEBUG_SUPP', false) + ] +], env('MAIL_NAME', 'lion-app')); + +/** + * ----------------------------------------------------------------------------- + * Queued Tasks + * ----------------------------------------------------------------------------- + * This is where you can register the processes required for your queued tasks + * ----------------------------------------------------------------------------- + **/ + +include(__DIR__ . '/config/queue.php'); + /** * ----------------------------------------------------------------------------- * Run The lion Application diff --git a/routes/web.php b/routes/web.php index 596dcebf..3927dd21 100644 --- a/routes/web.php +++ b/routes/web.php @@ -19,6 +19,8 @@ use Dotenv\Dotenv; use Lion\Bundle\Helpers\ExceptionCore; use Lion\Bundle\Helpers\Http\Routes; +use Lion\Database\Driver; +use Lion\Mailer\Mailer; /** * ----------------------------------------------------------------------------- @@ -40,6 +42,65 @@ Dotenv::createImmutable(__DIR__ . '/../')->load(); +/** + * ----------------------------------------------------------------------------- + * Database initialization + * ----------------------------------------------------------------------------- + * */ + +Driver::run([ + 'default' => env('DB_NAME'), + 'connections' => [ + env('DB_NAME') => [ + 'type' => env('DB_TYPE'), + 'host' => env('DB_HOST'), + 'port' => env('DB_PORT'), + 'dbname' => env('DB_NAME'), + 'user' => env('DB_USER'), + 'password' => env('DB_PASSWORD') + ], + env('DB_NAME_TEST') => [ + 'type' => env('DB_TYPE_TEST'), + 'host' => env('DB_HOST_TEST'), + 'port' => env('DB_PORT_TEST'), + 'dbname' => env('DB_NAME'), + 'user' => env('DB_USER_TEST'), + 'password' => env('DB_PASSWORD_TEST') + ] + ] +]); + +/** + * ----------------------------------------------------------------------------- + * Start mail service + * ----------------------------------------------------------------------------- + * describe connections to establish connecting to multiple databases + * ----------------------------------------------------------------------------- + **/ + +Mailer::initialize([ + env('MAIL_NAME', 'lion-app') => [ + 'name' => env('MAIL_NAME', 'lion-app'), + 'type' => env('MAIL_TYPE', 'symfony'), + 'host' => env('MAIL_HOST', 'mailhog'), + 'username' => env('MAIL_USER_NAME', 'lion-app'), + 'password' => env('MAIL_PASSWORD', 'lion'), + 'port' => (int) env('MAIL_PORT', 1025), + 'encryption' => env('MAIL_ENCRYPTION', 'tls'), + 'debug' => env('MAIL_DEBUG', false) + ], + env('MAIL_NAME_SUPP', 'lion-app') => [ + 'name' => env('MAIL_NAME_SUPP', 'lion-app'), + 'type' => env('MAIL_TYPE_SUPP', 'symfony'), + 'host' => env('MAIL_HOST_SUPP', 'mailhog'), + 'username' => env('MAIL_USER_NAME_SUPP', 'lion-app'), + 'password' => env('MAIL_PASSWORD_SUPP', 'lion'), + 'port' => (int) env('MAIL_PORT_SUPP', 1025), + 'encryption' => env('MAIL_ENCRYPTION_SUPP', 'tls'), + 'debug' => env('MAIL_DEBUG_SUPP', false) + ] +], env('MAIL_NAME', 'lion-app')); + /** * ----------------------------------------------------------------------------- * Web Routes diff --git a/src/LionBundle/Commands/Lion/New/ClassCommand.php b/src/LionBundle/Commands/Lion/New/ClassCommand.php new file mode 100644 index 00000000..4ef74909 --- /dev/null +++ b/src/LionBundle/Commands/Lion/New/ClassCommand.php @@ -0,0 +1,131 @@ +classFactory = $classFactory; + } + + /** + * @required + */ + public function setStore(Store $store): void + { + $this->store = $store; + } + + /** + * Configures the current command + * + * @return void + */ + protected function configure(): void + { + $this + ->setName('new:class') + ->setDescription('Command needed to create classes') + ->addArgument('class', InputArgument::REQUIRED, 'Class name'); + } + + /** + * Executes the current command. + * + * This method is not abstract because you can use this class + * as a concrete class. In this case, instead of defining the + * execute() method, you set the code to execute by passing + * a Closure to the setCode() method. + * + * @param InputInterface $input [InputInterface is the interface implemented + * by all input classes] + * @param OutputInterface $output [OutputInterface is the interface + * implemented by all Output classes] + * + * @return int 0 if everything went fine, or an exit code + * + * @throws LogicException When this abstract method is not implemented + * + * @see setCode() + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $class = $input->getArgument('class'); + + $this->classFactory->classFactory('app/', $class); + + $folder = $this->classFactory->getFolder(); + + $namespace = $this->classFactory->getNamespace(); + + $class = $this->classFactory->getClass(); + + $this->store->folder($folder); + + $this->classFactory + ->create($class, ClassFactory::PHP_EXTENSION, $folder) + ->add( + <<close(); + + $output->writeln($this->warningOutput("\t>> CLASS: {$class}")); + + $output->writeln($this->successOutput("\t>> CLASS: the '{$namespace}\\{$class}' class has been generated")); + + return Command::SUCCESS; + } +} diff --git a/src/LionBundle/Commands/Lion/New/HtmlCommand.php b/src/LionBundle/Commands/Lion/New/HtmlCommand.php new file mode 100644 index 00000000..15d9fc31 --- /dev/null +++ b/src/LionBundle/Commands/Lion/New/HtmlCommand.php @@ -0,0 +1,157 @@ +classFactory = $classFactory; + } + + /** + * @required + */ + public function setStore(Store $store): void + { + $this->store = $store; + } + + /** + * Configures the current command + * + * @return void + */ + protected function configure(): void + { + $this + ->setName('new:html') + ->setDescription('Command needed to create new HTML templates') + ->addArgument('html', InputArgument::OPTIONAL, 'Html name', 'ExampleHtml'); + } + + /** + * Executes the current command. + * + * This method is not abstract because you can use this class + * as a concrete class. In this case, instead of defining the + * execute() method, you set the code to execute by passing + * a Closure to the setCode() method. + * + * @param InputInterface $input [InputInterface is the interface implemented + * by all input classes] + * @param OutputInterface $output [OutputInterface is the interface + * implemented by all Output classes] + * + * @return int 0 if everything went fine, or an exit code + * + * @throws LogicException When this abstract method is not implemented + * + * @see setCode() + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $html = $input->getArgument('html'); + + $this->classFactory->classFactory('app/Html/', $html); + + $folder = $this->classFactory->getFolder(); + + $namespace = $this->classFactory->getNamespace(); + + $class = $this->classFactory->getClass(); + + $this->store->folder($folder); + + $this->classFactory + ->create($class, ClassFactory::PHP_EXTENSION, $folder) + ->add( + <<add( + << + + + + + HTML Template + + +

HTML Template (--REPLACE--)

+ + + HTML + ); + + return \$this; + } + } + + PHP + ) + ->close(); + + $output->writeln($this->warningOutput("\t>> HTML: {$class}")); + + $output->writeln($this->successOutput("\t>> HTML: the '{$namespace}\\{$class}' html has been generated")); + + return Command::SUCCESS; + } +} diff --git a/src/LionBundle/Commands/Lion/Schedule/RunQueuedTasksCommand.php b/src/LionBundle/Commands/Lion/Schedule/RunQueuedTasksCommand.php new file mode 100644 index 00000000..f73eb9b8 --- /dev/null +++ b/src/LionBundle/Commands/Lion/Schedule/RunQueuedTasksCommand.php @@ -0,0 +1,108 @@ +setName('schedule:run') + ->setDescription('Query and execute queued tasks in the background'); + } + + /** + * Executes the current command. + * + * This method is not abstract because you can use this class + * as a concrete class. In this case, instead of defining the + * execute() method, you set the code to execute by passing + * a Closure to the setCode() method. + * + * @param InputInterface $input [InputInterface is the interface implemented + * by all input classes] + * @param OutputInterface $output [OutputInterface is the interface + * implemented by all Output classes] + * + * @return int 0 if everything went fine, or an exit code + * + * @throws LogicException When this abstract method is not implemented + * + * @see setCode() + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + while (true) { + $data = DB::table('task_queue') + ->select() + ->getAll(); + + if (isSuccess($data)) { + $output->writeln($this->infoOutput("\t>> SCHEDULE: no queued tasks available")); + + TaskQueue::pause(60); + + continue; + } + + foreach ($data as $queue) { + $data = (object) json_decode($queue->task_queue_data, true); + + $output->writeln($this->warningOutput("\t>> SCHEDULE: {$queue->task_queue_type}")); + + $output->writeln($this->successOutput("\t>> SCHEDULE: {$data->template} [PROCESSING]")); + + if (TaskStatusEnum::COMPLETED->value === $queue->task_queue_status) { + $output->writeln($this->successOutput("\t>> SCHEDULE: {$data->template} [COMPLETED]")); + + $output->writeln($this->successOutput("\t>> SCHEDULE: {$data->template} [REMOVED]")); + + TaskQueue::remove($queue); + + TaskQueue::pause(1); + + continue; + } + + TaskQueue::edit($queue, TaskStatusEnum::IN_PROGRESS); + + $output->writeln($this->successOutput("\t>> SCHEDULE: {$data->template} [IN-PROGRESS]")); + + TaskQueue::pause(1); + + $callable = TaskQueue::get($queue->task_queue_type); + + $callable($queue); + + TaskQueue::pause(1); + + TaskQueue::edit($queue, TaskStatusEnum::COMPLETED); + + $output->writeln($this->successOutput("\t>> SCHEDULE: {$data->template} [COMPLETED]")); + + TaskQueue::pause(1); + } + } + + return Command::SUCCESS; + } +} diff --git a/src/LionBundle/Commands/Lion/Schedule/ScheduleSchemaCommand.php b/src/LionBundle/Commands/Lion/Schedule/ScheduleSchemaCommand.php new file mode 100644 index 00000000..fb716f6f --- /dev/null +++ b/src/LionBundle/Commands/Lion/Schedule/ScheduleSchemaCommand.php @@ -0,0 +1,138 @@ +setName('schedule:schema') + ->setDescription('Create the necessary tables in the database to process the queued tasks'); + } + + /** + * Initializes the command after the input has been bound and before the + * input is validated + * + * This is mainly useful when a lot of commands extends one main command + * where some things need to be initialized based on the input arguments and + * options + * + * @param InputInterface $input [InputInterface is the interface implemented + * by all input classes] + * @param OutputInterface $output [OutputInterface is the interface + * implemented by all Output classes] + * + * @see InputInterface::bind() + * @see InputInterface::validate() + * + * @return void + */ + protected function initialize(InputInterface $input, OutputInterface $output): void + { + } + + /** + * Interacts with the user + * + * This method is executed before the InputDefinition is validated + * + * This means that this is the only place where the command can + * interactively ask for values of missing required arguments + * + * @param InputInterface $input [InputInterface is the interface implemented + * by all input classes] + * @param OutputInterface $output [OutputInterface is the interface + * implemented by all Output classes] + * + * @return void + */ + protected function interact(InputInterface $input, OutputInterface $output): void + { + } + + /** + * Executes the current command. + * + * This method is not abstract because you can use this class + * as a concrete class. In this case, instead of defining the + * execute() method, you set the code to execute by passing + * a Closure to the setCode() method. + * + * @param InputInterface $input [InputInterface is the interface implemented + * by all input classes] + * @param OutputInterface $output [OutputInterface is the interface + * implemented by all Output classes] + * + * @return int 0 if everything went fine, or an exit code + * + * @throws LogicException When this abstract method is not implemented + * + * @see setCode() + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $connection = $this->selectConnection($input, $output); + + $this->createScheduleTable($connection); + + $output->writeln($this->successOutput("\t>> SCHEDULE: the schema for queued tasks has been created")); + + return Command::SUCCESS; + } + + /** + * Generate the schema for the queued tasks + * + * @param string $connection [Connection name where the table is created] + * + * @return void + * + * @throws Exception [Catch an exception if the schema of the queued tasks + * is not created] + */ + private function createScheduleTable(string $connection): void + { + $response = Schema::connection($connection) + ->createTable('task_queue', function () { + Schema::int('idtask_queue')->notNull()->autoIncrement()->primaryKey(); + Schema::varchar('task_queue_type', 255)->notNull(); + Schema::json('task_queue_data')->notNull(); + + Schema::enum('task_queue_status', TaskStatusEnum::values()) + ->notNull() + ->default(TaskStatusEnum::PENDING->value); + + Schema::int('task_queue_attempts', 11)->notNull(); + Schema::timeStamp('task_queue_create_at')->default(MySQLConstants::CURRENT_TIMESTAMP); + }) + ->execute(); + + if (isError($response)) { + throw new Exception($response->message, Request::HTTP_INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/LionBundle/Enums/TaskStatusEnum.php b/src/LionBundle/Enums/TaskStatusEnum.php new file mode 100644 index 00000000..12930bde --- /dev/null +++ b/src/LionBundle/Enums/TaskStatusEnum.php @@ -0,0 +1,51 @@ + + */ + public static function values(): array + { + return array_map(fn(object $value) => $value->value, self::cases()); + } +} diff --git a/src/LionBundle/Helpers/Commands/Html.php b/src/LionBundle/Helpers/Commands/Html.php new file mode 100644 index 00000000..3e399fde --- /dev/null +++ b/src/LionBundle/Helpers/Commands/Html.php @@ -0,0 +1,59 @@ +htmlTemplate = $htmlTemplate; + } + + /** + * Replaces values defined in the HTML template + * + * @param string $search [Text searched to be replaced] + * @param string $replace [Text to replace string with another string] + * + * @return Html + */ + public function replace(string $search, string $replace): Html + { + $this->htmlTemplate = str_replace($search, $replace, $this->htmlTemplate); + + return $this; + } + + /** + * Gets the current HTML template + * + * @return string + */ + public function get(): string + { + return $this->htmlTemplate; + } +} diff --git a/src/LionBundle/Helpers/Commands/Schedule/TaskQueue.php b/src/LionBundle/Helpers/Commands/Schedule/TaskQueue.php new file mode 100644 index 00000000..b731530c --- /dev/null +++ b/src/LionBundle/Helpers/Commands/Schedule/TaskQueue.php @@ -0,0 +1,142 @@ + $functions [List of processes to run in task + * list] + * + * @package Lion\Bundle\Helpers\Commands\Schedule + */ +class TaskQueue +{ + /** + * [List of processes to run in task list] + * + * @var array $functions + */ + private static array $functions = []; + + /** + * Add the list of processes to run in the task list + * + * @param string $name [Name of the function being executed] + * @param Closure $callable [Function that is executed] + * + * @return void + */ + public static function add(string $name, Closure $callable): void + { + self::$functions[$name] = $callable; + } + + /** + * Gets a function that is executed + * + * @param string $name [Name of the function being executed] + * + * @return Closure + */ + public static function get(string $name): Closure + { + return self::$functions[$name]; + } + + /** + * Add a task to queue + * + * @param string $queueType [queued task type] + * @param string $json [data in json format] + * + * @return void + * + * @throws Exception [Catch an exception if the task is not inserted into + * the queue] + */ + public static function push(string $queueType, string $json): void + { + $response = DB::table('task_queue') + ->insert([ + 'task_queue_type' => $queueType, + 'task_queue_data' => $json, + 'task_queue_status' => TaskStatusEnum::PENDING->value, + 'task_queue_attempts' => 0 + ]) + ->execute(); + + if (isError($response)) { + throw new Exception($response->message, Request::HTTP_INTERNAL_SERVER_ERROR); + } + } + + /** + * Update a queued task + * + * @param object $queue [Queued task object] + * @param TaskStatusEnum $taskStatusEnum [Queued task status] + * + * @return void + * + * @throws Exception [Catch an exception if the task is not inserted into + * the queue] + */ + public static function edit(object $queue, TaskStatusEnum $taskStatusEnum): void + { + $response = DB::table('task_queue') + ->update([ + 'task_queue_status' => $taskStatusEnum->value + ]) + ->where()->equalTo('idtask_queue', $queue->idtask_queue) + ->execute(); + + if (isError($response)) { + throw new Exception($response->message, Request::HTTP_INTERNAL_SERVER_ERROR); + } + } + + /** + * Delete a task from the queue + * + * @param object $queue [Queued task object] + * + * @return void + * + * @throws Exception [Catch an exception if the task is not inserted into + * the queue] + */ + public static function remove(object $queue): void + { + $response = DB::table('task_queue') + ->delete() + ->where()->equalTo('idtask_queue', $queue->idtask_queue) + ->execute(); + + if (isError($response)) { + throw new Exception($response->message, Request::HTTP_INTERNAL_SERVER_ERROR); + } + } + + /** + * Pause the process for a certain time in seconds + * + * @param int $time [Amount of time in seconds] + * + * @return void + */ + public static function pause(int $time): void + { + sleep($time); + + flush(); + } +} diff --git a/src/LionBundle/Helpers/Commands/Selection/MenuCommand.php b/src/LionBundle/Helpers/Commands/Selection/MenuCommand.php index 09a53842..c47750c8 100644 --- a/src/LionBundle/Helpers/Commands/Selection/MenuCommand.php +++ b/src/LionBundle/Helpers/Commands/Selection/MenuCommand.php @@ -17,16 +17,16 @@ /** * Command class for selecting different types of selection menu * - * @property Arr $arr [Arr class object] + * @property Arr $arr [Modify and build arrays with different indexes or values] * @property Store $store [Store class object] - * @property Str $str [Str class object] + * @property Str $str [Modify and construct strings with different formats] * * @package Lion\Bundle\Helpers\Commands\Selection */ class MenuCommand extends Command { /** - * [Arr class object] + * [Modify and build arrays with different indexes or values] * * @var Arr $arr */ @@ -40,7 +40,7 @@ class MenuCommand extends Command protected Store $store; /** - * [Str class object] + * [Modify and construct strings with different formats] * * @var Str $str */ diff --git a/src/LionBundle/Interface/HtmlInterface.php b/src/LionBundle/Interface/HtmlInterface.php new file mode 100644 index 00000000..84bc20eb --- /dev/null +++ b/src/LionBundle/Interface/HtmlInterface.php @@ -0,0 +1,20 @@ +getApplication(); + + $application->add((new Container())->injectDependencies(new ClassCommand())); + + $this->commandTester = new CommandTester($application->find('new:class')); + + $this->createDirectory(self::URL_PATH); + } + + protected function tearDown(): void + { + $this->rmdirRecursively(self::URL_PATH); + } + + public function testExecute(): void + { + $this->assertSame(Command::SUCCESS, $this->commandTester->execute(['class' => self::CLASS_NAME])); + $this->assertStringContainsString(self::OUTPUT_MESSAGE, $this->commandTester->getDisplay()); + $this->assertFileExists(self::URL_PATH . self::FILE_NAME); + + $classObject = new (self::OBJECT_NAME)(); + + $this->assertIsObject($classObject); + $this->assertInstanceOf(self::OBJECT_NAME, $classObject); + } +} diff --git a/tests/Commands/Lion/New/EnumCommandTest.php b/tests/Commands/Lion/New/EnumCommandTest.php index ec92bd48..d52ca665 100644 --- a/tests/Commands/Lion/New/EnumCommandTest.php +++ b/tests/Commands/Lion/New/EnumCommandTest.php @@ -26,7 +26,9 @@ class EnumCommandTest extends Test protected function setUp(): void { $application = (new Kernel())->getApplication(); + $application->add((new Container())->injectDependencies(new EnumCommand())); + $this->commandTester = new CommandTester($application->find('new:enum')); $this->createDirectory(self::URL_PATH); diff --git a/tests/Commands/Lion/New/FactoryCommandTest.php b/tests/Commands/Lion/New/FactoryCommandTest.php index e8c85c9b..3d652708 100644 --- a/tests/Commands/Lion/New/FactoryCommandTest.php +++ b/tests/Commands/Lion/New/FactoryCommandTest.php @@ -26,7 +26,9 @@ class FactoryCommandTest extends Test protected function setUp(): void { $application = (new Kernel())->getApplication(); + $application->add((new Container())->injectDependencies(new FactoryCommand())); + $this->commandTester = new CommandTester($application->find('new:factory')); $this->createDirectory(self::URL_PATH); diff --git a/tests/Commands/Lion/New/HtmlCommandTest.php b/tests/Commands/Lion/New/HtmlCommandTest.php new file mode 100644 index 00000000..b517a639 --- /dev/null +++ b/tests/Commands/Lion/New/HtmlCommandTest.php @@ -0,0 +1,59 @@ +getApplication(); + + $application->add((new Container())->injectDependencies(new HtmlCommand())); + + $this->commandTester = new CommandTester($application->find('new:html')); + + $this->createDirectory(self::URL_PATH); + } + + protected function tearDown(): void + { + $this->rmdirRecursively('./app/'); + } + + public function testExecute(): void + { + $this->assertSame(Command::SUCCESS, $this->commandTester->execute(['html' => self::CLASS_NAME])); + $this->assertStringContainsString(self::OUTPUT_MESSAGE, $this->commandTester->getDisplay()); + $this->assertFileExists(self::URL_PATH . self::FILE_NAME); + + $objClass = new (self::OBJECT_NAME)(); + + $this->assertIsObject($objClass); + + $this->assertInstances($objClass, [ + self::OBJECT_NAME, + Html::class, + HtmlInterface::class + ]); + } +} diff --git a/tests/Commands/Lion/Schedule/RunQueuedTasksCommandTest.php b/tests/Commands/Lion/Schedule/RunQueuedTasksCommandTest.php new file mode 100644 index 00000000..087aa6c4 --- /dev/null +++ b/tests/Commands/Lion/Schedule/RunQueuedTasksCommandTest.php @@ -0,0 +1,34 @@ +runDatabaseConnections(); + + $application = (new Kernel())->getApplication(); + + $application->add(new RunQueuedTasksCommand()); + + $this->commandTester = new CommandTester($application->find('schedule:run')); + } + + public function testExecute(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/Commands/Lion/Schedule/ScheduleSchemaCommandTest.php b/tests/Commands/Lion/Schedule/ScheduleSchemaCommandTest.php new file mode 100644 index 00000000..fe31fb26 --- /dev/null +++ b/tests/Commands/Lion/Schedule/ScheduleSchemaCommandTest.php @@ -0,0 +1,55 @@ +runDatabaseConnections(); + + $application = (new Kernel())->getApplication(); + + $application->add((new Container())->injectDependencies(new ScheduleSchemaCommand())); + + $this->commandTester = new CommandTester($application->find('schedule:schema')); + } + + protected function tearDown(): void + { + Schema::dropTable('task_queue'); + } + + public function testExecute(): void + { + $this->assertSame(Command::SUCCESS, $this->commandTester->setInputs(['0'])->execute([])); + $this->assertStringContainsString(self::MESSAGE, $this->commandTester->getDisplay()); + + $response = DB::table('task_queue')->select()->getAll(); + + $this->assertIsObject($response); + $this->assertObjectHasProperty('status', $response); + $this->assertObjectHasProperty('message', $response); + $this->assertSame(Response::SUCCESS, $response->status); + $this->assertSame('no data available', $response->message); + } +} diff --git a/tests/Helpers/Commands/HtmlTest.php b/tests/Helpers/Commands/HtmlTest.php new file mode 100644 index 00000000..18f8716f --- /dev/null +++ b/tests/Helpers/Commands/HtmlTest.php @@ -0,0 +1,62 @@ +html = new Html(); + + $this->initReflection($this->html); + } + + public function testAdd(): void + { + $template = (new Store)->get('./tests/Providers/Helpers/Commands/HtmlTemplate.html'); + + $this->getPrivateMethod('add', [$template]); + + $this->assertSame($template, $this->getPrivateProperty('htmlTemplate')); + } + + public function testReplace(): void + { + $template = (new Store)->get('./tests/Providers/Helpers/Commands/HtmlTemplate.html'); + + $this->getPrivateMethod('add', [$template]); + + $this->assertSame($template, $this->getPrivateProperty('htmlTemplate')); + + $this->html->replace('--REPLACE--', self::REPLACE_TEXT); + + $templateReplaced = (new Store)->get('./tests/Providers/Helpers/Commands/HtmlTemplateReplaced.html'); + + $this->assertSame($templateReplaced, $this->getPrivateProperty('htmlTemplate')); + } + + public function testGet(): void + { + $template = (new Store)->get('./tests/Providers/Helpers/Commands/HtmlTemplate.html'); + + $this->getPrivateMethod('add', [$template]); + + $this->assertSame($template, $this->getPrivateProperty('htmlTemplate')); + + $this->html->replace('--REPLACE--', self::REPLACE_TEXT); + + $templateReplaced = (new Store)->get('./tests/Providers/Helpers/Commands/HtmlTemplateReplaced.html'); + + $this->assertSame($templateReplaced, $this->html->get()); + } +} diff --git a/tests/Helpers/Commands/Schedule/TaskQueueTest.php b/tests/Helpers/Commands/Schedule/TaskQueueTest.php new file mode 100644 index 00000000..306bba8d --- /dev/null +++ b/tests/Helpers/Commands/Schedule/TaskQueueTest.php @@ -0,0 +1,237 @@ +runDatabaseConnections(); + + $this->taskQueue = new TaskQueue(); + + $this->initReflection($this->taskQueue); + + $application = (new Kernel())->getApplication(); + + $application->add((new Container())->injectDependencies(new ScheduleSchemaCommand())); + + $this->commandTester = new CommandTester($application->find('schedule:schema')); + } + + protected function tearDown(): void + { + Schema::dropTable('task_queue')->execute(); + } + + public function testAdd(): void + { + $callable = function (object $object): void { + $name = null; + }; + + $this->taskQueue->add(self::TASK_QUEUE_NAME, $callable); + + $functions = $this->getPrivateProperty('functions'); + + $this->assertIsArray($functions); + $this->assertArrayHasKey(self::TASK_QUEUE_NAME, $functions); + $this->assertSame($callable, $functions[self::TASK_QUEUE_NAME]); + } + + public function testGet(): void + { + $callable = function (object $object): void { + $name = null; + }; + + $this->taskQueue->add(self::TASK_QUEUE_NAME, $callable); + + $closure = $this->taskQueue->get(self::TASK_QUEUE_NAME); + + $this->assertSame($callable, $closure); + } + + public function testPush(): void + { + $this->assertSame(Command::SUCCESS, $this->commandTester->setInputs(['0'])->execute([])); + $this->assertStringContainsString(self::MESSAGE, $this->commandTester->getDisplay()); + + $tasks = DB::table('task_queue')->select()->getAll(); + + $this->assertIsObject($tasks); + $this->assertObjectHasProperty('status', $tasks); + $this->assertObjectHasProperty('message', $tasks); + $this->assertSame(Response::SUCCESS, $tasks->status); + $this->assertSame('no data available', $tasks->message); + + $queueType = 'send:email'; + + $json = json([ + 'email' => 'root@dev.com' + ]); + + $this->taskQueue->push($queueType, $json); + + $tasks = DB::table('task_queue')->select()->getAll(); + + $this->assertIsArray($tasks); + $this->assertCount(1, $tasks); + + $row = reset($tasks); + + $this->assertIsObject($row); + $this->assertObjectHasProperty('idtask_queue', $row); + $this->assertObjectHasProperty('task_queue_type', $row); + $this->assertObjectHasProperty('task_queue_data', $row); + $this->assertObjectHasProperty('task_queue_status', $row); + $this->assertObjectHasProperty('task_queue_attempts', $row); + $this->assertObjectHasProperty('task_queue_create_at', $row); + $this->assertSame($queueType, $row->task_queue_type); + $this->assertJsonStringEqualsJsonString($json, $row->task_queue_data); + $this->assertSame(TaskStatusEnum::PENDING->value, $row->task_queue_status); + } + + public function testEdit(): void + { + $this->assertSame(Command::SUCCESS, $this->commandTester->setInputs(['0'])->execute([])); + $this->assertStringContainsString(self::MESSAGE, $this->commandTester->getDisplay()); + + $tasks = DB::table('task_queue')->select()->getAll(); + + $this->assertIsObject($tasks); + $this->assertObjectHasProperty('status', $tasks); + $this->assertObjectHasProperty('message', $tasks); + $this->assertSame(Response::SUCCESS, $tasks->status); + $this->assertSame('no data available', $tasks->message); + + $queueType = 'send:email'; + + $json = json([ + 'email' => 'root@dev.com' + ]); + + $this->taskQueue->push($queueType, $json); + + $tasks = DB::table('task_queue')->select()->getAll(); + + $this->assertIsArray($tasks); + $this->assertCount(1, $tasks); + + $queue = reset($tasks); + + $this->assertIsObject($queue); + $this->assertObjectHasProperty('idtask_queue', $queue); + $this->assertObjectHasProperty('task_queue_type', $queue); + $this->assertObjectHasProperty('task_queue_data', $queue); + $this->assertObjectHasProperty('task_queue_status', $queue); + $this->assertObjectHasProperty('task_queue_attempts', $queue); + $this->assertObjectHasProperty('task_queue_create_at', $queue); + $this->assertSame($queueType, $queue->task_queue_type); + $this->assertJsonStringEqualsJsonString($json, $queue->task_queue_data); + $this->assertSame(TaskStatusEnum::PENDING->value, $queue->task_queue_status); + + $this->taskQueue->edit($queue, TaskStatusEnum::IN_PROGRESS); + + $tasks = DB::table('task_queue')->select()->getAll(); + + $this->assertIsArray($tasks); + $this->assertCount(1, $tasks); + + $queue = reset($tasks); + + $this->assertIsObject($queue); + $this->assertObjectHasProperty('idtask_queue', $queue); + $this->assertObjectHasProperty('task_queue_type', $queue); + $this->assertObjectHasProperty('task_queue_data', $queue); + $this->assertObjectHasProperty('task_queue_status', $queue); + $this->assertObjectHasProperty('task_queue_attempts', $queue); + $this->assertObjectHasProperty('task_queue_create_at', $queue); + $this->assertSame($queueType, $queue->task_queue_type); + $this->assertJsonStringEqualsJsonString($json, $queue->task_queue_data); + $this->assertSame(TaskStatusEnum::IN_PROGRESS->value, $queue->task_queue_status); + } + + public function testRemove(): void + { + $this->assertSame(Command::SUCCESS, $this->commandTester->setInputs(['0'])->execute([])); + $this->assertStringContainsString(self::MESSAGE, $this->commandTester->getDisplay()); + + $tasks = DB::table('task_queue')->select()->getAll(); + + $this->assertIsObject($tasks); + $this->assertObjectHasProperty('status', $tasks); + $this->assertObjectHasProperty('message', $tasks); + $this->assertSame(Response::SUCCESS, $tasks->status); + $this->assertSame('no data available', $tasks->message); + + $queueType = 'send:email'; + + $json = json([ + 'email' => 'root@dev.com' + ]); + + $this->taskQueue->push($queueType, $json); + + $tasks = DB::table('task_queue')->select()->getAll(); + + $this->assertIsArray($tasks); + $this->assertCount(1, $tasks); + + $queue = reset($tasks); + + $this->assertIsObject($queue); + $this->assertObjectHasProperty('idtask_queue', $queue); + $this->assertObjectHasProperty('task_queue_type', $queue); + $this->assertObjectHasProperty('task_queue_data', $queue); + $this->assertObjectHasProperty('task_queue_status', $queue); + $this->assertObjectHasProperty('task_queue_attempts', $queue); + $this->assertObjectHasProperty('task_queue_create_at', $queue); + $this->assertSame($queueType, $queue->task_queue_type); + $this->assertJsonStringEqualsJsonString($json, $queue->task_queue_data); + $this->assertSame(TaskStatusEnum::PENDING->value, $queue->task_queue_status); + + $this->taskQueue->remove($queue); + + $tasks = DB::table('task_queue')->select()->getAll(); + + $this->assertIsObject($tasks); + $this->assertObjectHasProperty('status', $tasks); + $this->assertObjectHasProperty('message', $tasks); + $this->assertSame(Response::SUCCESS, $tasks->status); + $this->assertSame('no data available', $tasks->message); + } + + public function testPause(): void + { + $initialDate = now(); + + $this->taskQueue->pause(self::TASK_QUEUE_TIME); + + $this->assertSame(self::TASK_QUEUE_TIME, now()->diffInSeconds($initialDate)); + } +} diff --git a/tests/Providers/Helpers/Commands/HtmlTemplate.html b/tests/Providers/Helpers/Commands/HtmlTemplate.html new file mode 100644 index 00000000..642f09c2 --- /dev/null +++ b/tests/Providers/Helpers/Commands/HtmlTemplate.html @@ -0,0 +1,11 @@ + + + + + + HTML Template + + +

HTML Template (--REPLACE--)

+ + diff --git a/tests/Providers/Helpers/Commands/HtmlTemplateReplaced.html b/tests/Providers/Helpers/Commands/HtmlTemplateReplaced.html new file mode 100644 index 00000000..ffbf843a --- /dev/null +++ b/tests/Providers/Helpers/Commands/HtmlTemplateReplaced.html @@ -0,0 +1,11 @@ + + + + + + HTML Template + + +

HTML Template (Lion-Bundle)

+ +