find('app:tenant:dump')); } private function outputPath(): string { return sys_get_temp_dir() . '/tenant-dump-test-' . bin2hex(random_bytes(8)) . '.sql'; } public function testDumpsOnlyTheRequestedClinicRows(): void { $clinic = new Clinic($this->createUser(['ROLE_CLINIC'])); $clinic->setName('کلینیک بکاپ'); $this->em->persist($clinic); $this->em->flush(); $path = $this->outputPath(); $tester = $this->tester(); $exit = $tester->execute(['--tenant' => 'clinic:' . $clinic->getId(), '--output' => $path]); self::assertSame(0, $exit, $tester->getDisplay()); self::assertFileExists($path); $sql = (string) file_get_contents($path); self::assertStringNotContainsString("'doctor',", $sql, 'ردیف محیط دیگری نباید در خروجی باشد'); unlink($path); } /** ⚠️ محیط بدون داده: فایل معتبر، بدون خطا. */ public function testTenantWithNoRowsStillProducesAValidFile(): void { $doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر بی‌داده'); $this->em->persist($doctor); $this->em->flush(); $path = $this->outputPath(); $tester = $this->tester(); self::assertSame(0, $tester->execute(['--tenant' => 'doctor:' . $doctor->getId(), '--output' => $path])); self::assertFileExists($path); unlink($path); } public function testUnknownTenantFails(): void { $path = $this->outputPath(); $tester = $this->tester(); self::assertSame(1, $tester->execute(['--tenant' => 'clinic:99999999', '--output' => $path])); self::assertStringContainsString('وجود ندارد', $tester->getDisplay()); } #[DataProvider('malformedTenants')] public function testMalformedTenantIsRejectedBeforeTouchingTheDatabase(string $tenant): void { $tester = $this->tester(); self::assertSame(1, $tester->execute(['--tenant' => $tenant, '--output' => $this->outputPath()])); self::assertStringContainsString('doctor:', $tester->getDisplay()); } /** @return iterable */ public static function malformedTenants(): iterable { yield 'sql injection' => ['clinic:1 OR 1=1']; yield 'shell injection' => ['clinic:1; rm -rf /']; yield 'unknown type' => ['admin:1']; yield 'zero id' => ['clinic:0']; yield 'negative id' => ['clinic:-1']; yield 'missing id' => ['clinic']; yield 'empty' => ['']; } public function testOutputOptionIsRequired(): void { $tester = $this->tester(); self::assertSame(1, $tester->execute(['--tenant' => 'clinic:1'])); self::assertStringContainsString('--output', $tester->getDisplay()); } }