*** Settings *** Documentation Integration tests for PostgreSQL storage backend abstraction layer. ... Tests the dialect-agnostic ORM models, connection configuration, ... and database URL selection logic using SQLite as the test backend. ... Tests tagged @requires_postgresql are excluded from default CI runs. Library OperatingSystem Library String Library Collections Library Process Library indentation_library.py Resource ${CURDIR}/common.resource *** Variables *** ${PYTHON} python *** Test Cases *** Settings Resolve Database URL Local Mode [Documentation] Verify resolve_database_url returns SQLite in local mode ${script}= Catenate SEPARATOR=\n ... import os ... os.environ.pop('CLEVERAGENTS_SERVER_MODE', None) ... os.environ.pop('CLEVERAGENTS_DATABASE_URL', None) ... from cleveragents.config.settings import Settings ... s = Settings() ... print(s.resolve_database_url()) ${result}= Run Process ${PYTHON} -c ${script} ... cwd=${CURDIR}/.. Should Be Equal As Integers ${result.rc} 0 Should Start With ${result.stdout.strip()} sqlite Settings Resolve Database URL Server Mode [Documentation] Verify resolve_database_url returns PostgreSQL when server_mode is on ... and CLEVERAGENTS_DATABASE_URL points to a PostgreSQL instance. ${script}= Catenate SEPARATOR=\n ... import os ... os.environ['CLEVERAGENTS_SERVER_MODE'] = 'true' ... os.environ['CLEVERAGENTS_DATABASE_URL'] = 'postgresql://localhost/cleveragents' ... from cleveragents.config.settings import Settings ... s = Settings() ... print(s.resolve_database_url()) ${result}= Run Process ${PYTHON} -c ${script} ... cwd=${CURDIR}/.. Should Be Equal As Integers ${result.rc} 0 Should Start With ${result.stdout.strip()} postgresql Settings Is PostgreSQL Returns True For PG URL [Documentation] Verify is_postgresql detects PostgreSQL URLs ${script}= Catenate SEPARATOR=\n ... import os ... os.environ['CLEVERAGENTS_SERVER_MODE'] = 'true' ... os.environ['CLEVERAGENTS_DATABASE_URL'] = 'postgresql://localhost/db' ... from cleveragents.config.settings import Settings ... s = Settings() ... print(s.is_postgresql()) ${result}= Run Process ${PYTHON} -c ${script} ... cwd=${CURDIR}/.. Should Be Equal As Integers ${result.rc} 0 Should Be Equal As Strings ${result.stdout.strip()} True Settings Is PostgreSQL Returns False For SQLite URL [Documentation] Verify is_postgresql returns False for SQLite URLs ${script}= Catenate SEPARATOR=\n ... import os ... os.environ.pop('CLEVERAGENTS_SERVER_MODE', None) ... os.environ.pop('CLEVERAGENTS_DATABASE_URL', None) ... from cleveragents.config.settings import Settings ... s = Settings() ... print(s.is_postgresql()) ${result}= Run Process ${PYTHON} -c ${script} ... cwd=${CURDIR}/.. Should Be Equal As Integers ${result.rc} 0 Should Be Equal As Strings ${result.stdout.strip()} False UnitOfWork Creates SQLite Engine With Pool Parameters [Documentation] UnitOfWork stores pool parameters and creates an SQLite engine ${result}= Run Process ${PYTHON} -c ... from cleveragents.infrastructure.database.unit_of_work import UnitOfWork; u \= UnitOfWork('sqlite:///:memory:', prompt_for_migration\=lambda m: True, pool_size\=8, max_overflow\=15, pool_recycle\=900); print(u._pool_size, u._max_overflow, u._pool_recycle, u.engine.dialect.name) ... cwd=${CURDIR}/.. Should Be Equal As Integers ${result.rc} 0 Should Contain ${result.stdout.strip()} 8 15 900 sqlite ORM Models Use Dialect-Agnostic Types [Documentation] All ORM column types are portable across SQLite and PostgreSQL ${script}= Catenate SEPARATOR=\n ... from cleveragents.infrastructure.database.models import Base ... PORTABLE = {'VARCHAR','STRING','TEXT','INTEGER','BIGINTEGER','SMALLINTEGER', ... 'FLOAT','NUMERIC','BOOLEAN','DATE','DATETIME','TIME','TIMESTAMP','JSON', ... 'BLOB','LARGEBINARY','ENUM'} ... for t in Base.metadata.tables.values(): ... ${SPACE}${SPACE}${SPACE}${SPACE}for c in t.columns: ... ${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}tn = type(c.type).__name__.upper() ... ${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}if tn not in PORTABLE and not any(b in tn for b in PORTABLE): ... ${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}raise AssertionError(f'{t.name}.{c.name} has non-portable type {tn}') ... print('ALL_PORTABLE') ${result}= Run Process ${PYTHON} -c ${script} ... cwd=${CURDIR}/.. Should Be Equal As Integers ${result.rc} 0 Should Contain ${result.stdout.strip()} ALL_PORTABLE ORM Models Source Contains No SQLite Specific Code [Documentation] models.py should not import sqlite3 or use PRAGMAs ${script}= Catenate SEPARATOR=\n ... import inspect ... from cleveragents.infrastructure.database import models ... src = inspect.getsource(models) ... for forbidden in ['sqlite3', 'PRAGMA']: ... ${SPACE}${SPACE}${SPACE}${SPACE}assert forbidden not in src, f'Found {forbidden}' ... print('CLEAN') ${result}= Run Process ${PYTHON} -c ${script} ... cwd=${CURDIR}/.. Should Be Equal As Integers ${result.rc} 0 Should Contain ${result.stdout.strip()} CLEAN Table Names Are Valid PostgreSQL Identifiers [Documentation] All table names are lowercase SQL identifiers ${script}= Catenate SEPARATOR=\n ... import re ... from cleveragents.infrastructure.database.models import Base ... pat = re.compile(r'^[a-z_][a-z0-9_]*$') ... for t in Base.metadata.tables.values(): ... ${SPACE}${SPACE}${SPACE}${SPACE}assert pat.match(t.name), f'Invalid table: {t.name}' ... print('VALID') ${result}= Run Process ${PYTHON} -c ${script} ... cwd=${CURDIR}/.. Should Be Equal As Integers ${result.rc} 0 Should Contain ${result.stdout.strip()} VALID Docker Compose File Exists For PostgreSQL Development [Documentation] docker-compose.yml exists at project root File Should Exist ${CURDIR}/../docker-compose.yml Pool Defaults Are Multi-User Appropriate [Documentation] Default pool settings: pool_size=5, max_overflow=10, pool_recycle=1800 ${script}= Catenate SEPARATOR=\n ... import os ... os.environ['CLEVERAGENTS_SERVER_MODE'] = 'true' ... os.environ.pop('CLEVERAGENTS_DATABASE_URL', None) ... from cleveragents.config.settings import Settings ... s = Settings() ... print(s.db_pool_size, s.db_max_overflow, s.db_pool_recycle) ${result}= Run Process ${PYTHON} -c ${script} ... cwd=${CURDIR}/.. Should Be Equal As Integers ${result.rc} 0 Should Be Equal As Strings ${result.stdout.strip()} 5 10 1800 Migration Runner Accepts PostgreSQL URL [Documentation] MigrationRunner can be constructed with a PostgreSQL URL ${result}= Run Process ${PYTHON} -c ... from cleveragents.infrastructure.database.migration_runner import MigrationRunner; r \= MigrationRunner('postgresql://localhost/test'); print(r.database_url) ... cwd=${CURDIR}/.. Should Be Equal As Integers ${result.rc} 0 Should Start With ${result.stdout.strip()} postgresql PostgreSQL Live Connection Test [Documentation] Connect to a real PostgreSQL instance and run migrations. ... Requires a running PostgreSQL server and CLEVERAGENTS_DATABASE_URL ... pointing to it. Excluded from default CI runs. [Tags] requires_postgresql ${pg_url}= Get Environment Variable CLEVERAGENTS_DATABASE_URL default=SKIP Skip If '${pg_url}' == 'SKIP' PostgreSQL not available ${result}= Run Process ${PYTHON} -c ... from cleveragents.infrastructure.database.migration_runner import MigrationRunner; r \= MigrationRunner('${pg_url}'); r.init_or_upgrade(); print('OK') ... cwd=${CURDIR}/.. Should Be Equal As Integers ${result.rc} 0 Should Contain ${result.stdout.strip()} OK