Skip to content

Commit 56d99d8

Browse files
committed
feat: Add more comprehensive tests
1 parent ff2f7ee commit 56d99d8

File tree

1 file changed

+197
-4
lines changed

1 file changed

+197
-4
lines changed

test_concourse.py

Lines changed: 197 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from github.GithubObject import NotSet
12
import pytest
23
from unittest.mock import MagicMock, patch
34
from datetime import datetime, timedelta
@@ -7,10 +8,38 @@
78
ConcourseGithubIssuesVersion,
89
ISO_8601_FORMAT,
910
)
11+
from concoursetools import BuildMetadata # Import the actual class
1012
from concoursetools.testing import SimpleTestResourceWrapper
1113
from github.Issue import Issue
1214

1315

16+
# Helper function to create mock BuildMetadata objects
17+
def mock_build_metadata(**kwargs) -> BuildMetadata:
18+
"""Creates a BuildMetadata object with default values, allowing overrides."""
19+
defaults = {
20+
"BUILD_ID": "12345",
21+
"BUILD_NAME": "42",
22+
"BUILD_JOB_NAME": "test-job",
23+
"BUILD_PIPELINE_NAME": "test-pipeline",
24+
"BUILD_PIPELINE_INSTANCE_VARS": '{"var": "value"}',
25+
"BUILD_TEAM_NAME": "main",
26+
"ATC_EXTERNAL_URL": "http://concourse.example.com",
27+
}
28+
# Map simplified kwargs to the expected BuildMetadata keys
29+
key_map = {
30+
"pipeline_name": "BUILD_PIPELINE_NAME",
31+
"job_name": "BUILD_JOB_NAME",
32+
"build_name": "BUILD_NAME",
33+
# Add other mappings if needed
34+
}
35+
mapped_kwargs = {key_map.get(k, k): v for k, v in kwargs.items()}
36+
37+
# Override defaults with provided mapped kwargs
38+
defaults.update(mapped_kwargs)
39+
# Create BuildMetadata instance using the combined dict
40+
return BuildMetadata(**defaults)
41+
42+
1443
# Helper function to create mock Issue objects
1544
def create_mock_issue(
1645
number: int,
@@ -90,6 +119,9 @@ def mock_github():
90119
with patch("concourse.Github") as MockGithub:
91120
mock_gh_instance = MockGithub.return_value
92121
mock_repo = MagicMock()
122+
mock_repo.full_name = (
123+
"test/repo"
124+
) # Set the full_name attribute for search queries
93125
mock_gh_instance.get_repo.return_value = mock_repo
94126
# Set a default rate limit mock to avoid errors
95127
mock_rate_limit = MagicMock()
@@ -132,7 +164,7 @@ def test_fetch_new_versions_no_previous(
132164
assert version_numbers == expected_issue_numbers
133165
# Verify get_issues was called with the correct state and no 'since'
134166
mock_repo.get_issues.assert_called_once_with(
135-
state=config_state, labels=[], since=None
167+
state=config_state, labels=[], since=NotSet
136168
)
137169

138170

@@ -318,8 +350,169 @@ def test_fetch_new_versions_limit_old(mock_github):
318350
# get_matching_issues sorts by number ascending: 1, 5, 6, 7, 8, 9
319351
# limit_old_versions=2 takes the first 2: 1, 5
320352
assert version_numbers == {1, 5}
321-
mock_repo.get_issues.assert_called_once_with(state="closed", labels=[], since=None)
353+
mock_repo.get_issues.assert_called_once_with(
354+
state="closed", labels=[], since=NotSet
355+
)
356+
357+
358+
@patch("pathlib.Path.open")
359+
def test_download_version_tombstones(mock_open, mock_github, tmp_path):
360+
"""Test that download_version tombstones the issue and writes the file."""
361+
mock_gh_instance, mock_repo = mock_github
362+
mock_issue = create_mock_issue(
363+
number=5,
364+
title="[bot] Ready Issue",
365+
state="closed",
366+
created_at=T_MINUS_2,
367+
closed_at=T_MINUS_1,
368+
)
369+
mock_repo.get_issue.return_value = mock_issue
370+
371+
resource = ConcourseGithubIssuesResource(
372+
repository="test/repo", access_token="dummy_token", issue_state="closed"
373+
)
374+
# wrapper = SimpleTestResourceWrapper(resource) # Wrapper not needed for download test
375+
376+
version_to_download = ConcourseGithubIssuesVersion(
377+
issue_number=5,
378+
issue_title="[bot] Ready Issue",
379+
issue_state="closed",
380+
issue_created_at=T_MINUS_2.strftime(ISO_8601_FORMAT),
381+
issue_closed_at=T_MINUS_1.strftime(ISO_8601_FORMAT),
382+
issue_url="http://example.com/issue/5",
383+
)
384+
385+
build_meta = mock_build_metadata() # Use default build meta here
386+
dest_dir = str(tmp_path)
387+
388+
# Call download_version directly on the resource instance
389+
returned_version, returned_metadata = resource.download_version(
390+
version=version_to_download,
391+
destination_dir=dest_dir,
392+
build_metadata=build_meta,
393+
)
394+
395+
# Check tombstoning
396+
# Need to update the expected title based on the default build_meta name '42'
397+
mock_repo.get_issue.assert_called_once_with(5)
398+
# Calculate the expected title exactly how the resource does it
399+
current_title_from_build = resource.get_title_from_build(build_meta)
400+
expected_tombstone_title = (
401+
f"[CONSUMED #{build_meta.BUILD_NAME}]" + current_title_from_build
402+
)
403+
mock_issue.edit.assert_called_once_with(title=expected_tombstone_title)
404+
# Check file writing
405+
# expected_file_path = Path(dest_dir) / "gh_issue.json" # This path wasn't used, just verify open call
406+
mock_open.assert_called_once_with("w")
407+
# Check that the file handle's write method was called (actual content check is tricky with mock_open)
408+
mock_open.return_value.__enter__.return_value.write.assert_called_once()
409+
410+
# Check return values
411+
assert returned_version == version_to_download
412+
assert returned_metadata == {}
413+
414+
415+
def test_publish_new_version_creates_new_issue(mock_github):
416+
"""Test publish creates a new issue when none exists."""
417+
mock_gh_instance, mock_repo = mock_github
418+
mock_gh_instance.search_issues.return_value = [] # No existing issue found
419+
created_mock_issue = create_mock_issue(
420+
number=10,
421+
title="[bot] Pipeline my-pipeline task my-job completed",
422+
state="open",
423+
created_at=NOW,
424+
)
425+
mock_repo.create_issue.return_value = created_mock_issue
426+
427+
resource = ConcourseGithubIssuesResource(
428+
repository="test/repo",
429+
access_token="dummy_token",
430+
issue_state="open", # Important for publish logic
431+
issue_title_template="[bot] Pipeline {BUILD_PIPELINE_NAME} task {BUILD_JOB_NAME} completed",
432+
issue_body_template="Build {BUILD_NAME} finished.",
433+
assignees=["user1"],
434+
labels=["bot-created"],
435+
)
436+
# wrapper = SimpleTestResourceWrapper(resource) # Wrapper not needed for publish tests
437+
build_meta = mock_build_metadata(
438+
pipeline_name="my-pipeline", job_name="my-job", build_name="b123"
439+
)
440+
441+
# Use resource directly for publish, wrapper doesn't have it
442+
version, metadata = resource.publish_new_version(
443+
sources_dir="dummy",
444+
build_metadata=build_meta,
445+
assignees=["user1"], # Pass explicitly if needed by method
446+
labels=["bot-created"],
447+
)
448+
449+
# Check search was called
450+
expected_title = "[bot] Pipeline my-pipeline task my-job completed"
451+
expected_query = f'repo:test/repo state:open "{expected_title}" in:title is:issue'
452+
mock_gh_instance.search_issues.assert_called_once_with(expected_query)
453+
454+
# Check create_issue was called
455+
expected_body = "Build b123 finished."
456+
mock_repo.create_issue.assert_called_once_with(
457+
title=expected_title,
458+
assignees=["user1"],
459+
labels=["bot-created"],
460+
body=expected_body,
461+
)
462+
463+
# Check returned version
464+
assert version.issue_number == 10
465+
assert version.issue_title == expected_title
466+
assert version.issue_state == "open"
467+
assert metadata == {}
468+
469+
470+
def test_publish_new_version_comments_on_existing(mock_github):
471+
"""Test publish comments on an existing issue if found."""
472+
mock_gh_instance, mock_repo = mock_github
473+
existing_mock_issue = create_mock_issue(
474+
number=9,
475+
title="[bot] Pipeline my-pipeline task my-job completed",
476+
state="open",
477+
created_at=T_MINUS_1,
478+
)
479+
# Mock the create_comment method on the existing issue
480+
existing_mock_issue.create_comment = MagicMock()
481+
mock_gh_instance.search_issues.return_value = [
482+
existing_mock_issue
483+
] # Found existing
484+
485+
resource = ConcourseGithubIssuesResource(
486+
repository="test/repo",
487+
access_token="dummy_token",
488+
issue_state="open",
489+
issue_title_template="[bot] Pipeline {BUILD_PIPELINE_NAME} task {BUILD_JOB_NAME} completed",
490+
issue_body_template="Build {BUILD_NAME} finished.",
491+
)
492+
# wrapper = SimpleTestResourceWrapper(resource) # Wrapper not needed for publish tests
493+
build_meta = mock_build_metadata(
494+
pipeline_name="my-pipeline", job_name="my-job", build_name="b456"
495+
)
496+
497+
# Use resource directly for publish
498+
version, metadata = resource.publish_new_version(
499+
sources_dir="dummy", build_metadata=build_meta
500+
)
501+
502+
# Check search was called
503+
expected_title = "[bot] Pipeline my-pipeline task my-job completed"
504+
expected_query = f'repo:test/repo state:open "{expected_title}" in:title is:issue'
505+
mock_gh_instance.search_issues.assert_called_once_with(expected_query)
506+
507+
# Check create_issue was NOT called
508+
mock_repo.create_issue.assert_not_called()
322509

510+
# Check create_comment was called on the existing issue
511+
expected_comment_body = "Build b456 finished."
512+
existing_mock_issue.create_comment.assert_called_once_with(expected_comment_body)
323513

324-
# TODO: Add tests for download_version (tombstoning) and publish_new_version (creation/commenting)
325-
# These would require mocking issue.edit(), issue.create_comment(), repo.create_issue(), gh.search_issues() etc.
514+
# Check returned version matches the existing issue
515+
assert version.issue_number == 9
516+
assert version.issue_title == expected_title
517+
assert version.issue_state == "open"
518+
assert metadata == {}

0 commit comments

Comments
 (0)