whereCalls[] = [$key, $value, $escape]; return $this; } } class InventoryFilterDbStub { public function escape($value): string { return "'" . str_replace("'", "''", (string) $value) . "'"; } } class InventorySchemaNormalizationTest extends CIUnitTestCase { public function testAffectedRoutesAreRegistered(): void { $routes = file_get_contents(ROOTPATH . 'app/Config/Routes.php'); $this->assertStringContainsString("\$routes->get('card', 'View\\FamilyAdminController::card')", $routes); $this->assertStringContainsString("\$routes->get('summary-all', 'View\\InventoryController::summaryAll')", $routes); $this->assertStringContainsString("\$routes->get('/', 'View\\InventoryController::index')", $routes); $this->assertStringContainsString("\$routes->get('(classroom|book|office|kitchen)', 'View\\InventoryController::index/\$1')", $routes); } public function testInventoryPeriodFilterUsesMovementExistsWithoutJoiningDuplicates(): void { $controller = new NormalizedInventoryTestController(); $dbProperty = new ReflectionProperty(InventoryController::class, 'db'); $dbProperty->setAccessible(true); $dbProperty->setValue($controller, new InventoryFilterDbStub()); $builder = new InventoryFilterBuilderSpy(); $method = (new ReflectionClass(InventoryController::class))->getMethod('applyInventoryMovementPeriodFilter'); $method->setAccessible(true); $method->invoke($controller, $builder, 'i', '2025-2026', 'Fall'); $this->assertCount(1, $builder->whereCalls); [$sql, $value, $escape] = $builder->whereCalls[0]; $this->assertNull($value); $this->assertFalse($escape); $this->assertStringContainsString('EXISTS (SELECT 1 FROM inventory_movements m', $sql); $this->assertStringContainsString('m.item_id = i.id', $sql); $this->assertStringContainsString("m.school_year = '2025-2026'", $sql); $this->assertStringContainsString("m.semester = 'Fall'", $sql); $this->assertStringNotContainsString('inventory_items.school_year', $sql); $this->assertStringNotContainsString('i.school_year', $sql); } public function testInventoryAllYearsWithoutSemesterDoesNotFilterItems(): void { $controller = new NormalizedInventoryTestController(); $dbProperty = new ReflectionProperty(InventoryController::class, 'db'); $dbProperty->setAccessible(true); $dbProperty->setValue($controller, new InventoryFilterDbStub()); $builder = new InventoryFilterBuilderSpy(); $method = (new ReflectionClass(InventoryController::class))->getMethod('applyInventoryMovementPeriodFilter'); $method->setAccessible(true); $method->invoke($controller, $builder, 'i', 'All', ''); $this->assertSame([], $builder->whereCalls); } public function testPatchedFilesDoNotReferenceRemovedNormalizedColumns(): void { $familyAdmin = file_get_contents(ROOTPATH . 'app/Controllers/View/FamilyAdminController.php'); $parent = file_get_contents(ROOTPATH . 'app/Controllers/View/ParentController.php'); $inventory = file_get_contents(ROOTPATH . 'app/Controllers/View/InventoryController.php'); $classPreparation = file_get_contents(ROOTPATH . 'app/Controllers/View/ClassPreparationController.php'); $this->assertStringNotContainsString('emergency_contact_name, relation, cellphone, email, school_year, semester', $familyAdmin); $this->assertStringNotContainsString("'school_year' => \$schoolYear", $parent); $this->assertStringNotContainsString("'semester' => \$semester", $parent); $this->assertStringNotContainsString("->where('i.school_year'", $inventory); $this->assertStringNotContainsString("->where('ii.school_year'", $classPreparation); $this->assertStringNotContainsString("->where('ii.semester'", $classPreparation); } }