'add_user', 'active_codes' => 'users', 'active_codes_batch' => 'users', 'active_codes_mass' => 'mass_edit_lines', ]; private const CHILD = <<<'PHP' 9, 'member_group_id' => $rIn['group']]; $rPermissions = ['is_admin' => 1, 'advanced' => $rIn['advanced']]; $db = new stdClass(); $language = AuditAdminViewsLanguage::class; $batches = [['batch_name' => 'B1', 'package_name' => 'P', 'creator_name' => 'admin', 'total_codes' => 1, 'stock_count' => 1, 'active_count' => 0, 'created_at' => 0]]; $resellers = $rResellers = $rPackages = $rBouquets = []; require MAIN_HOME . 'Public/Views/admin/' . $rIn['view'] . '.php'; } PHP; private string $rChild; protected function setUp(): void { $this->rChild = sys_get_temp_dir() . '/xcvm-code-links-' . getmypid() . '-' . bin2hex(random_bytes(4)) . '.php'; file_put_contents($this->rChild, str_replace('%BOOTSTRAP%', var_export(dirname(__DIR__) . '/bootstrap.php', true), self::CHILD)); } protected function tearDown(): void { @unlink($this->rChild); } /** * The activation-code pages a view leads an administrator to, by a link or * by sending the browser there, the page itself left out. * * @param array $rAdvanced the permissions the administrator's group lists * @return list */ private function leadsTo(string $rView, array $rAdvanced, int $rGroup = 5): array { $rIn = ['view' => $rView, 'advanced' => array_values($rAdvanced), 'group' => $rGroup]; $rProc = proc_open([...xcvm_test_child_php(), '-d', 'display_errors=stderr', $this->rChild, (string) json_encode($rIn)], [0 => ['file', '/dev/null', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $rPipes); $this->assertIsResource($rProc); $rOut = (string) stream_get_contents($rPipes[1]); $rErr = (string) stream_get_contents($rPipes[2]); proc_close($rProc); $this->assertSame('', $rErr, $rView); $this->assertStringContainsString('', $rOut, $rView . ' was rendered to its end'); preg_match_all('/(?:\bhref="|\blocation\.href = [\'"])(active_codes?(?:_[a-z]+)?)\b/', $rOut, $rLinks); $rPages = array_values(array_unique(array_diff($rLinks[1], [$rView]))); sort($rPages); return $rPages; } /** @return list the other activation-code pages, in the order leadsTo() gives them */ private function others(string $rView): array { $rPages = array_values(array_diff(array_keys(self::PAGES), [$rView])); sort($rPages); return $rPages; } public function testAPageOffersOnlyThePagesTheGroupMayOpen(): void { $rRefused = []; foreach (self::PAGES as $rView => $rKey) { // The group holds what opens this page, and nothing else. foreach ($this->leadsTo($rView, [$rKey]) as $rPage) { if (self::PAGES[$rPage] !== $rKey) { $rRefused[] = $rView . ' leads to ' . $rPage . ' without ' . self::PAGES[$rPage]; } } } $this->assertSame([], $rRefused); } public function testAPageOffersEveryPageTheGroupMayOpen(): void { // What each page leads to when nothing is held back. $rLeads = [ 'active_code' => ['active_codes', 'active_codes_batch'], 'active_codes' => $this->others('active_codes'), 'active_codes_batch' => ['active_code', 'active_codes'], 'active_codes_mass' => ['active_codes'], ]; foreach ($rLeads as $rView => $rPages) { $this->assertSame($rPages, $this->leadsTo($rView, PermissionReference::keys()), $rView . ' with every permission'); $this->assertSame($rPages, $this->leadsTo($rView, ['ticket'], 1), $rView . ' for group 1'); $this->assertSame($rPages, $this->leadsTo($rView, []), $rView . ' for a group that lists no permissions'); // One permission more than the page's own opens the pages it guards. foreach (array_diff(array_unique(self::PAGES), [self::PAGES[$rView]]) as $rKey) { $rOpened = array_values(array_filter($rPages, static fn(string $rPage): bool => in_array(self::PAGES[$rPage], [$rKey, self::PAGES[$rView]], true))); $this->assertSame($rOpened, $this->leadsTo($rView, [self::PAGES[$rView], $rKey]), $rView . ' with ' . $rKey); } } } public function testAMassEditEndsOnAPageTheGroupMayOpen(): void { // The mass edit sends the browser on when it is done: to the list, or back here. $this->assertSame([], $this->leadsTo('active_codes_mass', ['mass_edit_lines'])); $this->assertSame(['active_codes'], $this->leadsTo('active_codes_mass', ['mass_edit_lines', 'users'])); } }