sqlitePath = sys_get_temp_dir() . '/irdb-auth-' . bin2hex(random_bytes(6)) . '.sqlite'; touch($this->sqlitePath); $config = new Config([ 'paths' => [ 'migrations' => __DIR__ . '/../../../db/migrations', 'seeds' => __DIR__ . '/../../../db/seeds', ], 'environments' => [ 'default_migration_table' => 'phinxlog', 'default_environment' => 'test', 'test' => [ 'adapter' => 'sqlite', 'name' => $this->sqlitePath, 'suffix' => '', ], ], 'version_order' => 'creation', ]); $manager = new Manager($config, new ArrayInput([]), new NullOutput()); $manager->migrate('test'); $manager->seed('test'); $settings = [ 'app_env' => 'development', 'log_level' => \Monolog\Level::Warning, 'app_secret' => 'test', 'db' => [ 'driver' => 'sqlite', 'sqlite_path' => $this->sqlitePath, 'mysql_host' => '', 'mysql_port' => 3306, 'mysql_database' => '', 'mysql_username' => '', 'mysql_password' => '', ], 'ui_service_token' => '', 'internal_job_token' => '', 'ui_origin' => 'http://localhost:8080', 'oidc_default_role' => Role::Viewer, 'score_hard_cutoff_days' => 365, 'rate_limit_per_second' => 1000, 'cidr_evaluator_ttl_seconds' => 0, 'blocklist_cache_ttl_seconds' => 0, 'geoip' => [ 'enabled' => true, 'provider' => 'dbip', 'country_db' => sys_get_temp_dir() . '/irdb-geoip-missing-country.mmdb', 'asn_db' => sys_get_temp_dir() . '/irdb-geoip-missing-asn.mmdb', 'maxmind_license_key' => '', 'ipinfo_token' => '', 'refresh_interval_days' => 7, ], ]; $this->container = Container::build($settings); // Replace the logger with a null sink so integration tests don't spam // stdout (PHPUnit treats unexpected output as a "risky" outcome). if (method_exists($this->container, 'set')) { $nullLogger = new Logger('test'); $nullLogger->pushHandler(new NullHandler()); /** @var \DI\Container $container */ $container = $this->container; $container->set(LoggerInterface::class, $nullLogger); } /** @var Connection $conn */ $conn = $this->container->get(Connection::class); $this->db = $conn; $this->app = AppFactory::build($this->container); } protected function tearDown(): void { $this->db->close(); if (file_exists($this->sqlitePath)) { @unlink($this->sqlitePath); } } /** * Issues a token of the given kind, persists it, and returns the raw * string. The caller can then send it as `Authorization: Bearer …`. */ protected function createToken( TokenKind $kind, ?Role $role = null, ?int $reporterId = null, ?int $consumerId = null, ?int $userId = null, ): string { /** @var TokenIssuer $issuer */ $issuer = $this->container->get(TokenIssuer::class); /** @var TokenHasher $hasher */ $hasher = $this->container->get(TokenHasher::class); /** @var TokenRepository $repo */ $repo = $this->container->get(TokenRepository::class); $raw = $issuer->issue($kind); $repo->create(new TokenRecord( id: null, kind: $kind, hash: $hasher->hash($raw), prefix: substr($raw, 0, 8), reporterId: $reporterId, consumerId: $consumerId, role: $role, expiresAt: null, revokedAt: null, lastUsedAt: null, userId: $userId, )); return $raw; } /** * Inserts a row in `users` with the given role and returns the id. * Pass `disabled: true` to seed a disabled row (SEC_REVIEW F11 tests). */ protected function createUser( Role $role, bool $isLocal = false, ?string $subject = null, bool $disabled = false, ): int { $this->db->insert('users', [ 'subject' => $subject, 'email' => $isLocal ? null : 'user@example.com', 'display_name' => $isLocal ? 'admin' : 'OIDC User', 'role' => $role->value, 'is_local' => $isLocal ? 1 : 0, 'disabled_at' => $disabled ? (new \DateTimeImmutable('now', new \DateTimeZone('UTC')))->format('Y-m-d H:i:s') : null, ]); return (int) $this->db->lastInsertId(); } protected function createReporter(string $name = 'rep-test'): int { $this->db->insert('reporters', [ 'name' => $name, 'trust_weight' => '1.00', 'is_active' => 1, ]); return (int) $this->db->lastInsertId(); } /** * @param array $headers */ protected function request(string $method, string $path, array $headers = [], ?string $body = null): \Psr\Http\Message\ResponseInterface { $reqFactory = new ServerRequestFactory(); $request = $reqFactory->createServerRequest($method, $path); foreach ($headers as $name => $value) { $request = $request->withHeader($name, $value); } if ($body !== null) { $stream = (new StreamFactory())->createStream($body); $request = $request->withBody($stream); } return $this->app->handle($request); } /** * @return array */ protected function decode(\Psr\Http\Message\ResponseInterface $response): array { $raw = (string) $response->getBody(); $decoded = json_decode($raw, true); self::assertIsArray($decoded, 'response body was not JSON: ' . $raw); return $decoded; } }