Source code for pytest_databases.docker.oracle

from __future__ import annotations

import contextlib
import shlex
from dataclasses import dataclass
from typing import TYPE_CHECKING

import pytest

from pytest_databases.helpers import get_xdist_worker_num
from pytest_databases.types import ServiceContainer

if TYPE_CHECKING:
    from collections.abc import Generator, Iterator

    from docker.models.containers import Container

    from pytest_databases._service import DockerService


ORACLE_USER = "app"
ORACLE_PASSWORD = "super-secret"
ORACLE_SYSTEM_PASSWORD = "super-secret"


def _output_to_bytes(output: bytes | str | Iterator[bytes]) -> bytes:
    if isinstance(output, bytes):
        return output
    if isinstance(output, str):
        return output.encode()
    return b"".join(output)


def _normalize_sqlplus_statement(sql: str) -> str:
    statement = sql.strip()
    if not statement.endswith(";"):
        statement = f"{statement};"
    return statement


def _exec_sqlplus(
    container: Container,
    user: str,
    password: str,
    service_name: str,
    sql: str,
) -> tuple[int, bytes]:
    script = "\n".join([
        "SET HEADING OFF FEEDBACK OFF PAGESIZE 0 VERIFY OFF ECHO OFF",
        "WHENEVER OSERROR EXIT 9",
        "WHENEVER SQLERROR EXIT SQL.SQLCODE",
        _normalize_sqlplus_statement(sql),
        "EXIT",
        "",
    ])
    connect_string = f"{user}/{password}@//localhost:1521/{service_name}"
    command = f"printf '%s' {shlex.quote(script)} | sqlplus -L -S {shlex.quote(connect_string)}"
    result = container.exec_run(["bash", "-lc", command])
    return result.exit_code if result.exit_code is not None else -1, _output_to_bytes(result.output)


[docs] @dataclass class OracleService(ServiceContainer): user: str password: str system_password: str service_name: str
@contextlib.contextmanager def _provide_oracle_service( docker_service: DockerService, image: str, name: str, service_name: str, user: str, password: str, system_password: str, ) -> Generator[OracleService, None, None]: def check(_service: ServiceContainer) -> bool: exit_code, output = _exec_sqlplus( _service.container, user, password, service_name, "SELECT 1 FROM dual", ) return exit_code == 0 and output.strip() == b"1" worker_num = get_xdist_worker_num() if worker_num is not None: name = f"{name}_{worker_num}" with docker_service.run( image=image, name=name, check=check, container_port=1521, timeout=60, env={ "ORACLE_PASSWORD": system_password, "APP_USER_PASSWORD": password, "APP_USER": user, }, ) as service: yield OracleService( host=service.host, port=service.port, container=service.container, system_password=system_password, user=user, password=password, service_name=service_name, )
[docs] @pytest.fixture(autouse=False, scope="session") def oracle_23ai_image() -> str: return "gvenzl/oracle-free:23-slim-faststart"
[docs] @pytest.fixture(autouse=False, scope="session") def oracle_23ai_service_name() -> str: return "FREEPDB1"
[docs] @pytest.fixture(autouse=False, scope="session") def oracle_18c_image() -> str: return "gvenzl/oracle-xe:18-slim-faststart"
[docs] @pytest.fixture(autouse=False, scope="session") def oracle_18c_service_name() -> str: return "xepdb1"
[docs] @pytest.fixture(autouse=False, scope="session") def oracle_user() -> str: return ORACLE_USER
[docs] @pytest.fixture(autouse=False, scope="session") def oracle_password() -> str: return ORACLE_PASSWORD
[docs] @pytest.fixture(autouse=False, scope="session") def oracle_system_password() -> str: return ORACLE_SYSTEM_PASSWORD
[docs] @pytest.fixture(autouse=False, scope="session") def oracle_23ai_service( docker_service: DockerService, oracle_23ai_image: str, oracle_23ai_service_name: str, oracle_user: str, oracle_password: str, oracle_system_password: str, ) -> Generator[OracleService, None, None]: with _provide_oracle_service( image=oracle_23ai_image, name="oracle23ai", service_name=oracle_23ai_service_name, user=oracle_user, password=oracle_password, system_password=oracle_system_password, docker_service=docker_service, ) as service: yield service
[docs] @pytest.fixture(autouse=False, scope="session") def oracle_18c_service( docker_service: DockerService, oracle_18c_image: str, oracle_18c_service_name: str, oracle_user: str, oracle_password: str, oracle_system_password: str, ) -> Generator[OracleService, None, None]: with _provide_oracle_service( image=oracle_18c_image, name="oracle18c", service_name=oracle_18c_service_name, user=oracle_user, password=oracle_password, system_password=oracle_system_password, docker_service=docker_service, ) as service: yield service
# alias to the latest
[docs] @pytest.fixture(autouse=False, scope="session") def oracle_service(oracle_23ai_service: OracleService) -> OracleService: return oracle_23ai_service