arcade-mcp/toolkits/linear/tests/test_utils.py
Shub 0b1f513826
Add Simplified Linear Toolkit (#465)
This PR adds a simplified Linear toolkit focused on core functionality:

## What's Included

**Tools:**
- `get_issue` - Get detailed information about Linear issues
- `get_teams` - Get team information and details

**Models:**
- `DateRange` enum with comprehensive date range support (TODAY,
YESTERDAY, THIS_WEEK, LAST_WEEK, THIS_MONTH, LAST_MONTH, THIS_YEAR,
LAST_YEAR, LAST_7_DAYS, LAST_30_DAYS)
- Timezone-aware datetime handling following Google toolkit patterns

## What's Simplified

This toolkit has been streamlined by removing:
- Cycles management tools
- Projects management tools  
- Users management tools
- Workflows management tools
- Corresponding tests and evaluations for removed features

## Quality Assurance

- All linting and formatting checks pass
- Comprehensive test coverage for included functionality
- Follows established patterns from Google toolkit

---------

Co-authored-by: Eric Gustin <34000337+EricGustin@users.noreply.github.com>
2025-07-18 14:08:58 -07:00

236 lines
8.6 KiB
Python

import pytest
from arcade_tdk.errors import ToolExecutionError
from arcade_linear.models import DateRange
from arcade_linear.utils import (
add_pagination_info,
clean_issue_data,
clean_team_data,
parse_date_range,
parse_date_string,
remove_none_values,
validate_date_format,
)
class TestDateParsing:
"""Tests for date parsing utilities"""
def test_parse_date_string_valid_iso(self):
"""Test date parsing with valid ISO strings"""
result = parse_date_string("2024-01-01")
assert result is not None
assert result.year == 2024
assert result.month == 1
assert result.day == 1
assert result.tzinfo is not None
def test_parse_date_string_with_time(self):
"""Test date parsing with date and time"""
result = parse_date_string("2024-01-01T12:30:00Z")
assert result is not None
assert result.year == 2024
assert result.hour == 12
assert result.minute == 30
def test_parse_date_string_relative(self):
"""Test date parsing with relative strings"""
result = parse_date_string("today")
assert result is not None
result = parse_date_string("yesterday")
assert result is not None
def test_parse_date_string_time_mappings(self):
"""Test date parsing with DateRange enum expressions"""
result = parse_date_string("this week")
assert result is not None
result = parse_date_string("last month")
assert result is not None
result = parse_date_string("yesterday")
assert result is not None
result = parse_date_string("last 7 days")
assert result is not None
def test_parse_date_string_invalid(self):
"""Test date parsing with invalid strings"""
result = parse_date_string("invalid-date")
assert result is None
result = parse_date_string("not-a-date")
assert result is None
def test_parse_date_string_empty(self):
"""Test date parsing with empty string"""
result = parse_date_string("")
assert result is None
result = parse_date_string(None)
assert result is None
def test_validate_date_format_valid(self):
"""Test date validation with valid format"""
# Should not raise exception
validate_date_format("test_field", "2024-01-01")
validate_date_format("test_field", "today")
validate_date_format("test_field", None)
validate_date_format("test_field", "")
def test_validate_date_format_invalid(self):
"""Test date validation with invalid format"""
with pytest.raises(ToolExecutionError) as exc_info:
validate_date_format("test_field", "invalid-date")
assert "Invalid date format for test_field" in str(exc_info.value)
class TestDateRange:
"""Tests for DateRange enum"""
def test_date_range_from_string_valid(self):
"""Test DateRange.from_string with valid strings"""
assert DateRange.from_string("today") == DateRange.TODAY
assert DateRange.from_string("yesterday") == DateRange.YESTERDAY
assert DateRange.from_string("last week") == DateRange.LAST_WEEK
assert DateRange.from_string("this month") == DateRange.THIS_MONTH
assert DateRange.from_string("last month") == DateRange.LAST_MONTH
assert DateRange.from_string("last 7 days") == DateRange.LAST_7_DAYS
assert DateRange.from_string("last 30 days") == DateRange.LAST_30_DAYS
def test_date_range_from_string_case_insensitive(self):
"""Test DateRange.from_string is case insensitive"""
assert DateRange.from_string("TODAY") == DateRange.TODAY
assert DateRange.from_string("Last Week") == DateRange.LAST_WEEK
assert DateRange.from_string("THIS MONTH") == DateRange.THIS_MONTH
def test_date_range_from_string_invalid(self):
"""Test DateRange.from_string with invalid strings"""
assert DateRange.from_string("invalid") is None
assert DateRange.from_string("next week") is None
assert DateRange.from_string("") is None
def test_date_range_to_datetime_range(self):
"""Test DateRange.to_datetime_range returns proper datetime objects"""
today_range = DateRange.TODAY.to_datetime_range()
assert len(today_range) == 2
start, end = today_range
assert start.tzinfo is not None
assert end.tzinfo is not None
assert start <= end
def test_date_range_to_start_datetime(self):
"""Test DateRange.to_start_datetime returns proper datetime"""
start = DateRange.LAST_WEEK.to_start_datetime()
assert start.tzinfo is not None
def test_date_range_to_end_datetime(self):
"""Test DateRange.to_end_datetime returns proper datetime"""
end = DateRange.LAST_MONTH.to_end_datetime()
assert end.tzinfo is not None
class TestParseDateRange:
"""Tests for parse_date_range function"""
def test_parse_date_range_valid(self):
"""Test parse_date_range with valid DateRange strings"""
result = parse_date_range("today")
assert result is not None
assert len(result) == 2
start, end = result
assert start.tzinfo is not None
assert end.tzinfo is not None
def test_parse_date_range_invalid(self):
"""Test parse_date_range with invalid strings"""
assert parse_date_range("invalid") is None
assert parse_date_range("") is None
assert parse_date_range("2024-01-01") is None # ISO date, not range
def test_parse_date_range_relative_dates(self):
"""Test parse_date_range with various relative date strings"""
ranges = ["yesterday", "last week", "this month", "last month", "last 7 days"]
for range_str in ranges:
result = parse_date_range(range_str)
assert result is not None, f"Failed for {range_str}"
start, end = result
assert start <= end, f"Invalid range for {range_str}"
class TestDataCleaning:
"""Tests for data cleaning functions"""
def test_remove_none_values(self):
"""Test removing None values from dictionary"""
data = {"a": 1, "b": None, "c": "test", "d": None}
result = remove_none_values(data)
assert result == {"a": 1, "c": "test"}
def test_clean_team_data(self):
"""Test team data cleaning"""
team_data = {
"id": "team_1",
"key": "FE",
"name": "Frontend",
"description": "Frontend team",
"private": False,
"archivedAt": None,
"createdAt": "2024-01-01T00:00:00Z",
"members": {"nodes": [{"id": "user_1", "name": "John Doe"}]},
}
result = clean_team_data(team_data)
assert result["id"] == "team_1"
assert result["key"] == "FE"
assert result["name"] == "Frontend"
assert len(result["members"]) == 1
assert result["members"][0]["name"] == "John Doe"
def test_clean_issue_data(self):
"""Test issue data cleaning"""
issue_data = {
"id": "issue_1",
"identifier": "FE-123",
"title": "Test issue",
"description": "Issue description",
"priority": 2,
"priorityLabel": "High",
"createdAt": "2024-01-01T00:00:00Z",
"assignee": {"id": "user_1", "name": "John Doe"},
"state": {"id": "state_1", "name": "In Progress"},
"team": {"id": "team_1", "name": "Frontend"},
"labels": {"nodes": [{"id": "label_1", "name": "bug"}]},
"children": {"nodes": []},
}
result = clean_issue_data(issue_data)
assert result["id"] == "issue_1"
assert result["identifier"] == "FE-123"
assert result["title"] == "Test issue"
assert result["assignee"]["name"] == "John Doe"
assert result["state"]["name"] == "In Progress"
assert result["team"]["name"] == "Frontend"
assert len(result["labels"]) == 1
assert result["labels"][0]["name"] == "bug"
def test_add_pagination_info(self):
"""Test adding pagination information"""
response = {"data": "test"}
page_info = {
"hasNextPage": True,
"hasPreviousPage": False,
"startCursor": "start123",
"endCursor": "end456",
}
result = add_pagination_info(response, page_info)
assert result["pagination"]["has_next_page"] is True
assert result["pagination"]["has_previous_page"] is False
assert result["pagination"]["start_cursor"] == "start123"
assert result["pagination"]["end_cursor"] == "end456"