isFile() && $file->getExtension() === 'php') { $paths[] = $relativeDirectory . '/' . $iterator->getSubPathName(); } } } $paths = array_values(array_unique($paths)); sort($paths); return array_map(static fn (string $path): array => [$path], $paths); } public function testTickSchemaDoesNotUseDatabaseDefaultsForGameSchedules(): void { $schema = file_get_contents(__DIR__ . '/../hwe/sql/schema.sql'); self::assertIsString($schema); foreach ([ '`turntime` BIGINT', '`recent_war` BIGINT', '`last_refresh` BIGINT', '`time` BIGINT', '`valid_until` BIGINT', '`reserved_until` BIGINT', '`open_tick` BIGINT', '`close_tick` BIGINT', ] as $expected) { self::assertStringContainsString($expected, $schema); } self::assertDoesNotMatchRegularExpression('/\b(?:CURRENT_TIMESTAMP|NOW\s*\()/i', $schema); } public function testMonthlyTrafficTimestampUsesLogicalClock(): void { $source = file_get_contents(__DIR__ . '/../hwe/func.php'); self::assertIsString($source); self::assertMatchesRegularExpression( '/function updateTraffic\(\).*?GameClock::fromStorage\(\$gameStor\)->formatNow\(\).*?function CheckOverhead\(/s', $source, ); self::assertDoesNotMatchRegularExpression( '/function updateTraffic\(\).*?TimeUtil::now\(.*?function CheckOverhead\(/s', $source, ); } public function testGameLoginDeathCheckUsesTicksWhileSessionTtlRemainsOperational(): void { $source = file_get_contents(__DIR__ . '/../src/sammo/Session.php'); self::assertIsString($source); self::assertMatchesRegularExpression( '/function loginGame\(.*?GameClock::isInitialized\(\$gameStor\).*?GameClock::fromStorage\(\$gameStor\)->nowTick\(\).*?GameClock::TICKS_PER_TURN.*?function logoutGame\(/s', $source, ); self::assertMatchesRegularExpression( '/function loginGame\(.*?new \\\\DateTimeImmutable.*?getTimestamp\(\).*?function logoutGame\(/s', $source, ); self::assertDoesNotMatchRegularExpression( '/function loginGame\(.*?new\s+\\?DateTime(?:Immutable)?\([^)]*turntime.*?function logoutGame\(/s', $source, ); } public function testBrowserDoesNotCompareProjectedGameDatesToItsWallClock(): void { $expectations = [ 'hwe/ts/PageVote.vue' => ['currentVote.value.isOpen'], 'hwe/ts/components/MessagePlate.vue' => ['msg.clockMode === "manual"'], 'hwe/ts/gateway/entrance.ts' => ['resolveGatewayOpenState(game.isOpen, game.opentime, now)'], 'hwe/ts/select_npc.ts' => ['logicalClockRunning'], 'hwe/ts/select_general_from_pool.ts' => ['logicalClockRunning'], ]; foreach ($expectations as $path => $needles) { $source = file_get_contents(__DIR__ . '/../' . $path); self::assertIsString($source); foreach ($needles as $needle) { self::assertStringContainsString($needle, $source, $path); } } self::assertStringNotContainsString('formatTime(new Date())', file_get_contents(__DIR__ . '/../hwe/ts/PageVote.vue')); } public function testGatewayReadsClockOnlyAfterClosedReservationResponse(): void { $source = file_get_contents(__DIR__ . '/../hwe/j_server_basic_info.php'); self::assertIsString($source); $closedBranch = strpos($source, "if(file_exists(__DIR__.'/.htaccess'))"); $closedBranchEnd = strpos($source, '//TODO: 천통시에도 예약 오픈 알림이 필요..?'); $clockDetection = strpos($source, 'GameClock::isInitialized($gameStor)'); $clockRead = strpos($source, 'GameClock::fromStorage($gameStor)'); self::assertNotFalse($closedBranch); self::assertNotFalse($closedBranchEnd); self::assertNotFalse($clockDetection); self::assertNotFalse($clockRead); self::assertGreaterThan($closedBranch, $closedBranchEnd); self::assertGreaterThan($closedBranchEnd, $clockDetection); self::assertGreaterThan($closedBranchEnd, $clockRead); } public function testGatewayPreservesWallClockProfilesAndFormatsLogicalOpenTime(): void { $source = file_get_contents(__DIR__ . '/../hwe/j_server_basic_info.php'); self::assertIsString($source); self::assertStringContainsString( '$usesLogicalClock = GameClock::isInitialized($gameStor);', $source, ); self::assertStringContainsString( '$admin[\'opentime\'] = $clock->formatTick(Util::toInt($admin[\'opentime\']));', $source, ); self::assertStringContainsString( '$admin[\'isOpen\'] = new \\DateTimeImmutable((string)$admin[\'opentime\']) <= GameClock::readWallTime();', $source, ); } }