createMock(BaseConnection::class); $db->method('tableExists')->willReturn(true); $yearStatusModel = $this->getMockBuilder(StudentYearStatusModel::class) ->disableOriginalConstructor() ->onlyMethods(['first']) ->addMethods(['where']) ->getMock(); $yearStatusModel->method('where')->willReturnSelf(); $yearStatusModel->method('first')->willReturn(null); $service = new StudentYearStatusService($db, $yearStatusModel); $this->assertTrue($service->isNew(42, '2025-2026')); } public function testIsNewReadsYearScopedFlag(): void { $db = $this->createMock(BaseConnection::class); $db->method('tableExists')->willReturn(true); $yearStatusModel = $this->getMockBuilder(StudentYearStatusModel::class) ->disableOriginalConstructor() ->onlyMethods(['first']) ->addMethods(['where']) ->getMock(); $yearStatusModel->method('where')->willReturnSelf(); $yearStatusModel->method('first')->willReturn([ 'student_id' => 7, 'school_year' => '2025-2026', 'is_new' => 0, ]); $service = new StudentYearStatusService($db, $yearStatusModel); $this->assertFalse($service->isNew(7, '2025-2026')); } public function testAttachToStudentsAppliesYearFlags(): void { $db = $this->createMock(BaseConnection::class); $db->method('tableExists')->willReturn(true); $yearStatusModel = $this->getMockBuilder(StudentYearStatusModel::class) ->disableOriginalConstructor() ->onlyMethods(['findAll']) ->addMethods(['select', 'whereIn', 'where']) ->getMock(); $yearStatusModel->method('select')->willReturnSelf(); $yearStatusModel->method('whereIn')->willReturnSelf(); $yearStatusModel->method('where')->willReturnSelf(); $yearStatusModel->method('findAll')->willReturn([ ['student_id' => 1, 'is_new' => 1], ['student_id' => 2, 'is_new' => 0], ]); $service = new StudentYearStatusService($db, $yearStatusModel); $students = [ ['id' => 1, 'is_new' => 0], ['student_id' => 2, 'is_new' => 1], ['id' => 3, 'is_new' => 0], ]; $service->attachToStudents($students, '2025-2026'); $this->assertSame(1, $students[0]['is_new']); $this->assertSame(0, $students[1]['is_new']); $this->assertSame(1, $students[2]['is_new']); } public function testUpsertInsertsYearRowOnly(): void { $db = $this->createMock(BaseConnection::class); $db->method('tableExists')->willReturn(true); $yearStatusModel = $this->getMockBuilder(StudentYearStatusModel::class) ->disableOriginalConstructor() ->onlyMethods(['insert', 'update', 'first']) ->addMethods(['where']) ->getMock(); $yearStatusModel->method('where')->willReturnSelf(); $yearStatusModel->method('first')->willReturn(null); $yearStatusModel->expects($this->once())->method('insert')->with([ 'student_id' => 9, 'school_year' => '2025-2026', 'is_new' => 0, ]); $service = new StudentYearStatusService($db, $yearStatusModel); $service->upsert(9, '2025-2026', false); } public function testRegistryIncludesStudentYearStatus(): void { $this->assertTrue(SchoolYearTableRegistry::isYearScoped('student_year_status')); } }