user = new User('test@example.com', 'hash'); } public function testConstructorSetsFields(): void { $key = new ApiKey($this->user, 'dev laptop', 'abcd1234', 'hashed-key'); self::assertSame('dev laptop', $key->getLabel()); self::assertSame('abcd1234', $key->getKeyPrefix()); self::assertSame('hashed-key', $key->getKeyHash()); self::assertSame($this->user, $key->getUser()); } public function testIsActiveByDefault(): void { $key = new ApiKey($this->user, 'label', 'abcd1234', 'hash'); self::assertTrue($key->isActive()); } public function testCanBeDeactivated(): void { $key = new ApiKey($this->user, 'label', 'abcd1234', 'hash'); $key->setIsActive(false); self::assertFalse($key->isActive()); } public function testIsNotExpiredWithNoExpiry(): void { $key = new ApiKey($this->user, 'label', 'abcd1234', 'hash'); self::assertFalse($key->isExpired()); } public function testIsExpiredWhenExpiryIsPast(): void { $key = new ApiKey($this->user, 'label', 'abcd1234', 'hash'); $key->setExpiresAt(new \DateTimeImmutable('-1 hour')); self::assertTrue($key->isExpired()); } public function testIsNotExpiredWhenExpiryIsFuture(): void { $key = new ApiKey($this->user, 'label', 'abcd1234', 'hash'); $key->setExpiresAt(new \DateTimeImmutable('+1 hour')); self::assertFalse($key->isExpired()); } public function testMarkUsedSetsLastUsedAt(): void { $key = new ApiKey($this->user, 'label', 'abcd1234', 'hash'); self::assertNull($key->getLastUsedAt()); $key->markUsed(); self::assertNotNull($key->getLastUsedAt()); } public function testGrantAndCheckPermission(): void { $key = new ApiKey($this->user, 'label', 'abcd1234', 'hash'); $key->grantPermission('articles.write'); self::assertTrue($key->hasPermission('articles.write')); self::assertFalse($key->hasPermission('orders.delete')); } public function testRawKeyVerifiesAgainstStoredHash(): void { $rawKey = bin2hex(random_bytes(24)); $hash = password_hash($rawKey, PASSWORD_BCRYPT); $prefix = substr($rawKey, 0, 8); $key = new ApiKey($this->user, 'label', $prefix, $hash); self::assertTrue(password_verify($rawKey, $key->getKeyHash())); self::assertSame($prefix, $key->getKeyPrefix()); } }