mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
169 lines
5.6 KiB
Python
169 lines
5.6 KiB
Python
"""Migration 028 tests — seed_self_hosted_provider.
|
|||
|
|
|
||
|
|
Verifies the post-upgrade state and exercises the downgrade SQL ordering
|
||
|
|
to prove the FK-safe delete sequence works.
|
||
|
|
|
||
|
|
NOT a real alembic round-trip — the suite builds the test DB via
|
||
|
|
Base.metadata.create_all (see conftest). Migration 028's upgrade()/downgrade()
|
||
|
|
bodies are reviewed here; the tests guard the resulting DB-level contract.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from typing import TYPE_CHECKING
|
||
|
|
from uuid import uuid4
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from roboco.db.tables import ModelAssignmentTable, ProviderConfigTable
|
||
|
|
from roboco.models.base import AssignmentScope, ModelProvider
|
||
|
|
from sqlalchemy import text
|
||
|
|
|
||
|
|
if TYPE_CHECKING:
|
||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_migration_028_upgrade_insert_contract(
|
||
|
|
db_session: AsyncSession,
|
||
|
|
) -> None:
|
||
|
|
"""The upgrade INSERT SQL seeds the correct LOCAL provider row and is idempotent.
|
||
|
|
|
||
|
|
Executes the INSERT ... ON CONFLICT DO NOTHING SQL from migration 028's
|
||
|
|
upgrade() directly in the test session, verifying:
|
||
|
|
- name='Self-Hosted (Ollama)', type='local', enabled=False on the row.
|
||
|
|
- Running the same INSERT a second time leaves exactly one row (idempotency).
|
||
|
|
"""
|
||
|
|
_insert_sql = text(
|
||
|
|
"""
|
||
|
|
INSERT INTO provider_configs
|
||
|
|
(id, name, type, base_url, auth_token_encrypted, enabled, created_at)
|
||
|
|
VALUES
|
||
|
|
(
|
||
|
|
gen_random_uuid(),
|
||
|
|
'Self-Hosted (Ollama)',
|
||
|
|
'local',
|
||
|
|
NULL,
|
||
|
|
NULL,
|
||
|
|
false,
|
||
|
|
now()
|
||
|
|
)
|
||
|
|
ON CONFLICT (name) DO NOTHING
|
||
|
|
"""
|
||
|
|
)
|
||
|
|
|
||
|
|
# --- First run: the row should be inserted.
|
||
|
|
await db_session.execute(_insert_sql)
|
||
|
|
await db_session.flush()
|
||
|
|
|
||
|
|
# Verify the field contract on the newly-inserted row.
|
||
|
|
result = await db_session.execute(
|
||
|
|
text(
|
||
|
|
"SELECT name, type, enabled "
|
||
|
|
"FROM provider_configs "
|
||
|
|
"WHERE name = 'Self-Hosted (Ollama)'"
|
||
|
|
)
|
||
|
|
)
|
||
|
|
rows = list(result)
|
||
|
|
assert len(rows) == 1
|
||
|
|
name, ptype, enabled = rows[0]
|
||
|
|
assert name == "Self-Hosted (Ollama)"
|
||
|
|
assert ptype == "local"
|
||
|
|
assert enabled is False # starts disabled; user configures via PUT /self-hosted
|
||
|
|
|
||
|
|
# --- Second run: ON CONFLICT DO NOTHING must not create a duplicate.
|
||
|
|
await db_session.execute(_insert_sql)
|
||
|
|
await db_session.flush()
|
||
|
|
|
||
|
|
result = await db_session.execute(
|
||
|
|
text("SELECT id FROM provider_configs WHERE name = 'Self-Hosted (Ollama)'")
|
||
|
|
)
|
||
|
|
assert len(list(result)) == 1, (
|
||
|
|
"Expected exactly one 'Self-Hosted (Ollama)' row after two INSERT "
|
||
|
|
"executions; ON CONFLICT DO NOTHING must prevent duplicates."
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_migration_028_downgrade_deletes_assignments_before_config(
|
||
|
|
db_session: AsyncSession,
|
||
|
|
) -> None:
|
||
|
|
"""Downgrade SQL deletes model_assignments before provider_configs.
|
||
|
|
|
||
|
|
Simulates the downgrade() logic from migration 028:
|
||
|
|
1. DELETE FROM model_assignments WHERE provider_config_id IN (SELECT id ...)
|
||
|
|
2. DELETE FROM provider_configs WHERE name = 'Self-Hosted (Ollama)'
|
||
|
|
|
||
|
|
A FK RESTRICT constraint on model_assignments.provider_config_id means that
|
||
|
|
executing step 2 before step 1 would raise an IntegrityError. This test
|
||
|
|
proves that doing them in the correct order succeeds without violation.
|
||
|
|
"""
|
||
|
|
# --- Arrange: insert a fresh LOCAL provider row and a referencing assignment.
|
||
|
|
suffix = uuid4().hex[:8]
|
||
|
|
local = ProviderConfigTable(
|
||
|
|
name=f"Self-Hosted (Ollama)-test-{suffix}",
|
||
|
|
type=ModelProvider.LOCAL,
|
||
|
|
enabled=False,
|
||
|
|
)
|
||
|
|
db_session.add(local)
|
||
|
|
await db_session.flush()
|
||
|
|
|
||
|
|
assignment = ModelAssignmentTable(
|
||
|
|
scope=AssignmentScope.AGENT_SLUG,
|
||
|
|
scope_value=f"test-agent-{suffix}",
|
||
|
|
provider_config_id=local.id,
|
||
|
|
model_name="llama3.1:8b",
|
||
|
|
)
|
||
|
|
db_session.add(assignment)
|
||
|
|
await db_session.flush()
|
||
|
|
|
||
|
|
# Verify both rows exist before we run the downgrade SQL.
|
||
|
|
result = await db_session.execute(
|
||
|
|
text("SELECT id FROM provider_configs WHERE name = :name").bindparams(
|
||
|
|
name=local.name
|
||
|
|
)
|
||
|
|
)
|
||
|
|
assert result.scalar_one_or_none() is not None
|
||
|
|
|
||
|
|
result = await db_session.execute(
|
||
|
|
text("SELECT id FROM model_assignments WHERE scope_value = :sv").bindparams(
|
||
|
|
sv=assignment.scope_value
|
||
|
|
)
|
||
|
|
)
|
||
|
|
assert result.scalar_one_or_none() is not None
|
||
|
|
|
||
|
|
# --- Act: execute downgrade SQL in the correct FK-safe order.
|
||
|
|
# Step 1: delete referencing model_assignments first.
|
||
|
|
await db_session.execute(
|
||
|
|
text(
|
||
|
|
"DELETE FROM model_assignments "
|
||
|
|
"WHERE provider_config_id IN ("
|
||
|
|
" SELECT id FROM provider_configs WHERE name = :name"
|
||
|
|
")"
|
||
|
|
).bindparams(name=local.name)
|
||
|
|
)
|
||
|
|
# Step 2: now safe to delete the provider row.
|
||
|
|
await db_session.execute(
|
||
|
|
text("DELETE FROM provider_configs WHERE name = :name").bindparams(
|
||
|
|
name=local.name
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
# --- Assert: both rows are gone, no IntegrityError was raised.
|
||
|
|
result = await db_session.execute(
|
||
|
|
text("SELECT id FROM provider_configs WHERE name = :name").bindparams(
|
||
|
|
name=local.name
|
||
|
|
)
|
||
|
|
)
|
||
|
|
assert result.scalar_one_or_none() is None, (
|
||
|
|
"provider_configs row should be deleted by downgrade"
|
||
|
|
)
|
||
|
|
|
||
|
|
result = await db_session.execute(
|
||
|
|
text("SELECT id FROM model_assignments WHERE scope_value = :sv").bindparams(
|
||
|
|
sv=assignment.scope_value
|
||
|
|
)
|
||
|
|
)
|
||
|
|
assert result.scalar_one_or_none() is None, (
|
||
|
|
"model_assignments row should be deleted before provider_configs"
|
||
|
|
)
|