Skip to content

Conversation

@fatih-acar
Copy link
Contributor

@fatih-acar fatih-acar commented Dec 30, 2025

Since the neo4j driver upgrade, we must ensure the neo4j driver is properly closed and that a closed driver is not re-used. We disabled singletons during DI to get that behavior.

A side effect is that during some integration tests that leverage local workers, some tasks/flows depend on the injected database instance, which would trigger new driver instances with new db connections. Those instances would not get cleaned since it's not using a singleton nor used within a context manager that would close the underlying db driver.

Fix this by introducing a new class level fixture for the db instance and use it within the injected dependency.

Summary by CodeRabbit

  • Tests
    • Optimized test infrastructure with shared database fixtures to improve test execution efficiency and resource usage.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 30, 2025

Walkthrough

This change introduces a new class-scoped fixture class_db in test files that creates a shared InfrahubDatabase instance via build_database(singleton=False). The test_client fixture is updated to depend on class_db and uses it during test client initialization. In backend/tests/integration/git/test_git_repository.py, the test_client fixture signature is also updated to return AsyncGenerator[InfrahubTestClient, None]. Additionally, the test client setup is refactored to use dependency_provider.scope and a lifespan context for managing the app lifecycle during test client creation.

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main problem: reducing database connections in integration tests by introducing a shared class-level database fixture instead of creating new instances.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added the group/backend Issue related to the backend (API Server, Git Agent) label Dec 30, 2025
@fatih-acar fatih-acar force-pushed the fac-fix-db-fixture-duplicate branch from 0206d1d to 2c002de Compare December 30, 2025 14:28
Since the neo4j driver upgrade, we must ensure the neo4j driver is
properly closed and that a closed driver is not re-used.
We disabled singletons during DI to get that behavior.

A side effect is that during some integration tests that leverage local
workers, some tasks/flows depend on the injected database instance,
which would trigger new driver instances with new db connections.
Those instances would not get cleaned since it's not using a singleton
nor used within a context manager that would close the underlying db
driver.

Fix this by introducing a new class level fixture for the db instance
and use it within the injected dependency.

Signed-off-by: Fatih Acar <fatih@opsmill.com>
@fatih-acar fatih-acar force-pushed the fac-fix-db-fixture-duplicate branch from 2c002de to 15f4890 Compare December 30, 2025 14:44
@codspeed-hq
Copy link

codspeed-hq bot commented Dec 30, 2025

CodSpeed Performance Report

Merging #7983 will not alter performance

Comparing fac-fix-db-fixture-duplicate (15f4890) with release-1.7 (0ed21ad)

Summary

✅ 12 untouched

@fatih-acar fatih-acar marked this pull request as ready for review December 30, 2025 15:15
@fatih-acar fatih-acar requested a review from a team as a code owner December 30, 2025 15:15
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
backend/tests/helpers/test_app.py (1)

142-143: Consider adding a clarifying comment.

The _db function accepts a singleton parameter to match the build_database signature but intentionally ignores it to always return the shared class_db instance. A brief comment would help future maintainers understand this design choice.

🔎 Suggested comment
 async def _db(singleton: bool = True) -> InfrahubDatabase:
+    # Always return class_db to prevent duplicate connections during tests
     return class_db
backend/tests/integration/git/test_git_repository.py (1)

77-78: Consider adding a clarifying comment.

Same as in backend/tests/helpers/test_app.py, the _db function ignores the singleton parameter. A comment would clarify this intentional design choice.

🔎 Suggested comment
 async def _db(singleton: bool = True) -> InfrahubDatabase:
+    # Always return class_db to prevent duplicate connections during tests
     return class_db
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ed21ad and 15f4890.

📒 Files selected for processing (2)
  • backend/tests/helpers/test_app.py
  • backend/tests/integration/git/test_git_repository.py
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (.github/instructions/python-docstring.instructions.md)

**/*.py: Always use triple quotes (""") for Python docstrings
Follow Google-style docstring format for Python docstrings
Include brief one-line description in Python docstrings when applicable
Include detailed description in Python docstrings when applicable
Include Args/Parameters section without typing in Python docstrings when applicable
Include Returns section in Python docstrings when applicable
Include Raises section in Python docstrings when applicable
Include Examples section in Python docstrings when applicable

**/*.py: Use type hints for all function parameters and return values in Python
Use Async whenever possible in Python
Use async def for asynchronous functions in Python
Use await for asynchronous calls in Python
Use Pydantic models for dataclasses in Python
Use ruff and mypy for type checking and code validation in Python

Use ruff and mypy to validate and lint Python files

Files:

  • backend/tests/integration/git/test_git_repository.py
  • backend/tests/helpers/test_app.py
backend/tests/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Run backend tests with pytest or via invoke tasks

Test files should mirror source structure: infrahub/core/node.py should have corresponding test at tests/unit/core/test_node.py

Files:

  • backend/tests/integration/git/test_git_repository.py
  • backend/tests/helpers/test_app.py
backend/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

backend/**/*.py: Follow Python coding standards defined in dev/guidelines/backend/python.md
Use type hints for Python in backend code

backend/**/*.py: Use async/await for all I/O operations in Python code
Type hint all function parameters and return values in Python code
Use Pydantic models for data structures in Python code

Files:

  • backend/tests/integration/git/test_git_repository.py
  • backend/tests/helpers/test_app.py
🧬 Code graph analysis (1)
backend/tests/integration/git/test_git_repository.py (3)
backend/tests/conftest.py (2)
  • dependency_provider (121-122)
  • _db (137-138)
backend/infrahub/server.py (1)
  • lifespan (108-111)
backend/infrahub/workers/dependencies.py (1)
  • build_database (63-72)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (1)
backend/tests/integration/git/test_git_repository.py (1)

70-82: Good use of AsyncGenerator type annotation.

The return type AsyncGenerator[InfrahubTestClient, None] correctly reflects the fixture's behavior with the yield statement. The pattern of using dependency_provider.scope and lifespan context manager is consistent with the changes in backend/tests/helpers/test_app.py.

Comment on lines +123 to +125
@pytest.fixture(scope="class")
async def class_db(self) -> InfrahubDatabase:
return await build_database(singleton=False)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's check the actual file and see the current state
head -n 130 backend/tests/helpers/test_app.py | tail -n 20

Repository: opsmill/infrahub

Length of output: 793


🏁 Script executed:

# Find the InfrahubDatabase class definition
find backend -name "*.py" -type f | xargs grep -l "class InfrahubDatabase" | head -5

Repository: opsmill/infrahub

Length of output: 196


🏁 Script executed:

# Search for InfrahubDatabase class and its methods
rg -nP --type=py "class InfrahubDatabase" -A 10 backend/

Repository: opsmill/infrahub

Length of output: 4003


🏁 Script executed:

# Let's look at the full InfrahubDatabase class definition to find cleanup methods
rg -nP --type=py "class InfrahubDatabase" -A 150 backend/infrahub/database/__init__.py | head -200

Repository: opsmill/infrahub

Length of output: 8777


🏁 Script executed:

# Also search for close, cleanup, or async context manager methods
rg -nP --type=py "(def close|def cleanup|async def __aenter__|async def __aexit__|def __del__)" backend/infrahub/database/__init__.py

Repository: opsmill/infrahub

Length of output: 171


🏁 Script executed:

# Get the details of the async context manager and close method
sed -n '269,330p' backend/infrahub/database/__init__.py

Repository: opsmill/infrahub

Length of output: 2204


🏁 Script executed:

# Also check how build_database is implemented
rg -nP --type=py "def build_database" -A 20 backend/

Repository: opsmill/infrahub

Length of output: 1649


🏁 Script executed:

# Check how other fixtures handle database cleanup in test files
rg -nP --type=py "async def.*db.*\)" backend/tests/ -A 3 | head -50

Repository: opsmill/infrahub

Length of output: 3165


🏁 Script executed:

# Look for fixture patterns with yield in the test files
rg -nP --type=py "@pytest.fixture.*\nyield" backend/tests/ -A 2 | head -40

Repository: opsmill/infrahub

Length of output: 42


🏁 Script executed:

# Look for fixtures with yield patterns more broadly
rg -nP --type=py "yield" backend/tests/helpers/test_app.py -B 5 -A 2

Repository: opsmill/infrahub

Length of output: 2158


🏁 Script executed:

# Check the conftest and other test fixtures to see if they use yield for cleanup
rg -nP --type=py "@pytest.fixture" backend/tests/conftest.py -A 5 | head -80

Repository: opsmill/infrahub

Length of output: 2901


🏁 Script executed:

# Look specifically at other class-scoped fixtures to see cleanup patterns
rg -nP --type=py '@pytest.fixture\(scope="class"\)' backend/tests/ -A 5 | head -100

Repository: opsmill/infrahub

Length of output: 8272


🏁 Script executed:

# Look at the full module-level db fixture in conftest.py to see cleanup pattern
sed -n '125,148p' backend/tests/conftest.py

Repository: opsmill/infrahub

Length of output: 856


🏁 Script executed:

# Check if class_db fixture is used elsewhere and how
rg -nP --type=py "class_db" backend/tests/helpers/test_app.py -B 2 -A 5

Repository: opsmill/infrahub

Length of output: 1133


🏁 Script executed:

# Check if pytest fixtures use AsyncGenerator type annotation in the codebase
rg -nP --type=py "AsyncGenerator\[" backend/tests/ | head -20

Repository: opsmill/infrahub

Length of output: 1529


🏁 Script executed:

# Let's look at more examples of async fixtures with yield to confirm the pattern
rg -nP --type=py "@pytest.fixture.*\nasync def.*yield" backend/tests/helpers/test_app.py -B 1 -A 10 | head -60

Repository: opsmill/infrahub

Length of output: 42


🏁 Script executed:

# Verify that the return type annotation is indeed present in the actual code
sed -n '123,125p' backend/tests/helpers/test_app.py

Repository: opsmill/infrahub

Length of output: 197


Implement yield-based cleanup for the database fixture.

The class_db fixture should use yield with cleanup logic to properly close the database connection when the test class completes, matching the pattern in the module-level db fixture. Update the return type to AsyncGenerator[InfrahubDatabase, None] and add cleanup:

 @pytest.fixture(scope="class")
-async def class_db(self) -> InfrahubDatabase:
-    return await build_database(singleton=False)
+async def class_db(self) -> AsyncGenerator[InfrahubDatabase, None]:
+    db = await build_database(singleton=False)
+    yield db
+    await db.close()
🤖 Prompt for AI Agents
In backend/tests/helpers/test_app.py around lines 123 to 125, the class_db
fixture currently returns the database directly and must be converted to a
yield-based async fixture that yields an InfrahubDatabase and then performs
cleanup; change the type hint to AsyncGenerator[InfrahubDatabase, None], create
the db with await build_database(singleton=False), yield it to tests, and after
the yield call the appropriate close/teardown method (await db.close() or
equivalent) to ensure the connection is closed when the test class completes.

async def test_client(
self,
dependency_provider: Provider,
db: InfrahubDatabase,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the test_app.py file around line 131
head -n 150 backend/tests/helpers/test_app.py | tail -n 30

Repository: opsmill/infrahub

Length of output: 1398


🏁 Script executed:

# Find the test_client fixture definition
rg -nP --type=py -B5 -A20 'def test_client\(' backend/tests/helpers/test_app.py

Repository: opsmill/infrahub

Length of output: 1415


🏁 Script executed:

# Let's also check the overall structure of the fixture
cat -n backend/tests/helpers/test_app.py

Repository: opsmill/infrahub

Length of output: 14353


🏁 Script executed:

# Check if db parameter is used anywhere in test_client fixture body
sed -n '128,147p' backend/tests/helpers/test_app.py | grep -c '\bdb\b' | head -1

Repository: opsmill/infrahub

Length of output: 61


🏁 Script executed:

# Count occurrences of 'db' in the fixture - should be only in parameter list
sed -n '128,147p' backend/tests/helpers/test_app.py | grep 'db'

Repository: opsmill/infrahub

Length of output: 281


🏁 Script executed:

# Verify what initialize_registry depends on to understand dependency chain
rg -nP --type=py -A15 'async def initialize_registry' backend/tests/helpers/test_app.py

Repository: opsmill/infrahub

Length of output: 737


Remove the unused db parameter from the test_client fixture.

The db parameter is never referenced in the fixture implementation—it only uses class_db. Since initialize_registry (a dependency of test_client) already ensures db is initialized, the explicit db parameter is redundant and should be removed.

🤖 Prompt for AI Agents
In backend/tests/helpers/test_app.py around line 131, the test_client fixture
declares an unused parameter `db` (it only uses `class_db` and
initialize_registry already ensures the DB is initialized); remove `db` from the
fixture signature (and from any local parameter list) so the fixture only takes
the dependencies it actually uses, and run tests to confirm no other references
to `db` remain in that fixture.

Comment on lines +66 to +68
@pytest.fixture(scope="class")
async def class_db(self) -> InfrahubDatabase:
return await build_database(singleton=False)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check the fixture in question
head -n 80 backend/tests/integration/git/test_git_repository.py | tail -n 20

Repository: opsmill/infrahub

Length of output: 176


🏁 Script executed:

# Check the referenced fixture in test_app.py at lines 123-125
sed -n '120,130p' backend/tests/helpers/test_app.py

Repository: opsmill/infrahub

Length of output: 384


🏁 Script executed:

# Search for InfrahubDatabase class and check for close() method
rg "class InfrahubDatabase" -A 30

Repository: opsmill/infrahub

Length of output: 10998


🏁 Script executed:

# Search for how db.close() is used elsewhere
rg "await db\.close\(\)" --type py

Repository: opsmill/infrahub

Length of output: 110


🏁 Script executed:

# Get the referenced fixture from test_app.py
sed -n '123,130p' backend/tests/helpers/test_app.py

Repository: opsmill/infrahub

Length of output: 313


🏁 Script executed:

# Search for AsyncGenerator usage in test fixtures
rg "AsyncGenerator" backend/tests --type py -B 2 -A 4

Repository: opsmill/infrahub

Length of output: 12219


🏁 Script executed:

# Search for pytest fixtures that use yield for cleanup
rg "@pytest.fixture" backend/tests --type py -A 5 | grep -A 5 "yield"

Repository: opsmill/infrahub

Length of output: 9310


Add proper cleanup to the database fixture with correct return type.

This fixture creates an InfrahubDatabase instance without closing it, causing connection leaks. The PR aims to fix these leaks after the neo4j driver upgrade. Change the return type to AsyncGenerator[InfrahubDatabase, None] and implement cleanup using yield and await db.close():

 @pytest.fixture(scope="class")
-async def class_db(self) -> InfrahubDatabase:
-    return await build_database(singleton=False)
+async def class_db(self) -> AsyncGenerator[InfrahubDatabase, None]:
+    db = await build_database(singleton=False)
+    yield db
+    await db.close()

Note: The same issue exists in backend/tests/helpers/test_app.py lines 123-125.

🤖 Prompt for AI Agents
In backend/tests/integration/git/test_git_repository.py around lines 66 to 68
(and similarly in backend/tests/helpers/test_app.py around lines 123 to 125),
the pytest fixture returns an InfrahubDatabase without closing it, leaking
connections; change the fixture signature to return an
AsyncGenerator[InfrahubDatabase, None], allocate the database into a local
variable, use yield to return it, and after the yield call await db.close() to
ensure proper async cleanup (also update imports to include
typing.AsyncGenerator if not present).

await default_branch.save(db=db)
return schema_branch

@pytest.fixture(scope="class")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could this fixture be defined next to the db fixture in tests/conftest.py
and maybe it should be called db_class or db_class_scope
I think we might want to have scope="class" versions of some other fixtures and they would be easier to read if they don't all start with class_

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

group/backend Issue related to the backend (API Server, Git Agent)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants