"""Postgres 集成测试:真实服务器上的端到端路径。 全部测试在无真实 Postgres 时 skip,绝不 fail。判定条件与 test_postgres_backend.py 的 _real_pg_params() 一致:须显式 设置 CLOUD_TEST_PG_DSN(postgres://user:pass@host:port/dbname) 且主机端口 2 秒内可达,且装有 psycopg 驱动。DSN 必须指向 一次性测试库;本文件只写业务表与测试行,跑完删掉测试行, 不删库、不动其它库。DSN 只从环境变量读,测试不打印它。 """ import os import sys import tempfile import unittest from pathlib import Path try: from cloud.tests.test_postgres_backend import ( _has_real_driver, _real_pg_params, ) from cloud.db import PostgresDatabase, init_postgres_schema from cloud.migrations import migrate_sqlite_to_postgres from cloud.server import init_db except ImportError: # discover -s cloud/tests 时把项目根目录补进 sys.path sys.path.insert(0, str(Path(__file__).resolve().parents[2])) from cloud.tests.test_postgres_backend import ( _has_real_driver, _real_pg_params, ) from cloud.db import PostgresDatabase, init_postgres_schema from cloud.migrations import migrate_sqlite_to_postgres from cloud.server import init_db def _pg_db_or_skip(testcase): params = _real_pg_params() if params is None: testcase.skipTest("未设置可达的 CLOUD_TEST_PG_DSN,跳过真实 PG 测试") if not _has_real_driver(): testcase.skipTest("未装 psycopg 驱动,跳过真实 PG 测试") host, port, dbname, user, password = params return PostgresDatabase(host=host, port=port, dbname=dbname, user=user, password=password) class PostgresIntegrationTestCase(unittest.TestCase): def test_server_init_db_and_adapter_crud(self): db = _pg_db_or_skip(self) conn = db.connect() try: init_postgres_schema(conn) # 适配层 CRUD:projects 插、读、改、删均走 ? 占位符。 with db.transaction(conn): conn.execute( "INSERT INTO projects(id, name, owner_id, created_at)" " VALUES(?, ?, ?, ?)", ("pg-int-proj", "pg-int", "pg-int-user", 0.0)) rows = db.read_all( conn, "SELECT * FROM projects WHERE id = ?", ("pg-int-proj",)) self.assertEqual(len(rows), 1) self.assertEqual(rows[0]["name"], "pg-int") with db.transaction(conn): conn.execute( "UPDATE projects SET name = ? WHERE id = ?", ("pg-int-2", "pg-int-proj")) rows = db.read_all( conn, "SELECT name FROM projects WHERE id = ?", ("pg-int-proj",)) self.assertEqual(rows[0]["name"], "pg-int-2") finally: try: with db.transaction(conn): conn.execute("DELETE FROM projects WHERE id = ?", ("pg-int-proj",)) except Exception: pass conn.close() def test_sqlite_to_postgres_migration_is_idempotent(self): import sqlite3 db = _pg_db_or_skip(self) # 先建一个最小 SQLite 库:含 projects/tasks 两行。 with tempfile.TemporaryDirectory() as tmp: src = os.path.join(tmp, "src.db") sconn = sqlite3.connect(src) sconn.execute( "CREATE TABLE projects(id TEXT PRIMARY KEY, name TEXT," " owner_id TEXT, created_at REAL)") sconn.execute( "CREATE TABLE tasks(id TEXT PRIMARY KEY, project_id TEXT," " prompt TEXT, status TEXT)") sconn.execute( "INSERT INTO projects VALUES(?, ?, ?, ?)", ("mig-proj", "mig", "mig-user", 1.5)) sconn.execute( "INSERT INTO tasks VALUES(?, ?, ?, ?)", ("mig-task", "mig-proj", "hello", "queued")) sconn.commit() sconn.close() conn = db.connect() try: first = migrate_sqlite_to_postgres(src, pg_conn=conn) self.assertGreaterEqual(first.get("projects", 0), 1) self.assertGreaterEqual(first.get("tasks", 0), 1) # 第二次跑:ON CONFLICT DO NOTHING,不重复插。 second = migrate_sqlite_to_postgres(src, pg_conn=conn) self.assertEqual(second.get("projects", -1), 0) self.assertEqual(second.get("tasks", -1), 0) rows = db.read_all( conn, "SELECT * FROM projects WHERE id = ?", ("mig-proj",)) self.assertEqual(len(rows), 1) self.assertEqual(rows[0]["name"], "mig") finally: try: with db.transaction(conn): conn.execute("DELETE FROM tasks WHERE id = ?", ("mig-task",)) conn.execute("DELETE FROM projects WHERE id = ?", ("mig-proj",)) except Exception: pass conn.close() def test_server_init_db_on_postgres(self): # 真走 server.init_db 的 PG 分支:环境变量切到 postgres # 后,init_db 不应建任何 SQLite 文件,而应在 PG 里建表 # (含 tasks 的 6 个结果列);本测试不启动 HTTP server。 import os from unittest import mock params = _real_pg_params() if params is None: self.skipTest("未设置可达的 CLOUD_TEST_PG_DSN,跳过真实 PG 测试") if not _has_real_driver(): self.skipTest("未装 psycopg 驱动,跳过真实 PG 测试") host, port, dbname, user, password = params env = { "CLOUD_DB_BACKEND": "postgres", "CLOUD_DB_HOST": host, "CLOUD_DB_PORT": str(port), "CLOUD_DB_NAME": dbname, "CLOUD_DB_USER": user or "", "CLOUD_DB_PASSWORD": password or "", } with mock.patch.dict(os.environ, env): init_db("ignored-in-postgres-mode.db") db = PostgresDatabase(host=host, port=port, dbname=dbname, user=user, password=password) conn = db.connect() try: rows = db.read_all( conn, "SELECT table_name FROM information_schema.tables" " WHERE table_schema = 'public'", ()) names = {r["table_name"] for r in rows} self.assertIn("projects", names) self.assertIn("tasks", names) self.assertIn("audit_log", names) cols = db.read_all( conn, "SELECT column_name FROM information_schema.columns" " WHERE table_name = 'tasks'", ()) colnames = {r["column_name"] for r in cols} for col in ("result_text", "error_text", "actual_model", "started_at", "finished_at", "child_pid"): self.assertIn(col, colnames) finally: conn.close()