getMessageId()); self::assertSame('user@example.com', $tracking->getRecipient()); self::assertSame('Hello', $tracking->getSubject()); self::assertSame('sent', $tracking->getState()); self::assertInstanceOf(\DateTimeImmutable::class, $tracking->getSentAt()); self::assertNull($tracking->getOpenedAt()); self::assertNull($tracking->getId()); } public function testMarkAsOpenedTransitionsState(): void { $tracking = new EmailTracking('msg-123', 'user@example.com', 'Hello'); $tracking->markAsOpened(); self::assertSame('opened', $tracking->getState()); self::assertInstanceOf(\DateTimeImmutable::class, $tracking->getOpenedAt()); } public function testMarkAsOpenedDoesNotTransitionTwice(): void { $tracking = new EmailTracking('msg-123', 'user@example.com', 'Hello'); $tracking->markAsOpened(); $firstOpenedAt = $tracking->getOpenedAt(); $tracking->markAsOpened(); self::assertSame($firstOpenedAt, $tracking->getOpenedAt()); } }