@@ -9,7 +9,7 @@ class DBReset
|
||||
{
|
||||
private const DB_GROUP = 'tests';
|
||||
|
||||
protected static array $excludeTables = ['migrations'];
|
||||
protected static array $excludeTables = ['migrations', 'school_years'];
|
||||
|
||||
public static function resetDatabase(): void
|
||||
{
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Support;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use Config\Database;
|
||||
use RuntimeException;
|
||||
|
||||
abstract class ModelCrudTestCase extends CIUnitTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
DBReset::resetDatabase();
|
||||
}
|
||||
|
||||
protected function assertModelCanInsertAndRetrieve(string $modelClass): void
|
||||
{
|
||||
$model = $this->newModel($modelClass);
|
||||
$record = $this->fakeModelRecord($model);
|
||||
$id = $this->primaryKeyValue($record, $model->primaryKey);
|
||||
|
||||
$this->assertNotNull($id);
|
||||
|
||||
$fetched = $model->find($id);
|
||||
|
||||
$this->assertNotNull($fetched);
|
||||
$this->assertEquals($id, $this->recordValue($fetched, $model->primaryKey));
|
||||
}
|
||||
|
||||
protected function assertModelCanUpdate(string $modelClass): void
|
||||
{
|
||||
$model = $this->newModel($modelClass);
|
||||
$record = $this->fakeModelRecord($model);
|
||||
$id = $this->primaryKeyValue($record, $model->primaryKey);
|
||||
$updateData = $this->updateData($model, $record);
|
||||
|
||||
$this->assertNotFalse($model->update($id, $updateData));
|
||||
}
|
||||
|
||||
protected function assertModelCanDelete(string $modelClass): void
|
||||
{
|
||||
$model = $this->newModel($modelClass);
|
||||
$record = $this->fakeModelRecord($model);
|
||||
$id = $this->primaryKeyValue($record, $model->primaryKey);
|
||||
|
||||
$model->delete($id);
|
||||
|
||||
$this->assertNull($model->find($id));
|
||||
}
|
||||
|
||||
protected function primaryKeyValue(array|object $record, string $primaryKey): int|string
|
||||
{
|
||||
return $this->recordValue($record, $primaryKey);
|
||||
}
|
||||
|
||||
protected function validSchoolYear(): string
|
||||
{
|
||||
$row = Database::connect('tests')
|
||||
->table('school_years')
|
||||
->select('name')
|
||||
->orderBy('name', 'DESC')
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
if ($row === null) {
|
||||
$year = $this->fallbackSchoolYear();
|
||||
$db = Database::connect('tests');
|
||||
$fields = $db->getFieldNames('school_years');
|
||||
$data = [
|
||||
'name' => $year,
|
||||
'status' => 'active',
|
||||
];
|
||||
|
||||
foreach (['activated_at', 'created_at', 'updated_at'] as $field) {
|
||||
if (in_array($field, $fields, true)) {
|
||||
$data[$field] = date('Y-m-d H:i:s');
|
||||
}
|
||||
}
|
||||
|
||||
$db->table('school_years')->insert($data);
|
||||
|
||||
return $year;
|
||||
}
|
||||
|
||||
return $row['name'];
|
||||
}
|
||||
|
||||
protected function fakeModelRecord(Model $model): array|object
|
||||
{
|
||||
if (! Database::connect('tests')->tableExists($model->table)) {
|
||||
$this->markTestSkipped(sprintf('Table %s is not present in the migrated test schema.', $model->table));
|
||||
}
|
||||
|
||||
return fake($model, $this->fabricatorOverrides($model));
|
||||
}
|
||||
|
||||
protected function fabricatorOverrides(Model $model): array
|
||||
{
|
||||
$overrides = [];
|
||||
|
||||
if (in_array('school_year', $model->allowedFields, true)) {
|
||||
$overrides['school_year'] = $this->validSchoolYear();
|
||||
}
|
||||
|
||||
foreach ($model->validationRules as $field => $rules) {
|
||||
if (! in_array($field, $model->allowedFields, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rules = is_array($rules) ? implode('|', $rules) : (string) $rules;
|
||||
|
||||
if (preg_match('/(?:^|\|)in_list\[([^\]]+)\]/', $rules, $matches) === 1) {
|
||||
$overrides[$field] = explode(',', $matches[1])[0];
|
||||
} elseif (str_contains($rules, 'valid_email')) {
|
||||
$overrides[$field] = uniqid('model-test-', true) . '@example.test';
|
||||
} elseif (str_contains($rules, 'decimal')) {
|
||||
$overrides[$field] = '1.00';
|
||||
} elseif (str_contains($rules, 'integer')) {
|
||||
$overrides[$field] = 1;
|
||||
} elseif (str_contains($rules, 'valid_date[Y-m-d H:i:s]')) {
|
||||
$overrides[$field] = date('Y-m-d H:i:s');
|
||||
} elseif (str_contains($rules, 'valid_date')) {
|
||||
$overrides[$field] = date('Y-m-d');
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($model->allowedFields as $field) {
|
||||
if (array_key_exists($field, $overrides)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($field === 'email') {
|
||||
$overrides[$field] = uniqid('model-test-', true) . '@example.test';
|
||||
} elseif ($field === 'status') {
|
||||
$overrides[$field] = 'Active';
|
||||
} elseif (str_ends_with($field, '_id')) {
|
||||
$overrides[$field] = 1;
|
||||
} elseif (str_starts_with($field, 'is_') || str_starts_with($field, 'has_')) {
|
||||
$overrides[$field] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $overrides;
|
||||
}
|
||||
|
||||
protected function recordValue(array|object $record, string $field): int|string|null
|
||||
{
|
||||
if (is_array($record)) {
|
||||
return $record[$field] ?? null;
|
||||
}
|
||||
|
||||
return $record->{$field} ?? null;
|
||||
}
|
||||
|
||||
protected function updateData(Model $model, array|object $record): array
|
||||
{
|
||||
foreach ($model->allowedFields as $field) {
|
||||
if ($field === $model->primaryKey) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rules = $model->validationRules[$field] ?? '';
|
||||
$rules = is_array($rules) ? implode('|', $rules) : (string) $rules;
|
||||
|
||||
if (str_contains($rules, 'is_unique')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $this->recordValue($record, $field);
|
||||
|
||||
if ($value !== null) {
|
||||
return [$field => $value];
|
||||
}
|
||||
}
|
||||
|
||||
if ($model->useTimestamps && $model->updatedField !== '') {
|
||||
return [$model->updatedField => date('Y-m-d H:i:s')];
|
||||
}
|
||||
|
||||
throw new RuntimeException(sprintf('No updateable data is available for %s.', $model::class));
|
||||
}
|
||||
|
||||
private function newModel(string $modelClass): Model
|
||||
{
|
||||
if (! class_exists($modelClass)) {
|
||||
$this->markTestSkipped(sprintf('Model %s is not part of the application.', $modelClass));
|
||||
}
|
||||
|
||||
$model = new $modelClass();
|
||||
|
||||
if (! $model instanceof Model) {
|
||||
throw new RuntimeException(sprintf('%s is not a CodeIgniter model.', $modelClass));
|
||||
}
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
private function fallbackSchoolYear(): string
|
||||
{
|
||||
$year = (int) date('Y');
|
||||
|
||||
return ($year - 1) . '-' . $year;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user