Co-authored-by: Juan Pablo Vial <jpvialb@incoviba.cl>
Reviewed-on: #45
This commit is contained in:
2025-10-04 11:40:52 -03:00
parent 6ddc48ec60
commit 742de657c5
815 changed files with 62089 additions and 3287 deletions

View File

@ -0,0 +1,15 @@
<?php
namespace Tests\Extension;
use GuzzleHttp\Client;
use PHPUnit\Framework\TestCase;
use Psr\Http\Client\ClientInterface;
abstract class AbstractIntegration extends TestCase
{
protected ClientInterface $client;
protected function setUp(): void
{
$this->client = new Client(['base_uri' => $_ENV['APP_URL']]);
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Tests\Extension;
use PHPUnit\Framework\TestCase;
use Incoviba\Common\Define\Model;
abstract class AbstractModel extends TestCase
{
protected Model $model;
}

View File

@ -0,0 +1,17 @@
<?php
namespace Tests\Extension;
use PHPUnit\Framework\TestCase;
abstract class AbstractPerformance extends TestCase
{
protected float $startTime;
protected function start(): void
{
$this->startTime = microtime(true);
}
protected function end(): float
{
return microtime(true) - $this->startTime;
}
}

View File

@ -0,0 +1,147 @@
<?php
namespace Tests\Extension;
use PDO;
use PDOException;
use Faker;
use Tests\Extension\Faker\Provider\Rut;
abstract class AbstractSeed implements SeedInterface
{
public function __construct(PDO $connection)
{
$this->setConnection($connection);
$this->faker = Faker\Factory::create('es_AR');
$this->faker->addProvider(new Rut($this->faker));
}
protected PDO $connection;
protected Faker\Generator $faker;
public function setConnection(PDO $connection): SeedInterface
{
$this->connection = $connection;
return $this;
}
public function getConnection(): PDO
{
return $this->connection;
}
public function getDependencies(): array
{
return [];
}
protected string $table;
protected function table(string $table): self
{
$this->table = $table;
return $this;
}
protected array $queryQueue = [];
protected function insertValues(array $valueRows): self
{
$columns = array_keys($valueRows[0]);
$columnsString = implode(', ', array_map(fn($column) => "`{$column}`", $columns));
$placeholderArray = array_map(fn($column) => ":{$column}", $columns);
$placeholders = implode(', ', $placeholderArray);
$query = "INSERT INTO `{$this->table}` ({$columnsString}) VALUES ({$placeholders})";
$this->queryQueue []= ['query' => $query, 'values' => $valueRows];
return $this;
}
protected function save(): self
{
foreach ($this->queryQueue as $entry) {
$query = $entry['query'];
$valueRows = $entry['values'];
foreach ($valueRows as $valueRow) {
try {
$this->connection->beginTransaction();
$statement = $this->connection->prepare($query);
if ($statement === false) {
$this->connection->rollBack();
continue;
}
$statement->execute($valueRow);
$this->connection->commit();
} catch (PDOException | \Throwable $exception) {
$this->connection->rollBack();
}
}
}
return $this;
}
protected function loadValues(string $table, array $conditions = [], string|array $columns = '*'): array
{
$columns = $this->processColumns($columns);
$query = "SELECT {$columns} FROM `{$table}`";
if (count($conditions) > 0) {
$conditionsString = $this->processConditions($conditions);
$query = "{$query} WHERE {$conditionsString}";
}
try {
$statement = $this->connection->prepare($query);
$statement->execute();
} catch (PDOException) {
return [];
}
try {
if ($columns !== '*' and !str_contains($columns, ',')) {
return $statement->fetchAll(PDO::FETCH_COLUMN);
}
return $statement->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException) {
return [];
}
}
protected function processColumns(string|array $columns): string
{
if (is_array($columns)) {
$columns = implode(',', array_map(fn($column) => "`{$column}`", $columns));
}
if ($columns === '*') {
return $columns;
}
$columns = array_map(fn($column) => trim($column), explode(',', $columns));
return implode(', ', array_map(function($column) {
if (!str_contains($column, '`')) {
return "`{$column}`";
}
return $column;
}, $columns));
}
protected function processConditions(array $conditions): array
{
$processedConditions = [];
$processedValues = [];
foreach ($conditions as $condition) {
if (is_string($condition) and (str_starts_with(strtolower($condition), 'and') or str_starts_with(strtolower($condition), 'or'))) {
$processedConditions[] = $condition;
continue;
}
$column = $condition['column'];
$value = $condition['value'];
$match = $condition['match'] ?? 'AND';
$operator = $condition['operator'] ?? '=';
$columnValue = ":{$column}";
if (is_array($value)) {
$columnString = [];
foreach ($value as $idx => $val) {
$columnValue = ":{$column}_{$idx}";
$columnString[] = $columnValue;
$processedValues["{$column}_{$idx}"] = $val;
}
$columnValue = '(' . implode(', ', $columnString) . ')';
if (!str_contains($operator, 'IN')) {
$operator = 'IN';
}
} else {
$processedValues[$column] = $value;
}
$processedConditions[] = "{$match} `{$column}` {$operator} {$columnValue}";
}
return ['query' => implode(' ', $processedConditions), 'values' => $processedValues];
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Tests\Extension;
use Psr\Container\ContainerInterface;
trait ContainerTrait
{
protected ContainerInterface $container;
protected function setUp(): void
{
parent::setUp();
require_once implode(DIRECTORY_SEPARATOR, [dirname(__DIR__, 2), 'setup', 'container.php']);
$this->container = buildContainer();
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace Tests\Extension\Faker\Provider;
use Faker\Provider\Base;
class Rut extends Base
{
public function rut(bool $withDigito = true, bool $withDotsAndSlash = true): string
{
$base = self::numberBetween(1000000, 99999999);
$rut = $base;
if ($withDotsAndSlash) {
$rut = number_format($rut, 0, ',', '.');
}
if ($withDigito) {
$digito = $this->getDigito($base);
if ($withDotsAndSlash) {
return "{$digito}-{$rut}";
}
return "{$digito}{$rut}";
}
return $rut;
}
public function digitoVerificador(string $rut): bool|string
{
if ( !preg_match("/^[0-9.]+/",$rut)) return false;
$rut = str_replace('.','',$rut);
return $this->getDigito($rut);
}
protected function getDigito(string $rut): string
{
$M=0;$S=1;
for(;$rut;$rut=floor($rut/10))
$S=($S+$rut%10*(9-$M++%6))%11;
return $S?$S-1:'K';
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Tests\Extension;
trait ObjectHasMethodTrait
{
public function assertObjectHasMethod(string $method, object $object): void
{
$this->assertTrue(method_exists($object, $method), sprintf('The object %s does not have the method %s', get_class($object), $method));
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Tests\Extension;
use PDO;
interface SeedInterface
{
/**
* @param PDO $connection
* @return self
*/
public function setConnection(PDO $connection): self;
/**
* @return PDO
*/
public function getConnection(): PDO;
/**
* @return array
*/
public function getDependencies(): array;
/**
* @return void
*/
public function run(): void;
}

View File

@ -0,0 +1,23 @@
<?php
namespace Tests\Extension\Seeds;
use Tests\Extension\AbstractSeed;
class Bancos extends AbstractSeed
{
public function run(): void
{
$count = 10;
$data = [];
for ($i = 0; $i < $count; $i++) {
$data[] = [
'nombre' => $this->faker->company,
];
}
$this->table('banco')
->insertValues($data)
->save();
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Tests\Extension\Seeds;
use Tests\Extension\AbstractSeed;
class BrokerContracts extends AbstractSeed
{
public function getDependencies(): array
{
return [
Brokers::class,
Proyectos::class,
];
}
public function run(): void
{
$brokers = $this->loadValues('brokers', columns: 'rut');
$projects = $this->loadValues('proyecto', columns: 'id');
$data = [];
foreach ($projects as $project) {
$count = $this->faker->numberBetween(1, count($brokers));
for ($i = 0; $i < $count; $i++) {
$data []= [
'broker_rut' => $brokers[$i],
'project_id' => $project,
'commission' => $this->faker->randomFloat(4, 0, 1),
];
}
}
$this->table('broker_contracts')->insertValues($data)->save();
$contracts = $this->loadValues('broker_contracts', columns: 'id');
$stateData = [];
foreach ($contracts as $contract) {
$stateData[]= [
'contract_id' => $contract,
'date' => $this->faker->dateTimeBetween('-1 year'),
'type' => 1
];
}
$this->table('broker_contract_states')->insertValues($stateData)->save();
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace Tests\Extension\Seeds;
use Tests\Extension\AbstractSeed;
class Brokers extends AbstractSeed
{
public function run(): void
{
$addresses = $this->loadValues('direccion', columns: 'id');
$count = 10;
$contactData = [];
for($i = 0; $i < $count; $i++) {
$rut = $this->faker->rut(false, false);
$contactData[]= [
'rut' => $rut,
'digit' => $this->faker->digitoVerificador($rut),
'name' => $this->faker->name,
'email' => $this->faker->email,
'phone' => $this->faker->phoneNumber,
'address_id' => $this->faker->randomElement($addresses)
];
}
$this->table('broker_contacts')->insertValues($contactData)->save();
$contacts = $this->loadValues('broker_contacts', columns: 'id');
$data = [];
$brokerData = [];
for($i = 0; $i < $count; $i ++) {
$rut = $this->faker->rut(false, false);
$data[] = [
'rut' => $rut,
'digit' => $this->faker->digitoVerificador($rut),
'name' => $this->faker->word
];
$brokerData []= [
'broker_rut' => $rut,
'representante_id' => $this->faker->randomElement($contacts),
'legal_name' => $this->faker->company
];
}
$this->table('brokers')->insertValues($data)->save();
$this->table('broker_data')->insertValues($brokerData)->save();
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Tests\Extension\Seeds;
use Tests\Extension\AbstractSeed;
class Direcciones extends AbstractSeed
{
public function run(): void
{
$comunas = $this->loadValues('comuna', columns: 'id');
$n = 100;
$data = [];
for ($i = 0; $i < $n; $i++) {
$row = [
'calle' => $this->faker->streetName,
'numero' => $this->faker->randomNumber(5),
'comuna' => $this->faker->randomElement($comunas),
'extra' => $this->faker->optional(0.9, '')->words(2, true),
];
$extraRand = ((int) round(rand() / getrandmax())) === 1;
if ($extraRand) {
$nExtra = (int) round(rand(1, 3));
$row['extra'] = $this->faker->words($nExtra, true);
}
$data[] = $row;
}
$this->table('direccion')
->insertValues($data)
->save();
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Tests\Extension\Seeds;
use Tests\Extension\AbstractSeed;
class Inmobiliarias extends AbstractSeed
{
public function getDependencies(): array
{
return [
Bancos::class
];
}
public function run(): void
{
$tipos = $this->loadValues('tipo_sociedad', columns: 'id');
$bancos = $this->loadValues('banco', columns: 'id');
$suffixes = [
'Inmobiliaria ',
'Administradora ',
'Asesorías ',
''
];
$n = 5;
$data = [];
for ($i = 0; $i < $n; $i++) {
$rut = $this->faker->rut(false, false);
$abreviacion = $this->faker->streetName;
$suffix = $this->faker->randomElement($suffixes);
$razon = "{$suffix}{$abreviacion}";
$sigla = strtoupper(substr($abreviacion, 0, 3));
$data []= [
'rut' => $rut,
'dv' => $this->faker->digitoVerificador($rut),
'razon' => $razon,
'abreviacion' => $abreviacion,
'cuenta' => $this->faker->randomNumber(8),
'banco' => $this->faker->randomElement($bancos),
'sociedad' => $this->faker->randomElement($tipos),
'sigla' => $sigla,
];
}
$this->table('inmobiliaria')
->insertValues($data)
->save();
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace Tests\Extension\Seeds;
use DateInterval;
use Tests\Extension\AbstractSeed;
class Promotions extends AbstractSeed
{
public function getDependencies(): array
{
return [
Proyectos::class,
];
}
public function run(): void
{
$projects = $this->loadValues('proyecto', columns: 'id');
$data = [];
$count = $this->faker->numberBetween(1, 10);
for ($i = 0; $i < $count; $i++) {
$start = $this->faker->dateTimeBetween('-1 year');
$end = $this->faker->dateTimeBetween($start, $start->add(new DateInterval('P1Y')));
$data[] = [
'description' => $this->faker->sentence,
'type' => 1,
'amount' => $this->faker->randomFloat(2, 0, 1),
'start_date' => $start,
'end_date' => $end,
'valid_until' => $this->faker->dateTimeBetween($end, $end->add(new DateInterval('P1M'))),
'state' => 1,
];
}
$this->table('promotions')->insertValues($data)->save();
$promotions = $this->loadValues('promotions', columns: 'id');
$data = [];
foreach ($projects as $project) {
$hasPromo = $this->faker->boolean(10);
if (!$hasPromo) {
continue;
}
$data []= [
'promotion_id' => $this->faker->randomElement($promotions),
'project_id' => $project,
];
}
if (count($data) > 0) {
$this->table('promotion_projects')->insertValues($data)->save();
}
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace Tests\Extension\Seeds;
use Tests\Extension\AbstractSeed;
class ProyectoTipoUnidad extends AbstractSeed
{
public function getDependencies(): array
{
return [
Proyectos::class,
];
}
public function run(): void
{
$projects = $this->loadValues('proyecto', columns: 'id');
$types = $this->loadValues('tipo_unidad', columns: 'id');
$data = [];
foreach ($projects as $project) {
foreach ($types as $type) {
$count = $this->faker->numberBetween(1, 10);
for ($i = 0; $i < $count; $i++) {
$name = $this->faker->word;
$data []= [
'proyecto' => $project,
'tipo' => $type,
'nombre' => $name,
'abreviacion' => substr($name, 0, 3),
'm2' => $this->faker->randomFloat(2, 10, 100),
'logia' => $this->faker->optional(.3, 0)->randomFloat(2, 1, 5),
'terraza' => $this->faker->optional(.3, 0)->randomFloat(2, 2, 30),
'descripcion' => $this->faker->sentence,
];
}
}
}
$this->table('proyecto_tipo_unidad')->insertValues($data)->save();
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Tests\Extension\Seeds;
use Tests\Extension\AbstractSeed;
class Proyectos extends AbstractSeed
{
public function getDependencies(): array
{
return [
Inmobiliarias::class,
Direcciones::class
];
}
public function run(): void
{
$inmobiliarias = $this->loadValues('inmobiliaria', columns: 'rut');
$direcciones = $this->loadValues('direccion', columns: 'id');
$n = 10;
$data = [];
for ($i = 0; $i < $n; $i++) {
$data[] = [
'inmobiliaria' => $this->faker->randomElement($inmobiliarias),
'descripcion' => $this->faker->words(2, true),
'direccion' => $this->faker->randomElement($direcciones),
'superficie_sobre_nivel' => $this->faker->randomFloat(2, 1000, 10000),
'superficie_bajo_nivel' => $this->faker->randomFloat(2, 0, 5000),
'pisos' => $this->faker->numberBetween(2, 30),
'subterraneos' => $this->faker->numberBetween(0, 5),
'corredor' => $this->faker->optional(.6, 0)->randomFloat(4, 0, 1)
];
}
$this->table('proyecto')
->insertValues($data)
->save();
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace Tests\Extension\Seeds;
use Tests\Extension\AbstractSeed;
class Unidades extends AbstractSeed
{
public function getDependencies(): array
{
return [
ProyectoTipoUnidad::class,
];
}
public function run(): void
{
$ptus = $this->loadValues('proyecto_tipo_unidad', columns: ['id', 'proyecto', 'tipo', 'm2', 'logia', 'terraza']);
$data = [];
foreach ($ptus as $s => $ptu) {
$count = $this->faker->numberBetween(1, 10);
$abr = $this->faker->word;
$orientation = $this->faker->randomElement(['N', 'NO', 'NP', 'S', 'SO', 'SP', 'P', 'O']);
for ($i = 0; $i < $count; $i++) {
$data[] = [
'proyecto' => $ptu['proyecto'],
'tipo' => $ptu['tipo'],
'subtipo' => $s,
'piso' => $i + 2,
'descripcion' => ($i + 2) * 100 + $s,
'abreviacion' => $abr,
'm2' => $ptu['m2'],
'logia' => $ptu['logia'],
'cubierta' => 0,
'terraza' => $ptu['terraza'],
'orientacion' => $orientation,
'costo_inmobiliaria' => $this->faker->randomFloat(2, 1000, 3000),
'pt' => $ptu['id'],
'valor' => $this->faker->randomFloat(2, 1000, 3000),
];
}
}
$this->table('unidad')->insertValues($data)->save();
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace Tests\Extension;
use FilesystemIterator;
use PDO;
use PDOException;
class TestSeeder
{
public function __construct(protected PDO $connection) {}
public function run(): void
{
$seedClasses = $this->getSeedClasses();
$orderedSeeds = $this->orderedSeeds($seedClasses);
foreach ($orderedSeeds as $seed) {
$seed->run();
}
}
protected function getSeedClasses(): array
{
$seedsFolder = implode(DIRECTORY_SEPARATOR, [__DIR__, 'Seeds']);
$files = new FilesystemIterator($seedsFolder, FilesystemIterator::SKIP_DOTS);
$seeds = [];
foreach ($files as $file) {
$seeds []= $this->buildClassName($file->getBasename('.php'));
}
return $seeds;
}
protected function getSeed(string $seedClassName): SeedInterface
{
return new $seedClassName($this->connection);
}
protected function buildClassName(string $fileBaseName): string
{
$namespace = implode('\\', [__NAMESPACE__, 'Seeds']);
return implode('\\', [$namespace, $fileBaseName]);
}
protected function orderedSeeds(array $seedClasses): array
{
$orderedSeeds = [];
foreach ($seedClasses as $seedClassName) {
$seed = $this->getSeed($seedClassName);
if ($seed->getDependencies() === []) {
$orderedSeeds[$seedClassName] = $seed;
continue;
}
$orderedSeeds = array_merge($orderedSeeds, $this->orderedDependencies($orderedSeeds, $seedClasses, $seedClassName));
}
return $orderedSeeds;
}
protected function orderedDependencies(array $orderedSeeds, array $seedClasses, string $seedClassName): array
{
$seed = $this->getSeed($seedClassName);
$dependencies = $seed->getDependencies();
foreach ($dependencies as $dependencyClass) {
if (!array_key_exists($dependencyClass, $orderedSeeds)) {
$orderedSeeds = array_merge($orderedSeeds, $this->orderedDependencies($orderedSeeds, $seedClasses, $dependencyClass));
}
}
if (!array_key_exists($seedClassName, $orderedSeeds)) {
$orderedSeeds[$seedClassName] = $seed;
}
return $orderedSeeds;
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Tests\Extension;
trait testMethodsTrait
{
use ObjectHasMethodTrait;
public function testMethods(): void
{
$object = $this->model;
foreach ($this->methods as $method) {
$this->assertObjectHasMethod($method, $object);
}
}
protected array $methods = [];
}

View File

@ -0,0 +1,24 @@
<?php
namespace Tests\Extension;
use Incoviba\Common\Define\Model;
trait testPropertiesTrait
{
use ObjectHasMethodTrait;
public function testProperties(): void
{
$model = $this->model;
$this->assertProperties($model);
}
protected array $properties = [];
protected function assertProperties(Model $model): void
{
foreach ($this->properties as $key) {
$this->assertObjectHasProperty($key, $model);
}
}
}