Skip to content
Closed
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
21 changes: 12 additions & 9 deletions src/pytest_ibutsu/pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,30 +132,33 @@ def is_server_mode(self) -> bool:
@cached_property
def project_uuid(self) -> str:
"""Return the ibutsu project value as a UUID.
If mode is upload and the `ibutsu_project` is a name, query the server to get the UUID.
If the project value is a name (not a UUID), query the Ibutsu server to resolve it.
Uses self.ibutsu_server in server mode, or IBUTSU_SERVER_URL env var otherwise.
"""
project_value = self.ibutsu_project

if not self.is_server_mode:
if validate_uuid_string(project_value):
return project_value

if validate_uuid_string(project_value):
server_url = self.ibutsu_server or os.environ.get("IBUTSU_SERVER_URL", "")
if not server_url:
logger.warning(
f"Cannot resolve project name '{project_value}' to UUID: "
"no server URL available. Set IBUTSU_SERVER_URL env var."
)
return project_value

# Query the Ibutsu server to get the UUID
logger.info(f"Using server: {self.ibutsu_server}")
logger.info(f"Using server: {server_url}")

config = create_api_configuration(
self.ibutsu_server, self.ibutsu_token, use_ssl_ca_cert=False
server_url, self.ibutsu_token, use_ssl_ca_cert=False
)
project_api = ProjectApi(ApiClient(config))

try:
response = project_api.get_project_list(filter=[f"name={project_value}"])
except NotFoundException:
logger.warning(
f"Could not find '{project_value}' on '{self.ibutsu_server}' (404)."
)
logger.warning(f"Could not find '{project_value}' on '{server_url}' (404).")
return project_value

if response.projects and len(response.projects) > 0:
Expand Down
136 changes: 136 additions & 0 deletions tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from datetime import datetime
from datetime import timezone
from typing import Generator
from unittest.mock import MagicMock, patch

import pytest
from jose import jwt
Expand Down Expand Up @@ -34,6 +35,7 @@ def isolate_ibutsu_env_vars(
"IBUTSU_DATA",
"IBUTSU_CA_BUNDLE",
"IBUTSU_ENV_ID",
"IBUTSU_SERVER_URL",
# Also include related AWS/S3 variables that might affect tests
"AWS_ACCESS_KEY_ID",
"AWS_SECRET_ACCESS_KEY",
Expand Down Expand Up @@ -321,3 +323,137 @@ def test_ibutsu_data_cli_overrides_environment(
plugin = IbutsuPlugin.from_config(test_config)
# CLI should override environment
assert plugin.extra_data == {"cli_key": "cli_value"}


class TestProjectUuid:
"""Tests for the project_uuid cached_property."""

def test_project_uuid_already_valid_uuid(
self, isolate_ibutsu_env_vars: None, pytester: pytest.Pytester
):
"""If project is already a UUID, return it directly without querying server."""
test_config = pytester.parseconfig(
"--ibutsu", "s3", "--ibutsu-project", "12345678-1234-1234-1234-123456789abc"
)
plugin = IbutsuPlugin.from_config(test_config)
assert plugin.project_uuid == "12345678-1234-1234-1234-123456789abc"

def test_project_uuid_s3_mode_no_server_url_returns_name(
self,
isolate_ibutsu_env_vars: None,
pytester: pytest.Pytester,
monkeypatch: pytest.MonkeyPatch,
):
"""In S3 mode with no IBUTSU_SERVER_URL, falls back to project name."""
monkeypatch.delenv("IBUTSU_SERVER_URL", raising=False)
test_config = pytester.parseconfig(
"--ibutsu", "s3", "--ibutsu-project", "insights-qe"
)
plugin = IbutsuPlugin.from_config(test_config)
assert plugin.project_uuid == "insights-qe"

@patch("pytest_ibutsu.pytest_plugin.ProjectApi")
@patch("pytest_ibutsu.pytest_plugin.ApiClient")
@patch("pytest_ibutsu.pytest_plugin.create_api_configuration")
def test_project_uuid_s3_mode_resolves_with_server_url(
self,
mock_create_config,
mock_api_client,
mock_project_api_cls,
isolate_ibutsu_env_vars: None,
pytester: pytest.Pytester,
monkeypatch: pytest.MonkeyPatch,
):
"""In S3 mode with IBUTSU_SERVER_URL set, resolves project name to UUID."""
monkeypatch.setenv("IBUTSU_SERVER_URL", "https://ibutsu-api.example.com")
test_config = pytester.parseconfig(
"--ibutsu", "s3", "--ibutsu-project", "insights-qe"
)
plugin = IbutsuPlugin.from_config(test_config)

mock_response = MagicMock()
mock_response.projects = [MagicMock(id="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee")]
mock_project_api_cls.return_value.get_project_list.return_value = mock_response

assert plugin.project_uuid == "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
mock_create_config.assert_called_once_with(
"https://ibutsu-api.example.com", None, use_ssl_ca_cert=False
)

@patch("pytest_ibutsu.pytest_plugin.ProjectApi")
@patch("pytest_ibutsu.pytest_plugin.ApiClient")
@patch("pytest_ibutsu.pytest_plugin.create_api_configuration")
def test_project_uuid_server_mode_resolves_name(
self,
mock_create_config,
mock_api_client,
mock_project_api_cls,
isolate_ibutsu_env_vars: None,
pytester: pytest.Pytester,
):
"""In server mode, resolves project name to UUID using the server URL."""
test_config = pytester.parseconfig(
"--ibutsu",
"https://ibutsu-api.example.com",
"--ibutsu-project",
"insights-qe",
)
plugin = IbutsuPlugin.from_config(test_config)

mock_response = MagicMock()
mock_response.projects = [MagicMock(id="11111111-2222-3333-4444-555555555555")]
mock_project_api_cls.return_value.get_project_list.return_value = mock_response

assert plugin.project_uuid == "11111111-2222-3333-4444-555555555555"

@patch("pytest_ibutsu.pytest_plugin.ProjectApi")
@patch("pytest_ibutsu.pytest_plugin.ApiClient")
@patch("pytest_ibutsu.pytest_plugin.create_api_configuration")
def test_project_uuid_server_returns_empty_list_falls_back(
self,
mock_create_config,
mock_api_client,
mock_project_api_cls,
isolate_ibutsu_env_vars: None,
pytester: pytest.Pytester,
monkeypatch: pytest.MonkeyPatch,
):
"""When server returns no matching projects, falls back to project name."""
Comment thread
akhil-jha marked this conversation as resolved.
monkeypatch.setenv("IBUTSU_SERVER_URL", "https://ibutsu-api.example.com")
test_config = pytester.parseconfig(
"--ibutsu", "s3", "--ibutsu-project", "nonexistent-project"
)
plugin = IbutsuPlugin.from_config(test_config)

mock_response = MagicMock()
mock_response.projects = []
mock_project_api_cls.return_value.get_project_list.return_value = mock_response

assert plugin.project_uuid == "nonexistent-project"

@patch("pytest_ibutsu.pytest_plugin.ProjectApi")
@patch("pytest_ibutsu.pytest_plugin.ApiClient")
@patch("pytest_ibutsu.pytest_plugin.create_api_configuration")
def test_project_uuid_not_found_exception_falls_back(
self,
mock_create_config,
mock_api_client,
mock_project_api_cls,
isolate_ibutsu_env_vars: None,
pytester: pytest.Pytester,
monkeypatch: pytest.MonkeyPatch,
):
"""When server returns 404 NotFoundException, falls back to project name."""
from ibutsu_client.exceptions import NotFoundException

monkeypatch.setenv("IBUTSU_SERVER_URL", "https://ibutsu-api.example.com")
test_config = pytester.parseconfig(
"--ibutsu", "s3", "--ibutsu-project", "deleted-project"
)
plugin = IbutsuPlugin.from_config(test_config)

mock_project_api_cls.return_value.get_project_list.side_effect = (
NotFoundException(status=404, reason="Not Found")
)

assert plugin.project_uuid == "deleted-project"
Loading