Files
bvandeusen 67a1bc740d
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 7s
CI / integration (push) Successful in 2m18s
CI / publish (push) Successful in 1m7s
fix(test): call sync create_app directly in ansible inventory integration test
create_app is synchronous (returns Quart) and runs its own internal
asyncio.run for settings/migrations. _make_app wrapped it in asyncio.run,
which nested event loops and raised "a coroutine is required" — the other
integration tests already call create_app() directly. This test was added
in an unpushed commit, so CI never exercised it until now.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 21:39:41 -04:00

181 lines
6.1 KiB
Python

"""Integration tests: ansible_targets / ansible_groups tables + fetch_scope_targets.
Requires a live Postgres DB (STEWARD_DATABASE_URL).
Run with:
pytest tests/integration/test_ansible_inventory.py -m integration -v
"""
from __future__ import annotations
import os
import uuid
import pytest
from sqlalchemy import select, text
pytestmark = pytest.mark.integration
_NEEDS_DB = pytest.mark.skipif(
not os.environ.get("STEWARD_DATABASE_URL"),
reason="integration test needs a live Postgres (STEWARD_DATABASE_URL)",
)
def _make_app():
# create_app is synchronous and runs its own internal asyncio.run for
# settings/migrations — call it directly (matching the other integration
# tests); wrapping it in asyncio.run nests event loops and fails.
from steward.app import create_app
return create_app(testing=False)
@_NEEDS_DB
def test_ansible_targets_table_exists():
"""Migration 0015 created the ansible_targets table."""
app = _make_app()
async def _check():
async with app.db_sessionmaker() as db:
result = await db.execute(
text(
"SELECT column_name FROM information_schema.columns "
"WHERE table_name = 'ansible_targets'"
)
)
return {row[0] for row in result.fetchall()}
import asyncio
columns = asyncio.run(_check())
assert "id" in columns
assert "name" in columns
assert "address" in columns
assert "ansible_vars" in columns
assert "host_id" in columns
@_NEEDS_DB
def test_ansible_groups_table_exists():
"""Migration 0016 created ansible_groups and ansible_target_groups."""
app = _make_app()
async def _check():
async with app.db_sessionmaker() as db:
groups_result = await db.execute(
text(
"SELECT column_name FROM information_schema.columns "
"WHERE table_name = 'ansible_groups'"
)
)
groups_cols = {row[0] for row in groups_result.fetchall()}
join_result = await db.execute(
text(
"SELECT column_name FROM information_schema.columns "
"WHERE table_name = 'ansible_target_groups'"
)
)
join_cols = {row[0] for row in join_result.fetchall()}
return groups_cols, join_cols
import asyncio
groups_cols, join_cols = asyncio.run(_check())
assert "id" in groups_cols
assert "name" in groups_cols
assert "ansible_vars" in groups_cols
assert "target_id" in join_cols
assert "group_id" in join_cols
@_NEEDS_DB
def test_ansible_run_scope_column_exists():
"""Migration 0017 added inventory_scope to ansible_runs."""
app = _make_app()
async def _check():
async with app.db_sessionmaker() as db:
result = await db.execute(
text(
"SELECT column_name FROM information_schema.columns "
"WHERE table_name = 'ansible_runs'"
)
)
return {row[0] for row in result.fetchall()}
import asyncio
columns = asyncio.run(_check())
assert "inventory_scope" in columns
assert "inventory_path" in columns
@_NEEDS_DB
def test_create_target_and_group_and_fetch():
"""Can create a target+group, assign membership, and fetch via fetch_scope_targets."""
from steward.models.ansible_inventory import AnsibleTarget, AnsibleGroup
from steward.ansible.inventory_gen import fetch_scope_targets, generate_inventory
app = _make_app()
target_id = str(uuid.uuid4())
group_id = str(uuid.uuid4())
target_name = f"test-target-{target_id[:8]}"
group_name = f"test-group-{group_id[:8]}"
async def _run():
# Create
async with app.db_sessionmaker() as db:
async with db.begin():
grp = AnsibleGroup(
id=group_id,
name=group_name,
ansible_vars={"env": "test"},
)
tgt = AnsibleTarget(
id=target_id,
name=target_name,
address="10.0.99.1",
ansible_vars={"ansible_user": "ubuntu"},
)
tgt.groups.append(grp)
db.add(grp)
db.add(tgt)
# Fetch all — target must be in the list
async with app.db_sessionmaker() as db:
all_targets = await fetch_scope_targets(db, "steward:all")
assert any(t.id == target_id for t in all_targets)
# Fetch by group
async with app.db_sessionmaker() as db:
group_targets = await fetch_scope_targets(db, f"steward:group:{group_id}")
assert any(t.id == target_id for t in group_targets)
# Fetch single target
async with app.db_sessionmaker() as db:
single = await fetch_scope_targets(db, f"steward:target:{target_id}")
assert len(single) == 1
assert single[0].id == target_id
# Generate inventory and verify var merging
async with app.db_sessionmaker() as db:
targets = await fetch_scope_targets(db, f"steward:group:{group_id}")
inv = generate_inventory(targets)
assert target_name in inv["all"]["hosts"]
assert target_name in inv[group_name]["hosts"]
assert inv["_meta"]["hostvars"][target_name]["ansible_host"] == "10.0.99.1"
assert inv["_meta"]["hostvars"][target_name]["ansible_user"] == "ubuntu"
assert inv["_meta"]["hostvars"][target_name]["env"] == "test"
# Cleanup
async with app.db_sessionmaker() as db:
async with db.begin():
t = (await db.execute(
select(AnsibleTarget).where(AnsibleTarget.id == target_id)
)).scalar_one_or_none()
if t:
await db.delete(t)
g = (await db.execute(
select(AnsibleGroup).where(AnsibleGroup.id == group_id)
)).scalar_one_or_none()
if g:
await db.delete(g)
import asyncio
asyncio.run(_run())