mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-04 16:13:23 +00:00
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.
145 lines
5.5 KiB
Python
145 lines
5.5 KiB
Python
"""Tests for ``docsgpt.core.db_uri``.
|
|
|
|
DocsGPT has two Postgres connection strings — ``POSTGRES_URI`` (consumed
|
|
by SQLAlchemy) and ``PGVECTOR_CONNECTION_STRING`` (consumed by
|
|
``psycopg.connect()`` directly). They need opposite normalization
|
|
because SQLAlchemy requires a ``postgresql+psycopg://`` dialect prefix
|
|
and libpq rejects it. Each field has its own normalizer so operators
|
|
can write whichever form feels natural and cross-pollination between
|
|
the two fields is forgiven.
|
|
|
|
The normalizers live in ``docsgpt.core.db_uri`` as plain functions
|
|
so these tests can exercise them directly without having to instantiate
|
|
``Settings`` (which would pull in ``.env`` file side effects).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from docsgpt.core.db_uri import (
|
|
normalize_pgvector_connection_string,
|
|
normalize_postgres_uri,
|
|
)
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestNormalizePostgresUri:
|
|
@pytest.mark.parametrize(
|
|
"input_value,expected",
|
|
[
|
|
# User-friendly forms get rewritten to the SQLAlchemy dialect.
|
|
(
|
|
"postgres://u:p@h:5432/d",
|
|
"postgresql+psycopg://u:p@h:5432/d",
|
|
),
|
|
(
|
|
"postgresql://u:p@h:5432/d",
|
|
"postgresql+psycopg://u:p@h:5432/d",
|
|
),
|
|
# Legacy psycopg2 dialect is silently upgraded — psycopg2 is
|
|
# no longer in requirements.txt, so there's no way it can work
|
|
# as-is, and rewriting is friendlier than failing.
|
|
(
|
|
"postgresql+psycopg2://u:p@h:5432/d",
|
|
"postgresql+psycopg://u:p@h:5432/d",
|
|
),
|
|
# Already-correct dialect passes through unchanged.
|
|
(
|
|
"postgresql+psycopg://u:p@h:5432/d",
|
|
"postgresql+psycopg://u:p@h:5432/d",
|
|
),
|
|
# Whitespace is trimmed before rewriting.
|
|
(
|
|
" postgres://u:p@h/d ",
|
|
"postgresql+psycopg://u:p@h/d",
|
|
),
|
|
# Query-string params (sslmode, options) are preserved verbatim.
|
|
(
|
|
"postgresql://u:p@h:5432/d?sslmode=require&application_name=docsgpt",
|
|
"postgresql+psycopg://u:p@h:5432/d?sslmode=require&application_name=docsgpt",
|
|
),
|
|
],
|
|
)
|
|
def test_rewrites_common_forms_to_psycopg_dialect(self, input_value, expected):
|
|
assert normalize_postgres_uri(input_value) == expected
|
|
|
|
@pytest.mark.parametrize(
|
|
"input_value",
|
|
[None, "", " ", "None", "none"],
|
|
)
|
|
def test_empty_or_none_like_returns_none(self, input_value):
|
|
assert normalize_postgres_uri(input_value) is None
|
|
|
|
def test_unknown_scheme_passes_through(self):
|
|
"""A dialect we don't recognise is left alone so SQLAlchemy can
|
|
produce its own error message when the engine tries to connect.
|
|
Better than silently eating the config."""
|
|
weird = "postgresql+asyncpg://u:p@h/d"
|
|
assert normalize_postgres_uri(weird) == weird
|
|
|
|
def test_non_string_input_passes_through(self):
|
|
"""Non-string inputs (e.g. if pydantic ever passes an int) shouldn't
|
|
crash the normalizer — let pydantic's own type validation handle it."""
|
|
assert normalize_postgres_uri(42) == 42 # type: ignore[arg-type]
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestNormalizePgvectorConnectionString:
|
|
"""Symmetric to the POSTGRES_URI normalizer but pulls in the OPPOSITE
|
|
direction: strips the SQLAlchemy dialect prefix so libpq accepts it.
|
|
"""
|
|
|
|
@pytest.mark.parametrize(
|
|
"input_value,expected",
|
|
[
|
|
# User-friendly forms pass through — libpq accepts them natively.
|
|
(
|
|
"postgres://u:p@h:5432/d",
|
|
"postgres://u:p@h:5432/d",
|
|
),
|
|
(
|
|
"postgresql://u:p@h:5432/d",
|
|
"postgresql://u:p@h:5432/d",
|
|
),
|
|
# SQLAlchemy dialect prefixes get stripped so libpq accepts them.
|
|
# Operators hit this when they copy POSTGRES_URI → PGVECTOR_CONNECTION_STRING.
|
|
(
|
|
"postgresql+psycopg://u:p@h:5432/d",
|
|
"postgresql://u:p@h:5432/d",
|
|
),
|
|
(
|
|
"postgresql+psycopg2://u:p@h:5432/d",
|
|
"postgresql://u:p@h:5432/d",
|
|
),
|
|
# Whitespace is trimmed before rewriting.
|
|
(
|
|
" postgresql+psycopg://u:p@h/d ",
|
|
"postgresql://u:p@h/d",
|
|
),
|
|
# Query-string params (sslmode, etc.) are preserved verbatim.
|
|
(
|
|
"postgresql+psycopg://u:p@h:5432/d?sslmode=require",
|
|
"postgresql://u:p@h:5432/d?sslmode=require",
|
|
),
|
|
],
|
|
)
|
|
def test_rewrites_dialect_forms_to_libpq_compatible(self, input_value, expected):
|
|
assert normalize_pgvector_connection_string(input_value) == expected
|
|
|
|
@pytest.mark.parametrize(
|
|
"input_value",
|
|
[None, "", " ", "None", "none"],
|
|
)
|
|
def test_empty_or_none_like_returns_none(self, input_value):
|
|
assert normalize_pgvector_connection_string(input_value) is None
|
|
|
|
def test_unknown_scheme_passes_through(self):
|
|
"""A scheme we don't recognise is left alone so libpq can produce
|
|
its own error message when the connection is attempted."""
|
|
weird = "mysql://u:p@h/d"
|
|
assert normalize_pgvector_connection_string(weird) == weird
|
|
|
|
def test_non_string_input_passes_through(self):
|
|
assert normalize_pgvector_connection_string(42) == 42 # type: ignore[arg-type]
|