Skip to content

Commit 3c9cb48

Browse files
committed
adding e2e tests
1 parent 944e5ae commit 3c9cb48

File tree

5 files changed

+1164
-0
lines changed

5 files changed

+1164
-0
lines changed

tests/conftest.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
# SPDX-FileCopyrightText: 2025 Sequent Tech Inc <legal@sequentech.io>
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
"""Shared pytest fixtures for end-to-end tests."""
6+
7+
import pytest
8+
import tempfile
9+
import shutil
10+
from pathlib import Path
11+
from unittest.mock import Mock, MagicMock, patch
12+
from git import Repo
13+
14+
from release_tool.db import Database
15+
from release_tool.config import Config
16+
from release_tool.models import PullRequest, Issue, Label, Repository
17+
from helpers.git_helpers import init_git_repo, GitScenario
18+
from helpers.config_helpers import create_test_config, write_config_file
19+
20+
21+
@pytest.fixture
22+
def tmp_git_repo(tmp_path):
23+
"""
24+
Create a temporary git repository.
25+
26+
Returns:
27+
Tuple of (repo_path, Repo object)
28+
"""
29+
repo_path = tmp_path / "test_repo"
30+
repo_path.mkdir()
31+
32+
repo = init_git_repo(repo_path)
33+
34+
yield repo_path, repo
35+
36+
# Cleanup handled by tmp_path
37+
38+
39+
@pytest.fixture
40+
def git_scenario(tmp_git_repo, test_db):
41+
"""
42+
Create a GitScenario helper for building complex git histories.
43+
44+
Returns:
45+
GitScenario instance with database sync
46+
"""
47+
repo_path, repo = tmp_git_repo
48+
db, repo_id = test_db
49+
scenario = GitScenario(repo, db=db, repo_id=repo_id)
50+
51+
return scenario
52+
53+
54+
@pytest.fixture
55+
def test_db(tmp_path):
56+
"""
57+
Create an in-memory test database with test data.
58+
59+
Returns:
60+
Database instance
61+
"""
62+
db_path = tmp_path / "test.db"
63+
db = Database(str(db_path))
64+
db.connect() # Initialize database connection and schema
65+
66+
# Create test repository
67+
test_repo = Repository(
68+
owner="test",
69+
name="repo",
70+
full_name="test/repo",
71+
url="https://github.com/test/repo"
72+
)
73+
repo_id = db.upsert_repository(test_repo)
74+
75+
yield db, repo_id
76+
77+
# Cleanup
78+
db.close()
79+
80+
81+
@pytest.fixture
82+
def mock_github_client():
83+
"""
84+
Create a mock GitHub client that doesn't make external calls.
85+
86+
Returns:
87+
Mock GitHub client
88+
"""
89+
client = Mock()
90+
91+
# Mock common methods
92+
client.get_pull_request = Mock(return_value=None)
93+
client.get_issue = Mock(return_value=None)
94+
client.create_release = Mock(return_value={'html_url': 'https://github.com/test/repo/releases/tag/v1.0.0'})
95+
client.create_pull_request = Mock(return_value={'html_url': 'https://github.com/test/repo/pull/1'})
96+
97+
return client
98+
99+
100+
@pytest.fixture
101+
def test_config_dict():
102+
"""
103+
Create a basic test configuration dictionary.
104+
105+
Returns:
106+
Configuration dictionary
107+
"""
108+
return create_test_config(code_repo="test/repo")
109+
110+
111+
@pytest.fixture
112+
def test_config(test_config_dict):
113+
"""
114+
Create a Config object from test configuration.
115+
116+
Returns:
117+
Config instance
118+
"""
119+
return Config.from_dict(test_config_dict)
120+
121+
122+
@pytest.fixture
123+
def test_config_file(tmp_path, test_config_dict):
124+
"""
125+
Create a temporary config file.
126+
127+
Returns:
128+
Path to config file
129+
"""
130+
config_path = tmp_path / "config.yaml"
131+
write_config_file(config_path, test_config_dict)
132+
133+
return config_path
134+
135+
136+
@pytest.fixture
137+
def populated_db(test_db):
138+
"""
139+
Create a database populated with test PRs and issues.
140+
141+
Returns:
142+
Tuple of (Database, repo_id, test_data)
143+
"""
144+
db, repo_id = test_db
145+
146+
test_data = {
147+
'prs': {},
148+
'issues': {}
149+
}
150+
151+
# Create test PRs (101-110)
152+
for pr_num in range(101, 111):
153+
pr = PullRequest(
154+
repo_id=repo_id,
155+
number=pr_num,
156+
title=f"Test PR #{pr_num}",
157+
body=f"Test PR body for #{pr_num}",
158+
state="closed",
159+
merged=True,
160+
labels=[Label(name="enhancement")],
161+
created_at="2024-01-01T00:00:00Z",
162+
updated_at="2024-01-01T00:00:00Z",
163+
closed_at="2024-01-01T00:00:00Z",
164+
merged_at="2024-01-01T00:00:00Z",
165+
merge_commit_sha=f"abc{pr_num}def",
166+
head_ref=f"feature/test-{pr_num}",
167+
base_ref="main"
168+
)
169+
db.upsert_pull_request(pr)
170+
test_data['prs'][pr_num] = pr
171+
172+
# Create corresponding issues
173+
for issue_num in range(101, 111):
174+
issue = Issue(
175+
repo_id=repo_id,
176+
number=issue_num,
177+
key=f"#{issue_num}", # Issue key (e.g., "#101")
178+
title=f"Test Issue #{issue_num}",
179+
body=f"Test issue body for #{issue_num}",
180+
state="closed",
181+
labels=[Label(name="enhancement")],
182+
created_at="2024-01-01T00:00:00Z",
183+
closed_at="2024-01-01T00:00:00Z"
184+
)
185+
db.upsert_issue(issue)
186+
test_data['issues'][issue_num] = issue
187+
188+
yield db, repo_id, test_data
189+
190+
191+
@pytest.fixture
192+
def mock_github_api():
193+
"""
194+
Patch GitHub API calls to avoid external dependencies.
195+
196+
Use as a context manager or decorator.
197+
"""
198+
with patch('release_tool.github_utils.GitHubClient') as mock_client_class:
199+
# Create a mock instance
200+
mock_instance = Mock()
201+
202+
# Mock the class to return our instance
203+
mock_client_class.return_value = mock_instance
204+
205+
# Setup default return values
206+
mock_instance.get_pull_request.return_value = None
207+
mock_instance.get_issue.return_value = None
208+
209+
yield mock_instance

tests/helpers/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# SPDX-FileCopyrightText: 2025 Sequent Tech Inc <legal@sequentech.io>
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
"""Test helper utilities."""

tests/helpers/config_helpers.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# SPDX-FileCopyrightText: 2025 Sequent Tech Inc <legal@sequentech.io>
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
"""Helper functions for creating test configurations."""
6+
7+
import toml
8+
from pathlib import Path
9+
from typing import Dict, Any, List, Optional
10+
11+
12+
def minimal_config(code_repo: str = "test/repo") -> Dict[str, Any]:
13+
"""Return minimal valid configuration."""
14+
return {
15+
"repository": {
16+
"code_repo": code_repo
17+
},
18+
"github": {
19+
"token": "test_token"
20+
}
21+
}
22+
23+
24+
def create_test_config(
25+
code_repo: str = "test/repo",
26+
pr_code_templates: Optional[List[Dict[str, Any]]] = None,
27+
draft_output_path: str = ".release_tool_cache/draft-releases/{{code_repo}}/{{version}}.md",
28+
**kwargs
29+
) -> Dict[str, Any]:
30+
"""
31+
Create a test configuration dictionary.
32+
33+
Args:
34+
code_repo: Repository name
35+
pr_code_templates: List of pr_code template configurations
36+
draft_output_path: Path template for draft file
37+
**kwargs: Additional config overrides
38+
39+
Returns:
40+
Configuration dictionary
41+
"""
42+
config = {
43+
"repository": {
44+
"code_repo": code_repo
45+
},
46+
"github": {
47+
"token": "test_token"
48+
},
49+
"output": {
50+
"draft_output_path": draft_output_path
51+
}
52+
}
53+
54+
# Add pr_code templates if provided
55+
if pr_code_templates:
56+
config["output"]["pr_code"] = {
57+
"templates": pr_code_templates
58+
}
59+
60+
# Merge additional kwargs
61+
for key, value in kwargs.items():
62+
if isinstance(value, dict) and key in config:
63+
config[key].update(value)
64+
else:
65+
config[key] = value
66+
67+
return config
68+
69+
70+
def create_pr_code_template(
71+
output_template: str,
72+
output_path: str,
73+
release_version_policy: str = "final-only"
74+
) -> Dict[str, Any]:
75+
"""
76+
Create a pr_code template configuration.
77+
78+
Args:
79+
output_template: Jinja2 template string for the output
80+
output_path: Output file path template
81+
release_version_policy: Policy for version comparison ("final-only" or "include-rcs")
82+
83+
Returns:
84+
Template configuration dictionary
85+
"""
86+
return {
87+
"output_template": output_template,
88+
"output_path": output_path,
89+
"release_version_policy": release_version_policy
90+
}
91+
92+
93+
def default_pr_code_template(
94+
output_path: str = "docs/releases/{{version}}.md",
95+
release_version_policy: str = "final-only"
96+
) -> Dict[str, Any]:
97+
"""
98+
Create a default pr_code template with standard formatting.
99+
100+
Args:
101+
output_path: Output file path template
102+
release_version_policy: Policy for version comparison
103+
104+
Returns:
105+
Template configuration dictionary
106+
"""
107+
template = """# Release {{ title }}
108+
109+
{% for category in categories %}
110+
## {{ category.name }}
111+
{% for note in category.notes %}
112+
- {{ note.title }}{% if note.pr_numbers %} (#{{ note.pr_numbers[0] }}){% endif %}
113+
{% endfor %}
114+
115+
{% endfor %}"""
116+
117+
return create_pr_code_template(
118+
output_template=template,
119+
output_path=output_path,
120+
release_version_policy=release_version_policy
121+
)
122+
123+
124+
def write_config_file(path: Path, config_dict: Dict[str, Any]) -> None:
125+
"""
126+
Write configuration dictionary to TOML file.
127+
128+
Args:
129+
path: Path to write config file
130+
config_dict: Configuration dictionary
131+
"""
132+
path.parent.mkdir(parents=True, exist_ok=True)
133+
with open(path, 'w') as f:
134+
toml.dump(config_dict, f)

0 commit comments

Comments
 (0)