229 lines
7.0 KiB
PHP
229 lines
7.0 KiB
PHP
<?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);
|
|
$this->assertModelFieldsExist($model);
|
|
$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);
|
|
$this->assertModelFieldsExist($model);
|
|
$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);
|
|
$this->assertModelFieldsExist($model);
|
|
$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
|
|
{
|
|
return fake($model, $this->fabricatorOverrides($model));
|
|
}
|
|
|
|
protected function assertModelFieldsExist(Model $model): void
|
|
{
|
|
$db = Database::connect('tests');
|
|
|
|
$this->assertTrue(
|
|
$db->tableExists($model->table, false),
|
|
sprintf('%s maps to table "%s", but that table is not present in the migrated test schema.', $model::class, $model->table)
|
|
);
|
|
|
|
$databaseFields = $db->getFieldNames($model->table);
|
|
|
|
foreach ($model->allowedFields as $field) {
|
|
$this->assertContains(
|
|
$field,
|
|
$databaseFields,
|
|
sprintf(
|
|
'%s allows field "%s", but table "%s" does not contain it.',
|
|
$model::class,
|
|
$field,
|
|
$model->table
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
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
|
|
{
|
|
$this->assertTrue(class_exists($modelClass), 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;
|
|
}
|
|
}
|