Database Testing (surety-db)¶
The surety-db extension provides database interaction with contract
validation, supporting multiple database engines.
pip install surety-db
Supported Databases¶
Client Class |
Database |
|---|---|
|
PostgreSQL |
|
MySQL / MariaDB |
|
SQLite (file-based) |
|
Apache Cassandra |
Defining Database Models¶
Database models extend Dictionary through DbModel and bind to
SQLAlchemy table models:
from surety.db import DbModel
from surety import String, Int, Bool
from surety.db.types import DbTimestamp, DbUuid
class WarehouseModel(DbModel):
__model__ = WarehouseTable # SQLAlchemy model
WarehouseId = DbUuid(name='id')
Name = String(name='name')
Capacity = Int(name='capacity', min_val=100, max_val=10000)
Active = Bool(name='active')
CreatedAt = DbTimestamp(name='created_at')
Database Types¶
surety-db provides field types tailored for database columns:
DbUuid— UUID field that returns string representationDbTimestamp— current timestamp with format and timezone conversionDbBool— boolean as integer (MySQL)DBJsonDict— JSON dictionary with serialization forto_db()DbJsonArray— JSON array with serialization forto_db()
Setting Up a Client¶
Pass a PostgresqlDBClient (or another supported client) directly to
Database. Connection parameters are typically read from surety-config:
from surety.db import Database, PostgresqlDBClient
from surety.config import Cfg
main_db = Database('main', client=PostgresqlDBClient(
host=Cfg.DB.main.host,
port=Cfg.DB.main.port,
user=Cfg.DB.main.user,
password=Cfg.DB.main.password,
db=Cfg.DB.main.db
))
CRUD Operations¶
# Insert a record
warehouse = WarehouseModel()
db.insert(warehouse)
# Batch insert
warehouses = [WarehouseModel() for _ in range(5)]
db.insert_records(warehouses)
# Query records
records = db.get_records(
WarehouseModel,
active=True
)
# Get a single record
record = db.get_record(
WarehouseModel,
name='Main Depot'
)
# Update records
db.update(
WarehouseModel,
{'capacity': 5000},
WarehouseTable.name == 'Main Depot'
)
# Delete records
db.delete(WarehouseModel, active=False)
# Cascade delete
db.cascade_delete(WarehouseModel)
Verifying Records¶
Compare database records against contract instances:
from surety.db import Database
# Verify a single record
Database.verify_record(
actual=records[0],
expected=warehouse
)
# Verify multiple records
Database.verify_records(
actual=records,
expected=warehouses
)
# Verify no record exists
db.verify_no_record(
WarehouseModel,
warehouse_id='non-existent-id'
)
# Verify no record with wait (for async operations)
db.verify_no_record_with_wait(
WarehouseModel,
warehouse_id='non-existent-id'
)
Verification supports comparison rules:
from surety.diff.rules import has_some_value
Database.verify_record(
actual=records[0],
expected=warehouse,
rules={WarehouseModel.CreatedAt.name: has_some_value}
)
No-DB Mode¶
For unit tests that don’t require a real database connection:
db.enable_no_db_mode()
# Database operations are skipped silently
db.insert(warehouse)
db.reset_mode() # Re-enable actual database calls
SQLite¶
SQLite client supports context manager usage:
from surety.db import SqliteDBClient
with SqliteDBClient('/tmp/test.db') as client:
client.write_data('INSERT INTO items (name) VALUES (?)', 'Widget')
rows = client.read_data('SELECT * FROM items')
Executing Raw SQL¶
PostgreSQL and MySQL clients support raw SQL execution:
db_client.execute('SELECT COUNT(*) FROM warehouses WHERE active = %s', True)