@@ -45,7 +45,7 @@ class Database extends Config
|
|||||||
'hostname' => env('TEST_DB_HOST', env('database.tests.hostname', env('database.default.hostname'))),
|
'hostname' => env('TEST_DB_HOST', env('database.tests.hostname', env('database.default.hostname'))),
|
||||||
'username' => env('TEST_DB_USER', env('database.tests.username', env('database.default.username'))),
|
'username' => env('TEST_DB_USER', env('database.tests.username', env('database.default.username'))),
|
||||||
'password' => env('TEST_DB_PASSWORD', env('database.tests.password', env('database.default.password'))),
|
'password' => env('TEST_DB_PASSWORD', env('database.tests.password', env('database.default.password'))),
|
||||||
'database' => env('TEST_DB_NAME', env('database.tests.database', env('database.default.database'))),
|
'database' => env('TEST_DB_NAME', env('database.tests.database', 'alrahma_test')),
|
||||||
'DBDriver' => env('database.tests.DBDriver', env('database.default.DBDriver', 'MySQLi')),
|
'DBDriver' => env('database.tests.DBDriver', env('database.default.DBDriver', 'MySQLi')),
|
||||||
'DBPrefix' => env('database.tests.DBPrefix', ''),
|
'DBPrefix' => env('database.tests.DBPrefix', ''),
|
||||||
'pConnect' => false,
|
'pConnect' => false,
|
||||||
|
|||||||
@@ -8,20 +8,8 @@ class RemoveClassAssignmentSemester extends Migration
|
|||||||
{
|
{
|
||||||
public function up()
|
public function up()
|
||||||
{
|
{
|
||||||
if ($this->db->tableExists('teacher_class') && $this->db->fieldExists('semester', 'teacher_class')) {
|
// Keep the legacy semester columns in place. They may be ignored by newer
|
||||||
if ($this->indexExists('teacher_class', 'unique_teacher_assignment')) {
|
// code, but dropping them during a post-restore migrate destroys data.
|
||||||
$this->db->query('ALTER TABLE `teacher_class` DROP INDEX `unique_teacher_assignment`');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->forge->dropColumn('teacher_class', 'semester');
|
|
||||||
|
|
||||||
$this->db->query(
|
|
||||||
'ALTER TABLE `teacher_class` ADD UNIQUE KEY `unique_teacher_assignment` (`teacher_id`, `class_section_id`, `school_year`)'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if ($this->db->tableExists('student_class') && $this->db->fieldExists('semester', 'student_class')) {
|
|
||||||
$this->forge->dropColumn('student_class', 'semester');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down()
|
public function down()
|
||||||
|
|||||||
@@ -183,8 +183,8 @@ class FinancialSystemLedgerCleanup extends Migration
|
|||||||
|
|
||||||
protected function archivePaypalTables(): void
|
protected function archivePaypalTables(): void
|
||||||
{
|
{
|
||||||
$this->renameTableIfPresent('paypal_payments', 'archived_paypal_payments');
|
// Keep legacy PayPal tables under their original names. Renaming them
|
||||||
$this->renameTableIfPresent('paypal_transactions', 'archived_paypal_transactions');
|
// during migrate makes restored data appear to disappear from the app.
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function restorePaypalTables(): void
|
protected function restorePaypalTables(): void
|
||||||
|
|||||||
@@ -169,28 +169,9 @@ class FixSchoolYearColumns extends Migration
|
|||||||
|
|
||||||
$this->db->resetDataCache();
|
$this->db->resetDataCache();
|
||||||
|
|
||||||
// Remove redundant columns. Dependent non-primary indexes are removed first.
|
// Do not drop redundant school_year/semester columns here. Restored
|
||||||
foreach ($this->tablesWithoutDirectYear as $table) {
|
// production dumps may still contain meaningful historical data in
|
||||||
if (! $this->db->tableExists($table) || ! $this->db->fieldExists('school_year', $table)) {
|
// those columns, and running migrate must not destroy it.
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->dropIndexesContainingColumn($table, 'school_year');
|
|
||||||
$this->dropChecksContainingColumn($table, 'school_year');
|
|
||||||
$this->forge->dropColumn($table, 'school_year');
|
|
||||||
$this->db->resetDataCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->tablesWithoutDirectSemester as $table) {
|
|
||||||
if (! $this->db->tableExists($table) || ! $this->db->fieldExists('semester', $table)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->dropIndexesContainingColumn($table, 'semester');
|
|
||||||
$this->dropChecksContainingColumn($table, 'semester');
|
|
||||||
$this->forge->dropColumn($table, 'semester');
|
|
||||||
$this->db->resetDataCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->db->resetDataCache();
|
$this->db->resetDataCache();
|
||||||
|
|
||||||
|
|||||||
+2
-9
@@ -61,15 +61,8 @@ final class ApplySchoolYearSemesterAuditCorrections extends Migration
|
|||||||
$this->ensureIndex($table, 'school_year');
|
$this->ensureIndex($table, 'school_year');
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->semesterDropTables as $table) {
|
// Preserve legacy semester columns. They may be redundant for newer
|
||||||
if (! $this->db->tableExists($table) || ! $this->db->fieldExists('semester', $table)) {
|
// code paths, but migration must not delete restored production data.
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->dropIndexesContainingColumn($table, 'semester');
|
|
||||||
$this->dropChecksContainingColumn($table, 'semester');
|
|
||||||
$this->forge->dropColumn($table, 'semester');
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->schoolYearIndexTables as $table) {
|
foreach ($this->schoolYearIndexTables as $table) {
|
||||||
if (! $this->db->tableExists($table) || ! $this->db->fieldExists('school_year', $table)) {
|
if (! $this->db->tableExists($table) || ! $this->db->fieldExists('school_year', $table)) {
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Models;
|
|
||||||
|
|
||||||
use CodeIgniter\Model;
|
|
||||||
|
|
||||||
class StatsModel extends Model
|
|
||||||
{
|
|
||||||
protected $table = 'stats';
|
|
||||||
protected $primaryKey = 'id';
|
|
||||||
protected $allowedFields = [
|
|
||||||
'students',
|
|
||||||
'teachers',
|
|
||||||
'admins',
|
|
||||||
'users'
|
|
||||||
];
|
|
||||||
|
|
||||||
public function getStats()
|
|
||||||
{
|
|
||||||
return $this->findAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,6 +15,7 @@ class DBReset
|
|||||||
{
|
{
|
||||||
$config = config('Database');
|
$config = config('Database');
|
||||||
$dbConfig = $config->{self::DB_GROUP} ?? [];
|
$dbConfig = $config->{self::DB_GROUP} ?? [];
|
||||||
|
self::assertSafeTestDatabase($config->default ?? [], $dbConfig);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$db = Database::connect(self::DB_GROUP);
|
$db = Database::connect(self::DB_GROUP);
|
||||||
@@ -35,14 +36,38 @@ class DBReset
|
|||||||
|
|
||||||
$db->disableForeignKeyChecks();
|
$db->disableForeignKeyChecks();
|
||||||
|
|
||||||
$tables = $db->listTables();
|
$tables = $db->resetDataCache()->listTables();
|
||||||
|
|
||||||
foreach ($tables as $table) {
|
foreach ($tables as $table) {
|
||||||
if (!in_array($table, self::$excludeTables)) {
|
if (!in_array($table, self::$excludeTables, true) && $db->tableExists($table, false)) {
|
||||||
$db->query('TRUNCATE TABLE ' . $db->escapeIdentifiers($table));
|
$db->query('TRUNCATE TABLE ' . $db->escapeIdentifiers($table));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$db->enableForeignKeyChecks();
|
$db->enableForeignKeyChecks();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function assertSafeTestDatabase(array $defaultConfig, array $testConfig): void
|
||||||
|
{
|
||||||
|
$defaultDatabase = (string) ($defaultConfig['database'] ?? '');
|
||||||
|
$testDatabase = (string) ($testConfig['database'] ?? '');
|
||||||
|
|
||||||
|
if ($testDatabase === '') {
|
||||||
|
throw new \RuntimeException('Refusing to reset database: tests database name is empty.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($defaultDatabase !== '' && $testDatabase === $defaultDatabase) {
|
||||||
|
throw new \RuntimeException(sprintf(
|
||||||
|
'Refusing to reset database "%s": tests database matches the default application database.',
|
||||||
|
$testDatabase
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! preg_match('/(^test_|_test$|^tests$|^alrahma_test$)/i', $testDatabase)) {
|
||||||
|
throw new \RuntimeException(sprintf(
|
||||||
|
'Refusing to reset database "%s": test database names must clearly identify a test database.',
|
||||||
|
$testDatabase
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ abstract class ModelCrudTestCase extends CIUnitTestCase
|
|||||||
protected function assertModelCanInsertAndRetrieve(string $modelClass): void
|
protected function assertModelCanInsertAndRetrieve(string $modelClass): void
|
||||||
{
|
{
|
||||||
$model = $this->newModel($modelClass);
|
$model = $this->newModel($modelClass);
|
||||||
|
$this->assertModelFieldsExist($model);
|
||||||
$record = $this->fakeModelRecord($model);
|
$record = $this->fakeModelRecord($model);
|
||||||
$id = $this->primaryKeyValue($record, $model->primaryKey);
|
$id = $this->primaryKeyValue($record, $model->primaryKey);
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ abstract class ModelCrudTestCase extends CIUnitTestCase
|
|||||||
protected function assertModelCanUpdate(string $modelClass): void
|
protected function assertModelCanUpdate(string $modelClass): void
|
||||||
{
|
{
|
||||||
$model = $this->newModel($modelClass);
|
$model = $this->newModel($modelClass);
|
||||||
|
$this->assertModelFieldsExist($model);
|
||||||
$record = $this->fakeModelRecord($model);
|
$record = $this->fakeModelRecord($model);
|
||||||
$id = $this->primaryKeyValue($record, $model->primaryKey);
|
$id = $this->primaryKeyValue($record, $model->primaryKey);
|
||||||
$updateData = $this->updateData($model, $record);
|
$updateData = $this->updateData($model, $record);
|
||||||
@@ -42,6 +44,7 @@ abstract class ModelCrudTestCase extends CIUnitTestCase
|
|||||||
protected function assertModelCanDelete(string $modelClass): void
|
protected function assertModelCanDelete(string $modelClass): void
|
||||||
{
|
{
|
||||||
$model = $this->newModel($modelClass);
|
$model = $this->newModel($modelClass);
|
||||||
|
$this->assertModelFieldsExist($model);
|
||||||
$record = $this->fakeModelRecord($model);
|
$record = $this->fakeModelRecord($model);
|
||||||
$id = $this->primaryKeyValue($record, $model->primaryKey);
|
$id = $this->primaryKeyValue($record, $model->primaryKey);
|
||||||
|
|
||||||
@@ -89,13 +92,34 @@ abstract class ModelCrudTestCase extends CIUnitTestCase
|
|||||||
|
|
||||||
protected function fakeModelRecord(Model $model): array|object
|
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));
|
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
|
protected function fabricatorOverrides(Model $model): array
|
||||||
{
|
{
|
||||||
$overrides = [];
|
$overrides = [];
|
||||||
@@ -184,9 +208,7 @@ abstract class ModelCrudTestCase extends CIUnitTestCase
|
|||||||
|
|
||||||
private function newModel(string $modelClass): Model
|
private function newModel(string $modelClass): Model
|
||||||
{
|
{
|
||||||
if (! class_exists($modelClass)) {
|
$this->assertTrue(class_exists($modelClass), sprintf('Model %s is not part of the application.', $modelClass));
|
||||||
$this->markTestSkipped(sprintf('Model %s is not part of the application.', $modelClass));
|
|
||||||
}
|
|
||||||
|
|
||||||
$model = new $modelClass();
|
$model = new $modelClass();
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,16 @@ foreach ($requiredEnv as $name) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$testDatabase = (string) getenv('TEST_DB_NAME');
|
||||||
|
|
||||||
|
if (! preg_match('/(^test_|_test$|^tests$|^alrahma_test$)/i', $testDatabase)) {
|
||||||
|
fwrite(
|
||||||
|
STDERR,
|
||||||
|
"Refusing to import baseline into {$testDatabase}: TEST_DB_NAME must clearly identify a test database.\n"
|
||||||
|
);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
|
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
|
||||||
|
|
||||||
$db = new mysqli(
|
$db = new mysqli(
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\App\Models;
|
|
||||||
|
|
||||||
use Tests\Support\ModelCrudTestCase;
|
|
||||||
use App\Models\ScoreModel;
|
|
||||||
|
|
||||||
class ScoreModelTest extends ModelCrudTestCase
|
|
||||||
{
|
|
||||||
|
|
||||||
public function testCanInsertAndRetrieve()
|
|
||||||
{
|
|
||||||
$this->assertModelCanInsertAndRetrieve(ScoreModel::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testCanUpdate()
|
|
||||||
{
|
|
||||||
$this->assertModelCanUpdate(ScoreModel::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testCanDelete()
|
|
||||||
{
|
|
||||||
$this->assertModelCanDelete(ScoreModel::class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\App\Models;
|
|
||||||
|
|
||||||
use Tests\Support\ModelCrudTestCase;
|
|
||||||
use App\Models\StatsModel;
|
|
||||||
|
|
||||||
class StatsModelTest extends ModelCrudTestCase
|
|
||||||
{
|
|
||||||
|
|
||||||
public function testCanInsertAndRetrieve()
|
|
||||||
{
|
|
||||||
$this->assertModelCanInsertAndRetrieve(StatsModel::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testCanUpdate()
|
|
||||||
{
|
|
||||||
$this->assertModelCanUpdate(StatsModel::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testCanDelete()
|
|
||||||
{
|
|
||||||
$this->assertModelCanDelete(StatsModel::class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\App\Models;
|
|
||||||
|
|
||||||
use Tests\Support\ModelCrudTestCase;
|
|
||||||
use App\Models\SupportRequestModel;
|
|
||||||
|
|
||||||
class SupportRequestModelTest extends ModelCrudTestCase
|
|
||||||
{
|
|
||||||
|
|
||||||
public function testCanInsertAndRetrieve()
|
|
||||||
{
|
|
||||||
$this->assertModelCanInsertAndRetrieve(SupportRequestModel::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testCanUpdate()
|
|
||||||
{
|
|
||||||
$this->assertModelCanUpdate(SupportRequestModel::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testCanDelete()
|
|
||||||
{
|
|
||||||
$this->assertModelCanDelete(SupportRequestModel::class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user