Skip to content

Commit 017bc70

Browse files
committed
Add docker config for integration tests
1 parent b5ea925 commit 017bc70

File tree

8 files changed

+277
-120
lines changed

8 files changed

+277
-120
lines changed

CLAUDE.md

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,31 @@ uv sync --extra dev # Install dependencies including dev tools
3333
uv pip install -e ".[dev]" # Install in editable mode
3434
```
3535

36+
### Docker Setup for Integration Tests
37+
38+
Integration tests require a TypeDB 3.5.5 server. The project includes Docker configuration for automated setup:
39+
40+
**Requirements:**
41+
- Docker and Docker Compose installed
42+
- Ports: 1729 (TypeDB server)
43+
44+
**Docker is managed automatically** by the test fixtures. Simply run:
45+
```bash
46+
./test-integration.sh # Starts Docker, runs tests, stops Docker
47+
```
48+
49+
**Manual Docker control:**
50+
```bash
51+
docker compose up -d # Start TypeDB container
52+
docker compose down # Stop TypeDB container
53+
docker compose logs typedb # View TypeDB logs
54+
```
55+
56+
**Skip Docker (use existing server):**
57+
```bash
58+
USE_DOCKER=false uv run pytest -m integration
59+
```
60+
3661
### Testing
3762
```bash
3863
# Unit tests (fast, no external dependencies)
@@ -41,12 +66,17 @@ uv run pytest -v # Run unit tests with verbose output
4166
uv run pytest -k test_name # Run specific unit test
4267

4368
# Integration tests (require running TypeDB)
69+
# Option 1: Use Docker (recommended - automatic management)
70+
./test-integration.sh # Run integration tests with Docker
71+
./test-integration.sh -v # Run integration tests with Docker (verbose)
72+
73+
# Option 2: Use existing TypeDB server (set USE_DOCKER=false)
4474
# First, start TypeDB 3.x server: typedb server
45-
uv run pytest -m integration # Run integration tests only
46-
uv run pytest -m integration -v # Run integration tests with verbose output
75+
USE_DOCKER=false uv run pytest -m integration # Run integration tests (no Docker)
76+
USE_DOCKER=false uv run pytest -m integration -v # Run integration tests (verbose)
4777

4878
# Run all tests (unit + integration)
49-
uv run pytest -m "" # Run all tests
79+
uv run pytest -m "" # Run all tests (Docker managed automatically)
5080
```
5181

5282
### Linting
@@ -193,11 +223,15 @@ Coverage:
193223

194224
**Setup for integration tests:**
195225
```bash
196-
# 1. Start TypeDB server
226+
# Option 1: Use Docker (recommended - automatic)
227+
./test-integration.sh -v
228+
229+
# Option 2: Use existing TypeDB server
230+
# 1. Start TypeDB 3.x server
197231
typedb server
198232

199-
# 2. Run integration tests
200-
uv run pytest -m integration -v
233+
# 2. Run integration tests (skip Docker)
234+
USE_DOCKER=false uv run pytest -m integration -v
201235
```
202236

203237
**Test execution patterns:**

docker-compose.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
services:
2+
typedb:
3+
image: typedb/typedb:3.5.5
4+
container_name: typedb_test
5+
ports:
6+
- "1729:1729"
7+
healthcheck:
8+
test: ["CMD-SHELL", "timeout 1 bash -c '</dev/tcp/localhost/1729' || exit 1"]
9+
interval: 5s
10+
timeout: 3s
11+
retries: 10
12+
start_period: 10s
13+
networks:
14+
- typedb_network
15+
16+
networks:
17+
typedb_network:
18+
driver: bridge

pyproject.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ classifiers = [
2424
]
2525
dependencies = [
2626
"typedb-driver==3.5.5",
27-
"pydantic>=2.0.0",
27+
"pydantic>=2.12.4",
2828
"isodate==0.7.2",
2929
]
3030

@@ -36,10 +36,11 @@ Issues = "https://github.com/ds1sqe/type_bridge/issues"
3636

3737
[project.optional-dependencies]
3838
dev = [
39-
"pytest>=8.0.0",
40-
"pytest-asyncio>=0.23.0",
39+
"pytest>=9.0.1",
40+
"pytest-asyncio>=1.3.0",
4141
"pytest-order>=1.2.0",
42-
"ruff>=0.6.0",
42+
"ruff>=0.14.5",
43+
"pyright>=1.1.407",
4344
]
4445

4546
[tool.ruff]

test-integration.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/usr/bin/env bash
2+
# Helper script to run integration tests with Docker
3+
4+
set -e
5+
6+
# Colors for output
7+
GREEN='\033[0;32m'
8+
YELLOW='\033[1;33m'
9+
NC='\033[0m' # No Color
10+
11+
echo -e "${GREEN}Running integration tests with Docker...${NC}"
12+
13+
# Run pytest with integration marker
14+
# Docker container management is handled by conftest.py
15+
uv run pytest -m integration "$@"
16+
17+
echo -e "${GREEN}Integration tests completed!${NC}"

tests/integration/conftest.py

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
"""Pytest fixtures for integration tests."""
22

3+
import os
4+
import subprocess
5+
import time
6+
37
import pytest
48
from typedb.driver import DriverOptions
59

@@ -11,9 +15,72 @@
1115

1216

1317
@pytest.fixture(scope="session")
14-
def typedb_driver():
18+
def docker_typedb():
19+
"""Start TypeDB Docker container for the test session.
20+
21+
Yields:
22+
None (container runs in background)
23+
"""
24+
# Check if we should use Docker (default: yes, unless USE_DOCKER=false)
25+
use_docker = os.getenv("USE_DOCKER", "true").lower() != "false"
26+
27+
if not use_docker:
28+
# Skip Docker management - assume TypeDB is already running
29+
yield
30+
return
31+
32+
# Get project root directory
33+
project_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
34+
35+
# Start Docker container
36+
try:
37+
# Stop any existing container
38+
subprocess.run(
39+
["docker", "compose", "down"],
40+
cwd=project_root,
41+
capture_output=True,
42+
)
43+
44+
# Start container
45+
subprocess.run(
46+
["docker", "compose", "up", "-d"],
47+
cwd=project_root,
48+
check=True,
49+
capture_output=True,
50+
)
51+
52+
# Wait for TypeDB to be healthy
53+
max_retries = 30
54+
for i in range(max_retries):
55+
result = subprocess.run(
56+
["docker", "inspect", "--format={{.State.Health.Status}}", "typedb_test"],
57+
capture_output=True,
58+
text=True,
59+
)
60+
if result.stdout.strip() == "healthy":
61+
break
62+
time.sleep(1)
63+
else:
64+
raise RuntimeError("TypeDB container failed to become healthy")
65+
66+
yield
67+
68+
finally:
69+
# Stop Docker container
70+
subprocess.run(
71+
["docker", "compose", "down"],
72+
cwd=project_root,
73+
capture_output=True,
74+
)
75+
76+
77+
@pytest.fixture(scope="session")
78+
def typedb_driver(docker_typedb):
1579
"""Create a TypeDB driver connection for the test session.
1680
81+
Args:
82+
docker_typedb: Fixture that ensures Docker container is running
83+
1784
Yields:
1885
TypeDB driver instance
1986

tests/unit/validation/test_duplicate_attributes.py

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
even when they share the same underlying value type.
55
"""
66

7+
from unittest.mock import Mock
8+
79
import pytest
810

911
from type_bridge import Entity, SchemaManager
1012
from type_bridge.attribute.datetime import DateTime
1113
from type_bridge.attribute.flags import Flag, Key
1214
from type_bridge.attribute.string import String
1315
from type_bridge.schema import SchemaValidationError
16+
from type_bridge.session import Database
1417

1518

1619
class TestDuplicateAttributeValidation:
@@ -30,12 +33,9 @@ class Issue(Entity):
3033
created: TimeStamp
3134
modified: TimeStamp
3235

33-
# Mock database for schema manager
34-
class MockDatabase:
35-
def database_exists(self):
36-
return False
37-
38-
db = MockDatabase()
36+
# Mock database for unit test
37+
db = Mock(spec=Database)
38+
db.database_exists.return_value = False
3939
schema_manager = SchemaManager(db)
4040
schema_manager.register(Issue)
4141

@@ -60,11 +60,9 @@ class Person(Entity):
6060
last_name: Name
6161
middle_name: Name
6262

63-
class MockDatabase:
64-
def database_exists(self):
65-
return False
66-
67-
db = MockDatabase()
63+
# Mock database for unit test
64+
db = Mock(spec=Database)
65+
db.database_exists.return_value = False
6866
schema_manager = SchemaManager(db)
6967
schema_manager.register(Person)
7068

@@ -95,11 +93,9 @@ class Issue(Entity):
9593
created: CreatedStamp
9694
modified: ModifiedStamp
9795

98-
class MockDatabase:
99-
def database_exists(self):
100-
return False
101-
102-
db = MockDatabase()
96+
# Mock database for unit test
97+
db = Mock(spec=Database)
98+
db.database_exists.return_value = False
10399
schema_manager = SchemaManager(db)
104100
schema_manager.register(Issue)
105101

@@ -119,11 +115,9 @@ class Task(Entity):
119115
current_status: Status
120116
previous_status: Status
121117

122-
class MockDatabase:
123-
def database_exists(self):
124-
return False
125-
126-
db = MockDatabase()
118+
# Mock database for unit test
119+
db = Mock(spec=Database)
120+
db.database_exists.return_value = False
127121
schema_manager = SchemaManager(db)
128122
schema_manager.register(Task)
129123

@@ -156,11 +150,9 @@ class Issue(Entity):
156150
class Task(Entity):
157151
name: IssueKey # Using IssueKey is OK here (different entity)
158152

159-
class MockDatabase:
160-
def database_exists(self):
161-
return False
162-
163-
db = MockDatabase()
153+
# Mock database for unit test
154+
db = Mock(spec=Database)
155+
db.database_exists.return_value = False
164156
schema_manager = SchemaManager(db)
165157
schema_manager.register(Issue, Task)
166158

@@ -182,11 +174,9 @@ class Event(Entity):
182174
start: Timestamp
183175
end: Timestamp
184176

185-
class MockDatabase:
186-
def database_exists(self):
187-
return False
188-
189-
db = MockDatabase()
177+
# Mock database for unit test
178+
db = Mock(spec=Database)
179+
db.database_exists.return_value = False
190180
schema_manager = SchemaManager(db)
191181
schema_manager.register(Event)
192182

type_bridge/schema/info.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,7 @@ def validate(self) -> None:
5555
"""
5656
# Validate entities
5757
for entity_model in self.entities:
58-
self._validate_no_duplicate_attribute_types(
59-
entity_model, entity_model.get_type_name()
60-
)
58+
self._validate_no_duplicate_attribute_types(entity_model, entity_model.get_type_name())
6159

6260
# Validate relations
6361
for relation_model in self.relations:
@@ -154,13 +152,17 @@ def _validate_no_duplicate_attribute_types(
154152
lines.append("")
155153
lines.append(" # Use:")
156154
for field in first_fields:
157-
field_class_name = field.capitalize() + "Stamp" if "time" in field.lower() else field.capitalize()
155+
field_class_name = (
156+
field.capitalize() + "Stamp" if "time" in field.lower() else field.capitalize()
157+
)
158158
lines.append(f" class {field_class_name}({value_type}):")
159159
lines.append(" pass")
160160
lines.append("")
161161
lines.append(f" class {type_name}(Entity):")
162162
for field in first_fields:
163-
field_class_name = field.capitalize() + "Stamp" if "time" in field.lower() else field.capitalize()
163+
field_class_name = (
164+
field.capitalize() + "Stamp" if "time" in field.lower() else field.capitalize()
165+
)
164166
lines.append(f" {field}: {field_class_name} # ✓ Distinct types")
165167

166168
raise SchemaValidationError("\n".join(lines))

0 commit comments

Comments
 (0)