101 lines
3.2 KiB
Python
101 lines
3.2 KiB
Python
"""Source mapping: the parts that work without a network."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date
|
|
|
|
from app.settings_schema import JiraSourceSettings, LaundrySourceSettings
|
|
from app.sources.jira import JiraSource, start_date_clause
|
|
from app.sources.laundry import parse_cookie_header, parse_my_slots
|
|
|
|
JIRA_CONFIG = JiraSourceSettings(
|
|
enabled=True,
|
|
base_url="https://example.atlassian.net",
|
|
email="me@example.com",
|
|
api_token="token",
|
|
)
|
|
|
|
|
|
def test_start_date_clause_handles_custom_fields_and_names():
|
|
assert start_date_clause("customfield_10015") == "cf[10015]"
|
|
assert start_date_clause("Start date") == '"Start date"'
|
|
|
|
|
|
def test_jira_jql_includes_the_extra_filter():
|
|
source = JiraSource(JIRA_CONFIG.model_copy(update={"extra_jql": "assignee = currentUser()"}))
|
|
jql = source._jql()
|
|
assert "statusCategory != Done" in jql
|
|
assert "assignee = currentUser()" in jql
|
|
source.close()
|
|
|
|
|
|
def test_jira_issue_maps_to_a_todo_item():
|
|
source = JiraSource(JIRA_CONFIG)
|
|
item = source._to_item(
|
|
{
|
|
"key": "ABC-1",
|
|
"fields": {
|
|
"summary": "Fix the thing",
|
|
"duedate": "2026-10-20",
|
|
"customfield_10015": "2026-10-15",
|
|
"status": {"name": "In Progress", "statusCategory": {"key": "indeterminate"}},
|
|
"priority": {"name": "High"},
|
|
"project": {"name": "Platform"},
|
|
"labels": ["backend"],
|
|
},
|
|
}
|
|
)
|
|
source.close()
|
|
assert item.external_id == "ABC-1"
|
|
assert item.title == "Fix the thing"
|
|
assert item.url == "https://example.atlassian.net/browse/ABC-1"
|
|
assert item.due_date == date(2026, 10, 20)
|
|
assert item.start_date == date(2026, 10, 15)
|
|
assert item.priority == "High"
|
|
assert item.is_done is False
|
|
assert item.days_overdue(date(2026, 10, 22)) == 2
|
|
|
|
|
|
def test_cookie_header_accepts_both_forms():
|
|
assert parse_cookie_header("abc123") == {"sessionid": "abc123"}
|
|
parsed = parse_cookie_header("csrftoken=x; sessionid=abc123")
|
|
assert parsed["sessionid"] == "abc123"
|
|
|
|
|
|
LAUNDRY_HTML = """
|
|
<div class="date-machines" id="Чт, 15 окт.">
|
|
<div class="machines">
|
|
<div class="floor-title">Этаж 1</div>
|
|
<div class="machine">
|
|
<div class="machine-title">Машинка 3</div>
|
|
<div class="period my finished"><div>09:00-12:00</div></div>
|
|
<div class="period my current"><div>18:00-21:00</div></div>
|
|
<div class="period"><div>21:00-00:00</div></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
"""
|
|
|
|
|
|
def test_laundry_grid_yields_only_my_upcoming_slots():
|
|
label, slots = parse_my_slots(LAUNDRY_HTML)
|
|
assert label == "Чт, 15 окт."
|
|
assert len(slots) == 2
|
|
upcoming = [slot for slot in slots if slot.is_upcoming]
|
|
assert len(upcoming) == 1
|
|
assert upcoming[0].time_range == "18:00-21:00"
|
|
assert upcoming[0].machine == "Машинка 3"
|
|
assert upcoming[0].location == "Этаж 1"
|
|
|
|
|
|
def test_laundry_config_requires_a_session_cookie():
|
|
from app.sources.base import SourceError
|
|
|
|
try:
|
|
from app.sources.laundry import LaundrySource
|
|
|
|
LaundrySource(LaundrySourceSettings(enabled=True, session_cookie=""))
|
|
except SourceError:
|
|
return
|
|
raise AssertionError("expected a SourceError")
|