"""Phase 1 integration smoke tests — connect, auth-fail, network-fail. Marked ``integration`` so the default ``pytest`` invocation skips them. Run with ``pytest -m integration`` after ``docker compose up`` (or with the container already running). """ from __future__ import annotations import pytest import informix_db from tests.conftest import ConnParams pytestmark = pytest.mark.integration def test_connect_and_close(conn_params: ConnParams) -> None: """The happy path: connect with valid creds, then close cleanly.""" conn = informix_db.connect( host=conn_params.host, port=conn_params.port, user=conn_params.user, password=conn_params.password, database=conn_params.database, server=conn_params.server, connect_timeout=10.0, read_timeout=10.0, ) assert conn.closed is False conn.close() assert conn.closed is True def test_close_is_idempotent(conn_params: ConnParams) -> None: """``close()`` must be safely callable multiple times.""" conn = informix_db.connect( host=conn_params.host, port=conn_params.port, user=conn_params.user, password=conn_params.password, database=conn_params.database, server=conn_params.server, connect_timeout=10.0, ) conn.close() conn.close() # must not raise assert conn.closed is True def test_context_manager(conn_params: ConnParams) -> None: """``with`` block closes on exit.""" with informix_db.connect( host=conn_params.host, port=conn_params.port, user=conn_params.user, password=conn_params.password, database=conn_params.database, server=conn_params.server, connect_timeout=10.0, ) as conn: assert conn.closed is False assert conn.closed is True def test_bad_password_raises_operational_error(conn_params: ConnParams) -> None: """Auth failure must be ``OperationalError``, not a generic socket error.""" with pytest.raises(informix_db.OperationalError): informix_db.connect( host=conn_params.host, port=conn_params.port, user=conn_params.user, password="definitely-the-wrong-password", database=conn_params.database, server=conn_params.server, connect_timeout=5.0, read_timeout=5.0, ) def test_bad_host_raises_operational_error(conn_params: ConnParams) -> None: """Network-level failure must also be ``OperationalError``.""" # Pick an unused port on loopback. with pytest.raises(informix_db.OperationalError, match="cannot connect"): informix_db.connect( host="127.0.0.1", port=1, # IANA-reserved, nothing listens user="x", password="x", database="x", server="x", connect_timeout=2.0, ) def test_cursor_returns_cursor_object(conn_params: ConnParams) -> None: """Phase 2: ``cursor()`` returns a Cursor; SELECT execution is partial work-in-progress.""" with informix_db.connect( host=conn_params.host, port=conn_params.port, user=conn_params.user, password=conn_params.password, database=conn_params.database, server=conn_params.server, connect_timeout=10.0, ) as conn: cur = conn.cursor() assert cur is not None assert cur.description is None # nothing executed yet assert cur.rowcount == -1 assert cur.fetchone() is None cur.close() assert cur.closed is True def test_cursor_with_parameters_for_dml_works(conn_params: ConnParams) -> None: """Phase 4: parameter binding works for DML. Parameterized SELECT lands in Phase 4.x — see ``tests/test_params.py``. """ with informix_db.connect( host=conn_params.host, port=conn_params.port, user=conn_params.user, password=conn_params.password, database=conn_params.database, server=conn_params.server, connect_timeout=10.0, ) as conn: cur = conn.cursor() cur.execute("CREATE TEMP TABLE t_smoke_p (id INTEGER)") cur.execute("INSERT INTO t_smoke_p VALUES (?)", (42,)) assert cur.rowcount == 1 cur.execute("SELECT id FROM t_smoke_p") assert cur.fetchall() == [(42,)]