'3.3.3.3', 'date_start' => 300], ['user_ip' => '1.1.1.1', 'date_start' => 100], ['user_ip' => '2.2.2.2', 'date_start' => 200], ]; $this->assertSame('1.1.1.1', ConnectionTracker::oldestConnectionIP($rRows)); $this->assertSame('1.1.1.1', ConnectionTracker::oldestConnectionIP(array_reverse($rRows))); } public function testOldestConnectionIpSkipsUnreadableRowsAndRowsWithoutAnIp(): void { $rRows = [ false, ['date_start' => 50], ['user_ip' => '', 'date_start' => 60], ['user_ip' => '2.2.2.2', 'date_start' => 200], ['user_ip' => '1.1.1.1', 'date_start' => 100], ]; $this->assertSame('1.1.1.1', ConnectionTracker::oldestConnectionIP($rRows)); } public function testOldestConnectionIpSortsUndatedRowsLastAndKeepsTheFirstOnATie(): void { $rRows = [ ['user_ip' => '9.9.9.9'], ['user_ip' => '1.1.1.1', 'date_start' => 100], ['user_ip' => '2.2.2.2', 'date_start' => 100], ]; $this->assertSame('1.1.1.1', ConnectionTracker::oldestConnectionIP($rRows)); $this->assertSame('9.9.9.9', ConnectionTracker::oldestConnectionIP([['user_ip' => '9.9.9.9']])); } public function testNoConnectionsMeansNoAcceptedIp(): void { $this->assertNull(ConnectionTracker::oldestConnectionIP([])); } public function testConnectionKeysAreNotConnectionRows(): void { // The LINE# members, uuid strings, carry no IP. $this->assertNull(ConnectionTracker::oldestConnectionIP(['uuid-a', 'uuid-b'])); } public function testOldestIpIsAcceptedForALineWithActiveConnections(): void { if (!extension_loaded('redis')) { $this->markTestSkipped('phpredis is not loaded; the mGet branch needs a \Redis double.'); } // Sorting these LINE# keys by date_start was "ValueError: Array sizes are // inconsistent" as soon as the line had one active connection. $rRedis = $this->createMock(\Redis::class); $rRedis->expects($this->once())->method('zRangeByScore')->with('LINE#7', '-inf', '+inf')->willReturn(['k-new', 'k-old']); $rRedis->expects($this->once())->method('mGet')->with(['k-new', 'k-old'])->willReturn([ igbinary_serialize(['user_ip' => '2.2.2.2', 'date_start' => 200]), igbinary_serialize(['user_ip' => '1.1.1.1', 'date_start' => 100]), ]); $this->assertSame('1.1.1.1', ConnectionTracker::acceptedLineIP($rRedis, '7')); } public function testHmacIdentityHasNoAcceptedIpAndNeverQueriesRedis(): void { if (!extension_loaded('redis')) { $this->markTestSkipped('phpredis is not loaded; the zRangeByScore branch needs a \Redis double.'); } // An HMAC token's line id is null; getLineConnections(int) was a TypeError. $rRedis = $this->createMock(\Redis::class); $rRedis->expects($this->never())->method('zRangeByScore'); $rRedis->expects($this->never())->method('mGet'); $this->assertNull(ConnectionTracker::acceptedLineIP($rRedis, null)); } public function testLineConnectionRowsReadsTheRowsBehindTheLineKeys(): void { if (!extension_loaded('redis')) { $this->markTestSkipped('phpredis is not loaded; the mGet branch needs a \Redis double.'); } $rRedis = $this->createMock(\Redis::class); $rRedis->expects($this->once())->method('zRangeByScore')->with('LINE#7', '-inf', '+inf')->willReturn(['a', 'b']); $rRedis->expects($this->once())->method('mGet')->with(['a', 'b'])->willReturn([igbinary_serialize(['user_ip' => '1.1.1.1', 'date_start' => 5]), false]); $this->assertSame([['user_ip' => '1.1.1.1', 'date_start' => 5]], ConnectionTracker::getLineConnectionRows($rRedis, 7, true)); } public function testDroppedConnectionReadsAsNoRows(): void { if (!extension_loaded('redis')) { $this->markTestSkipped('phpredis is not loaded; the mGet branch needs a \Redis double.'); } $rRedis = $this->createMock(\Redis::class); $rRedis->method('zRangeByScore')->willReturn(['a']); $rRedis->method('mGet')->willReturn(false); $this->assertSame([], ConnectionTracker::getLineConnectionRows($rRedis, 7, true)); } public function testUnreachableRedisReadsAsNoRows(): void { $this->assertSame([], ConnectionTracker::getLineConnectionRows(null, 7, true)); } public function testFailedKeyLookupReadsAsNoRows(): void { if (!extension_loaded('redis')) { $this->markTestSkipped('phpredis is not loaded; the zRangeByScore branch needs a \Redis double.'); } $rRedis = $this->createMock(\Redis::class); $rRedis->method('zRangeByScore')->willReturn(false); $rRedis->expects($this->never())->method('mGet'); $this->assertSame([], ConnectionTracker::getLineConnectionRows($rRedis, 7, true)); } public function testLiveEndpointTakesTheAcceptedIpFromTheSeam(): void { $rPath = MAIN_HOME . 'Public/stream/live.php'; $this->assertFileExists($rPath); $rSource = (string) file_get_contents($rPath); $this->assertSame(1, substr_count($rSource, 'ConnectionTracker::acceptedLineIP('), 'live.php must take the Redis-mode accepted IP from acceptedLineIP()'); } }