Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 8 additions & 47 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@
- Test isolation
"""


import hashlib
import pytest
from pymongo import MongoClient
from framework import fixtures


def pytest_addoption(parser):
Expand Down Expand Up @@ -70,18 +68,7 @@ def engine_client(request):
connection_string = request.config.connection_string
engine_name = request.config.engine_name

client = MongoClient(connection_string)

# Verify connection
try:
client.admin.command("ping")
except Exception as e:
# Close the client before raising
client.close()
# Raise ConnectionError so analyzer categorizes as INFRA_ERROR
raise ConnectionError(
f"Cannot connect to {engine_name} at {connection_string}: {e}"
) from e
client = fixtures.create_engine_client(connection_string, engine_name)

yield client

Expand All @@ -106,29 +93,16 @@ def database_client(engine_client, request, worker_id):
Yields:
Database: MongoDB database object
"""
# Get full test identifier (includes file path and test name)
# Generate unique database name using framework utility
full_test_id = request.node.nodeid

# Create a short hash for uniqueness (first 8 chars of SHA256)
name_hash = hashlib.sha256(full_test_id.encode()).hexdigest()[:8]

# Get abbreviated test name for readability (sanitize and truncate)
test_name = request.node.name.replace("[", "_").replace("]", "_")
abbreviated = test_name[:20]

# Combine: test_{worker}_{hash}_{abbreviated}
# Example: test_gw0_a1b2c3d4_find_all_documents
db_name = f"test_{worker_id}_{name_hash}_{abbreviated}"[:63] # MongoDB limit
db_name = fixtures.generate_database_name(full_test_id, worker_id)

db = engine_client[db_name]

yield db

# Cleanup: drop test database
try:
engine_client.drop_database(db_name)
except Exception:
pass # Best effort cleanup
fixtures.cleanup_database(engine_client, db_name)


@pytest.fixture(scope="function")
Expand All @@ -147,26 +121,13 @@ def collection(database_client, request, worker_id):
Yields:
Collection: Empty MongoDB collection object
"""
# Get full test identifier
# Generate unique collection name using framework utility
full_test_id = request.node.nodeid

# Create a short hash for uniqueness (first 8 chars of SHA256)
name_hash = hashlib.sha256(full_test_id.encode()).hexdigest()[:8]

# Get abbreviated test name for readability (sanitize and truncate)
test_name = request.node.name.replace("[", "_").replace("]", "_")
abbreviated = test_name[:25]

# Combine: coll_{worker}_{hash}_{abbreviated}
# Example: coll_gw0_a1b2c3d4_find_all_documents
collection_name = f"coll_{worker_id}_{name_hash}_{abbreviated}"[:100] # Collection name limit
collection_name = fixtures.generate_collection_name(full_test_id, worker_id)

coll = database_client[collection_name]

yield coll

# Cleanup: drop collection
try:
coll.drop()
except Exception:
pass # Best effort cleanup
fixtures.cleanup_collection(database_client, collection_name)
11 changes: 11 additions & 0 deletions framework/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""
Reusable testing framework for DocumentDB functional tests.

This framework provides:
- Assertion helpers for common test scenarios
- Fixture utilities for test isolation and database management
"""

from . import assertions, fixtures

__all__ = ["assertions", "fixtures"]
2 changes: 1 addition & 1 deletion tests/common/assertions.py → framework/assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,4 @@ def assert_count(collection, filter_query: Dict, expected_count: int):
assert actual_count == expected_count, (
f"Document count mismatch for filter {filter_query}. "
f"Expected {expected_count}, got {actual_count}"
)
)
123 changes: 123 additions & 0 deletions framework/fixtures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""
Fixture utilities for test isolation and database management.

Provides reusable functions for creating database clients, generating unique
names, and managing test isolation.
"""

import hashlib
from pymongo import MongoClient


def create_engine_client(connection_string: str, engine_name: str = "default"):
"""
Create and verify a MongoDB client connection.

Args:
connection_string: MongoDB connection string
engine_name: Optional engine identifier for error messages

Returns:
MongoClient: Connected MongoDB client

Raises:
ConnectionError: If unable to connect to the database
"""
client = MongoClient(connection_string)

# Verify connection
try:
client.admin.command("ping")
except Exception as e:
# Close the client before raising
client.close()
# Raise ConnectionError so analyzer categorizes as INFRA_ERROR
raise ConnectionError(
f"Cannot connect to {engine_name} at {connection_string}: {e}"
) from e

return client


def generate_database_name(test_id: str, worker_id: str = "master") -> str:
"""
Generate a unique database name for test isolation.

Creates a collision-free name for parallel execution that includes
worker ID, hash, and abbreviated test name.

Args:
test_id: Full test identifier (e.g., test file path + test name)
worker_id: Worker ID from pytest-xdist (e.g., 'gw0', 'gw1', or 'master')

Returns:
str: Unique database name (max 63 characters for MongoDB compatibility)
"""
# Create a short hash for uniqueness (first 8 chars of SHA256)
name_hash = hashlib.sha256(test_id.encode()).hexdigest()[:8]

# Get abbreviated test name for readability (sanitize and truncate)
test_name = test_id.split("::")[-1] if "::" in test_id else test_id
abbreviated = test_name.replace("[", "_").replace("]", "_")[:20]

# Combine: test_{worker}_{hash}_{abbreviated}
# Example: test_gw0_a1b2c3d4_find_all_documents
db_name = f"test_{worker_id}_{name_hash}_{abbreviated}"[:63] # MongoDB limit

return db_name


def generate_collection_name(test_id: str, worker_id: str = "master") -> str:
"""
Generate a unique collection name for test isolation.

Creates a collision-free name for parallel execution that includes
worker ID, hash, and abbreviated test name.

Args:
test_id: Full test identifier (e.g., test file path + test name)
worker_id: Worker ID from pytest-xdist (e.g., 'gw0', 'gw1', or 'master')

Returns:
str: Unique collection name (max 100 characters)
"""
# Create a short hash for uniqueness (first 8 chars of SHA256)
name_hash = hashlib.sha256(test_id.encode()).hexdigest()[:8]

# Get abbreviated test name for readability (sanitize and truncate)
test_name = test_id.split("::")[-1] if "::" in test_id else test_id
abbreviated = test_name.replace("[", "_").replace("]", "_")[:25]

# Combine: coll_{worker}_{hash}_{abbreviated}
# Example: coll_gw0_a1b2c3d4_find_all_documents
collection_name = f"coll_{worker_id}_{name_hash}_{abbreviated}"[:100] # Collection name limit

return collection_name


def cleanup_database(client: MongoClient, database_name: str):
"""
Drop a database, best effort.

Args:
client: MongoDB client
database_name: Name of database to drop
"""
try:
client.drop_database(database_name)
except Exception:
pass # Best effort cleanup


def cleanup_collection(database, collection_name: str):
"""
Drop a collection, best effort.

Args:
database: MongoDB database object
collection_name: Name of collection to drop
"""
try:
database[collection_name].drop()
except Exception:
pass # Best effort cleanup
2 changes: 1 addition & 1 deletion tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
DocumentDB functionality.
"""

from tests.common.assertions import (
from framework.assertions import (
assert_count,
assert_document_match,
assert_documents_match,
Expand Down
1 change: 0 additions & 1 deletion tests/common/__init__.py

This file was deleted.

2 changes: 1 addition & 1 deletion tests/find/test_basic_queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import pytest

from tests.common.assertions import assert_document_match
from framework.assertions import assert_document_match


@pytest.mark.find
Expand Down
2 changes: 1 addition & 1 deletion tests/find/test_projections.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import pytest

from tests.common.assertions import assert_field_exists, assert_field_not_exists
from framework.assertions import assert_field_exists, assert_field_not_exists


@pytest.mark.find
Expand Down
2 changes: 1 addition & 1 deletion tests/insert/test_insert_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from bson import ObjectId
from pymongo.errors import DuplicateKeyError

from tests.common.assertions import assert_count
from framework.assertions import assert_count


@pytest.mark.insert
Expand Down