Files
DocsGPT/tests/agents/test_postgres_tool.py
T
Alex 574f96341e refactor: rename the application package to docsgpt
The backend import package is now docsgpt, the name it will carry on PyPI;
application was far too generic to install into anyone's site-packages.
git mv plus a mechanical rewrite of every import, dotted string and path
reference: 734 Python files, the compose files, Dockerfile, workflows, docs,
setup scripts, devcontainer, k8s manifests, vscode config, pytest and coverage
config, .gitignore. Behaviour is unchanged.

Kept for one release:
- A top-level application package whose meta-path finder resolves
  application.x.y to the already-imported docsgpt.x.y object, so old imports
  and entry points (celery -A application.app.celery,
  uvicorn application.asgi:asgi_app) keep working with a FutureWarning.
- Celery registers every application.* task name as an alias of its
  docsgpt.* task on start-up, so messages queued by the previous release still
  run. The redbeat key prefix moves to redbeat:docsgpt:v2: so schedule entries
  the previous release wrote are left unread instead of firing twice.

The backend image builds from the repository root (docker build -f
docsgpt/Dockerfile .) so it can ship the alias package; a root .dockerignore
allow-lists docsgpt/ and application/ and keeps caches, local data, .env
files, the sample index files and the Dockerfile out. Compose and the image
workflows point at the new context.
2026-09-07 10:20:43 +01:00

147 lines
5.1 KiB
Python

"""Tests for docsgpt/agents/tools/postgres.py"""
from unittest.mock import MagicMock, patch
import pytest
from docsgpt.agents.tools.postgres import PostgresTool
@pytest.fixture
def tool():
return PostgresTool(config={"token": "postgresql://user:pass@localhost/testdb"})
@pytest.mark.unit
class TestPostgresExecuteAction:
def test_unknown_action_raises(self, tool):
with pytest.raises(ValueError, match="Unknown action"):
tool.execute_action("invalid")
@patch("docsgpt.agents.tools.postgres.psycopg.connect")
def test_select_query(self, mock_connect, tool):
mock_conn = MagicMock()
mock_cur = MagicMock()
mock_cur.description = [("id",), ("name",)]
mock_cur.fetchall.return_value = [(1, "Alice"), (2, "Bob")]
mock_conn.cursor.return_value = mock_cur
mock_connect.return_value = mock_conn
result = tool.execute_action(
"postgres_execute_sql", sql_query="SELECT id, name FROM users"
)
assert result["status_code"] == 200
assert result["response_data"]["column_names"] == ["id", "name"]
assert len(result["response_data"]["data"]) == 2
assert result["response_data"]["data"][0] == {"id": 1, "name": "Alice"}
mock_conn.close.assert_called_once()
@patch("docsgpt.agents.tools.postgres.psycopg.connect")
def test_insert_query(self, mock_connect, tool):
mock_conn = MagicMock()
mock_cur = MagicMock()
mock_cur.rowcount = 1
mock_conn.cursor.return_value = mock_cur
mock_connect.return_value = mock_conn
result = tool.execute_action(
"postgres_execute_sql",
sql_query="INSERT INTO users (name) VALUES ('Alice')",
)
assert result["status_code"] == 200
assert "1 rows affected" in result["response_data"]["message"]
mock_conn.commit.assert_called_once()
mock_conn.close.assert_called_once()
@patch("docsgpt.agents.tools.postgres.psycopg.connect")
def test_db_error(self, mock_connect, tool):
import psycopg
mock_connect.side_effect = psycopg.Error("connection refused")
result = tool.execute_action(
"postgres_execute_sql", sql_query="SELECT 1"
)
assert result["status_code"] == 500
assert "Database error" in result["error"]
@patch("docsgpt.agents.tools.postgres.psycopg.connect")
def test_get_schema(self, mock_connect, tool):
mock_conn = MagicMock()
mock_cur = MagicMock()
mock_cur.fetchall.return_value = [
("users", "id", "integer", "nextval(...)", "NO"),
("users", "name", "varchar", None, "YES"),
("posts", "id", "integer", "nextval(...)", "NO"),
]
mock_conn.cursor.return_value = mock_cur
mock_connect.return_value = mock_conn
result = tool.execute_action("postgres_get_schema", db_name="testdb")
assert result["status_code"] == 200
assert "users" in result["schema"]
assert "posts" in result["schema"]
assert len(result["schema"]["users"]) == 2
assert result["schema"]["users"][0]["column_name"] == "id"
mock_conn.close.assert_called_once()
@patch("docsgpt.agents.tools.postgres.psycopg.connect")
def test_get_schema_db_error(self, mock_connect, tool):
import psycopg
mock_connect.side_effect = psycopg.Error("auth failed")
result = tool.execute_action("postgres_get_schema", db_name="testdb")
assert result["status_code"] == 500
assert "Database error" in result["error"]
@patch("docsgpt.agents.tools.postgres.psycopg.connect")
def test_connection_closed_on_error(self, mock_connect, tool):
import psycopg
mock_conn = MagicMock()
mock_cur = MagicMock()
mock_cur.execute.side_effect = psycopg.Error("syntax error")
mock_conn.cursor.return_value = mock_cur
mock_connect.return_value = mock_conn
tool.execute_action("postgres_execute_sql", sql_query="BAD SQL")
mock_conn.close.assert_called_once()
@patch("docsgpt.agents.tools.postgres.psycopg.connect")
def test_select_with_no_description(self, mock_connect, tool):
mock_conn = MagicMock()
mock_cur = MagicMock()
mock_cur.description = None
mock_cur.fetchall.return_value = []
mock_conn.cursor.return_value = mock_cur
mock_connect.return_value = mock_conn
result = tool.execute_action(
"postgres_execute_sql", sql_query="SELECT 1 WHERE false"
)
assert result["status_code"] == 200
assert result["response_data"]["column_names"] == []
@pytest.mark.unit
class TestPostgresMetadata:
def test_actions_metadata(self, tool):
meta = tool.get_actions_metadata()
assert len(meta) == 2
names = {a["name"] for a in meta}
assert "postgres_execute_sql" in names
assert "postgres_get_schema" in names
def test_config_requirements(self, tool):
reqs = tool.get_config_requirements()
assert "token" in reqs
assert reqs["token"]["secret"] is True