191 lines
6.5 KiB
Python
191 lines
6.5 KiB
Python
# This file is part of sbs
|
|
#
|
|
# Copyright (C) 2025 Steve Milner
|
|
#
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
# Generated by Gemini 2.5 Flash
|
|
|
|
import pytest
|
|
|
|
from unittest.mock import patch, MagicMock
|
|
from urllib import parse
|
|
|
|
from sbs.ds.card import Card
|
|
from sbs.ds.jql import jira
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_response():
|
|
"""
|
|
A pytest fixture to create a mock requests.Response object.
|
|
"""
|
|
mock_resp = MagicMock()
|
|
mock_resp.status_code = 200
|
|
return mock_resp
|
|
|
|
|
|
def test_jira_fetches_and_parses_single_issue(mock_response):
|
|
"""
|
|
Test that the jira function correctly fetches and parses a single Jira issue.
|
|
"""
|
|
# Define the mock JSON response from Jira API
|
|
mock_jira_json = {
|
|
"issues": [
|
|
{
|
|
"key": "PROJ-123",
|
|
"fields": {
|
|
"assignee": {"name": "John Doe"},
|
|
"summary": "Implement feature A",
|
|
"description": "Details for feature A implementation.",
|
|
"customfield_12310243": 5,
|
|
},
|
|
}
|
|
]
|
|
}
|
|
mock_response.json.return_value = mock_jira_json
|
|
|
|
# Patch requests.get to return our mock_response
|
|
with patch("requests.get", return_value=mock_response) as mock_get:
|
|
endpoint = "https://jira.example.com/rest/api/2"
|
|
token = "test_token"
|
|
query = "project = PROJ AND status = Open"
|
|
|
|
cards = list(jira(endpoint, token, query))
|
|
|
|
# Assertions
|
|
expected_fjql = parse.quote_plus(query)
|
|
expected_fields = parse.quote_plus(
|
|
"assignee,summary,description,customfield_12310243"
|
|
)
|
|
expected_url = f"{endpoint}/search/?fields={expected_fields}&maxResults=100&jql={expected_fjql}"
|
|
|
|
mock_get.assert_called_once_with(
|
|
expected_url, headers={"Authorization": f"Bearer {token}"}
|
|
)
|
|
|
|
assert len(cards) == 1
|
|
card = cards[0]
|
|
assert isinstance(card, Card)
|
|
assert card.key == "PROJ-123"
|
|
assert card.assignee == "John Doe"
|
|
assert card.summary == "Implement feature A"
|
|
assert card.description == "Details for feature A implementation."
|
|
assert card.story_points == 5
|
|
|
|
|
|
def test_jira_fetches_and_parses_multiple_issues(mock_response):
|
|
"""
|
|
Test that the jira function correctly fetches and parses multiple Jira issues.
|
|
"""
|
|
mock_jira_json = {
|
|
"issues": [
|
|
{
|
|
"key": "PROJ-1",
|
|
"fields": {
|
|
"assignee": {"name": "User A"},
|
|
"summary": "Task 1",
|
|
"description": "Description 1",
|
|
"customfield_12310243": 3,
|
|
},
|
|
},
|
|
{
|
|
"key": "PROJ-2",
|
|
"fields": {
|
|
"assignee": {"name": "User B"},
|
|
"summary": "Task 2",
|
|
"description": "Description 2",
|
|
"customfield_12310243": 8,
|
|
},
|
|
},
|
|
]
|
|
}
|
|
mock_response.json.return_value = mock_jira_json
|
|
|
|
with patch("requests.get", return_value=mock_response):
|
|
cards = list(jira("dummy_endpoint", "dummy_token", "dummy_query"))
|
|
|
|
assert len(cards) == 2
|
|
assert cards[0].key == "PROJ-1"
|
|
assert cards[1].assignee == "User B"
|
|
assert cards[1].story_points == 8
|
|
|
|
|
|
def test_jira_handles_issue_with_no_assignee(mock_response):
|
|
"""
|
|
Test that the jira function correctly handles issues where the assignee field is missing.
|
|
It should default the assignee name to an empty string.
|
|
"""
|
|
mock_jira_json = {
|
|
"issues": [
|
|
{
|
|
"key": "PROJ-BUG",
|
|
"fields": {
|
|
"summary": "Untriaged Bug",
|
|
"description": "This bug has no assignee.",
|
|
"customfield_12310243": 1,
|
|
},
|
|
}
|
|
]
|
|
}
|
|
mock_response.json.return_value = mock_jira_json
|
|
|
|
with patch("requests.get", return_value=mock_response):
|
|
cards = list(jira("dummy_endpoint", "dummy_token", "dummy_query"))
|
|
|
|
assert len(cards) == 1
|
|
card = cards[0]
|
|
assert card.key == "PROJ-BUG"
|
|
assert card.assignee == "" # Should be an empty string
|
|
assert card.summary == "Untriaged Bug"
|
|
assert card.story_points == 1
|
|
|
|
|
|
def test_jira_handles_empty_issues_list(mock_response):
|
|
"""
|
|
Test that the jira function returns an empty list if the Jira API returns no issues.
|
|
"""
|
|
mock_jira_json = {"issues": []}
|
|
mock_response.json.return_value = mock_jira_json
|
|
|
|
with patch("requests.get", return_value=mock_response):
|
|
cards = list(jira("dummy_endpoint", "dummy_token", "dummy_query"))
|
|
assert len(cards) == 0
|
|
|
|
|
|
def test_jira_handles_api_error_response(mock_response):
|
|
"""
|
|
Test that the jira function gracefully handles a non-200 HTTP status code
|
|
by letting the requests library's behavior (e.g., raise_for_status) propagate
|
|
or by handling potential JSON decoding errors.
|
|
"""
|
|
mock_response.status_code = 404
|
|
# Simulate a JSON structure even for an error, as .json() is always called
|
|
mock_response.json.return_value = {"errorMessages": ["Issue Does Not Exist"]}
|
|
|
|
# Patch requests.get to return our mock_response
|
|
with patch("requests.get", return_value=mock_response) as mock_get:
|
|
endpoint = "https://jira.example.com/rest/api/2"
|
|
token = "test_token"
|
|
query = "project = NONEXISTENT"
|
|
|
|
# The original code calls .json() directly, so it expects JSON.
|
|
# If the API returns a non-200 status and valid JSON, the code
|
|
# will proceed. If it returns non-JSON, .json() would raise an error.
|
|
# For this test, we assume .json() works, and we are testing
|
|
# how the internal logic handles the 'issues' key not being present
|
|
# or the structure being different.
|
|
# In this specific case, the KeyError will be raised because 'issues'
|
|
# is not in the mock_jira_json for an error scenario. list() is used to
|
|
# call the underlying __next__().
|
|
with pytest.raises(KeyError):
|
|
list(jira(endpoint, token, query))
|
|
|
|
expected_fjql = parse.quote_plus(query)
|
|
expected_fields = parse.quote_plus(
|
|
"assignee,summary,description,customfield_12310243"
|
|
)
|
|
expected_url = f"{endpoint}/search/?fields={expected_fields}&maxResults=100&jql={expected_fjql}"
|
|
|
|
mock_get.assert_called_once_with(
|
|
expected_url, headers={"Authorization": f"Bearer {token}"}
|
|
)
|