Initial commit: MISF VOB download and upload automation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
lubna-fatimaandClaude Sonnet 4.6 committed 2026-06-16 14:55:29 +05:30
commit c2dec96b1f
43 files changed
+10944

No files matched your search

+54
View File
@@ -0,0 +1,54 @@
"""Tests for multi-attachment URL collection."""
from __future__ import annotations
from onedrive_planner import (
_attachment_tab_count,
_merge_attachment_hrefs,
_should_mark_task_in_progress,
)
SAMPLE_A = (
"https://netorg393890.sharepoint.com/sites/ArborHills/Shared%20Documents/"
"RICHARDSON%20ARBRHLS%205.26.26.pdf"
)
SAMPLE_B = (
"https://netorg393890.sharepoint.com/sites/ArborHills/Shared%20Documents/"
"RICHARDSON%20ARBRHLS%206.4.26.pdf"
)
class TestAttachmentMulti:
def test_merge_keeps_distinct_pdf_urls(self):
merged = _merge_attachment_hrefs(
[SAMPLE_A],
[SAMPLE_B],
[SAMPLE_A],
)
assert len(merged) == 2
assert SAMPLE_A in merged
assert SAMPLE_B in merged
def test_merge_dedupes_same_filename(self):
merged = _merge_attachment_hrefs([SAMPLE_A], [SAMPLE_A + "?download=1"])
assert len(merged) == 1
def test_mark_status_only_after_all_files(self, tmp_path):
one = tmp_path / "a.pdf"
two = tmp_path / "b.pdf"
one.write_bytes(b"x")
two.write_bytes(b"y")
assert not _should_mark_task_in_progress([one], 2)
assert _should_mark_task_in_progress([one, two], 2)
assert not _should_mark_task_in_progress([], 2)
assert _should_mark_task_in_progress([one], 0)
def test_attachment_tab_count_parses_number(self):
from unittest.mock import MagicMock
tab = MagicMock()
tab.is_displayed.return_value = True
tab.text = "Attachments (2)"
tab.get_attribute.return_value = ""
driver = MagicMock()
driver.find_elements.return_value = [tab]
assert _attachment_tab_count(driver) == 2
+100
View File
@@ -0,0 +1,100 @@
"""Unit tests for SharePoint URL extraction from attachment rows."""
from __future__ import annotations
from unittest.mock import MagicMock
import base64
from onedrive_planner import (
_attachment_filename_key,
_direct_download_url,
_download_href_via_browser_fetch,
_extract_sharepoint_url,
_is_pdf_bytes,
_resolve_attachment_click_target,
_sharepoint_download_candidates,
_url_from_attachment_element,
_viewer_preview_url,
)
SAMPLE = (
"https://netorg393890.sharepoint.com/sites/AnberryNursingandRehabilitationCenter/"
"Shared%20Documents/ANBERRY%20FS%205.29.26.pdf"
)
class TestAttachmentUrl:
def test_extract_from_aria_label(self):
assert _extract_sharepoint_url(SAMPLE) == SAMPLE
def test_direct_download_transform(self):
dl = _direct_download_url(SAMPLE)
assert "download=1" in dl
def test_viewer_preview_url(self):
viewer = _viewer_preview_url(SAMPLE + "?web=1")
assert "web=1" in viewer
assert "download=1" not in viewer
viewer2 = _viewer_preview_url(SAMPLE)
assert "web=1" in viewer2
def test_url_from_span_element(self):
span = MagicMock()
span.get_attribute.side_effect = lambda name: (
SAMPLE if name == "aria-label" else None
)
span.text = ""
span.find_elements.return_value = []
assert _url_from_attachment_element(span) == SAMPLE
def test_sharepoint_download_candidates_include_layouts(self):
cands = _sharepoint_download_candidates(SAMPLE)
assert any("download.aspx" in c for c in cands)
assert any("download=1" in c for c in cands)
def test_is_pdf_bytes(self):
assert _is_pdf_bytes(b"%PDF-1.4 fake content " + b"x" * 6000)
assert not _is_pdf_bytes(b"%PDF-1.4 tiny " + b"x" * 100)
assert not _is_pdf_bytes(b"<html>viewer</html>")
def test_resolve_click_target_prefers_href_span(self):
span = MagicMock()
span.is_displayed.return_value = True
span.tag_name = "span"
span.get_attribute.side_effect = lambda name: (
f"JACKSON ARBRHLS 6.3.26.pdf {SAMPLE}" if name == "aria-label" else None
)
span.text = ""
span.find_elements.return_value = []
li = MagicMock()
li.tag_name = "li"
li.is_displayed.return_value = True
li.get_attribute.return_value = None
li.text = "JACKSON ARBRHLS 6.3.26.pdf"
li.find_elements.return_value = [span]
assert _resolve_attachment_click_target(li) is span
assert "6.3.26.pdf" in _attachment_filename_key(span)
def test_browser_fetch_saves_pdf(self, tmp_path):
driver = MagicMock()
pdf = b"%PDF-1.4 " + b"x" * 6000
driver.execute_async_script.return_value = {
"ok": True,
"b64": base64.b64encode(pdf).decode("ascii"),
"ct": "application/pdf",
"size": len(pdf),
}
driver.current_window_handle = "h1"
driver.window_handles = ["h1"]
def _exec_script(script, *args):
if "window.open" in str(script):
driver.window_handles = ["h1", "h2"]
driver.execute_script.side_effect = _exec_script
out = _download_href_via_browser_fetch(driver, tmp_path, SAMPLE, set())
assert len(out) == 1
assert out[0].exists()
assert _is_pdf_bytes(out[0].read_bytes())
+55
View File
@@ -0,0 +1,55 @@
"""Tests for board column detection helpers."""
from __future__ import annotations
from unittest.mock import MagicMock
from selenium.common.exceptions import InvalidSelectorException
from onedrive_planner import (
_FIRST_COLUMN_CLUSTER_WIDTH_PX,
_element_in_board_container,
_parent_element,
)
def test_first_column_cluster_width_reasonable():
assert _FIRST_COLUMN_CLUSTER_WIDTH_PX >= 150
def test_parent_element_stops_at_html_document_error():
"""ChromeDriver rejects .. on <html> with InvalidSelectorException."""
html = MagicMock()
html.tag_name = "html"
html.find_element.side_effect = InvalidSelectorException(
'The result of the xpath expression ".." is: [object HTMLDocument].'
)
assert _parent_element(html) is None
def test_parent_element_returns_parent_for_body():
body = MagicMock()
body.tag_name = "body"
parent = MagicMock()
parent.tag_name = "html"
body.find_element.return_value = parent
assert _parent_element(body) is parent
def test_element_in_board_container_does_not_crash_at_root():
"""Containment walk must not raise when ancestors hit document root."""
child = MagicMock()
child.tag_name = "div"
container = MagicMock()
container.tag_name = "div"
html = MagicMock()
html.tag_name = "html"
html.find_element.side_effect = InvalidSelectorException("document root")
body = MagicMock()
body.tag_name = "body"
body.find_element.return_value = html
child.find_element.return_value = body
assert _element_in_board_container(child, container) is False
+147
View File
@@ -0,0 +1,147 @@
"""Unit tests for Chromium-only browser resolution."""
from __future__ import annotations
from pathlib import Path
from unittest.mock import MagicMock, patch
from chrome import (
PROJECT_ROOT,
_allow_google_chrome_fallback,
_browser_runs_in_background,
_bundled_chromedriver_for,
_google_chrome_fallback_paths,
_installed_chromium_paths,
_portable_chromium_executable,
_prefer_installed_chromium,
capture_fullscreen_screenshot,
find_chromium_executable,
)
def test_installed_paths_exclude_google():
installed = _installed_chromium_paths()
chrome_fb = _google_chrome_fallback_paths()
assert not any("Google" in p for p in installed)
assert any("Google" in p for p in chrome_fb)
def test_portable_chrome_win_preferred_over_installed():
portable = str(PROJECT_ROOT / "chrome-win" / "chrome-win" / "chrome.exe")
installed = r"C:\Program Files\Chromium\Application\chrome.exe"
def fake_isfile(path: str) -> bool:
return path in {portable, installed}
env = {"USERPROFILE": r"C:\Users\test", "LOCALAPPDATA": r"C:\Users\test\AppData\Local"}
with patch("chrome._portable_chromium_executable", return_value=portable):
with patch("chrome.os.path.isfile", side_effect=fake_isfile):
with patch.dict("os.environ", env, clear=True):
assert find_chromium_executable() == portable
def test_installed_used_when_portable_missing():
installed = r"C:\Program Files\Chromium\Application\chrome.exe"
def fake_isfile(path: str) -> bool:
return path == installed
env = {"USERPROFILE": r"C:\Users\test", "LOCALAPPDATA": r"C:\Users\test\AppData\Local"}
with patch("chrome._portable_chromium_executable", return_value=None):
with patch("chrome.os.path.isfile", side_effect=fake_isfile):
with patch.dict("os.environ", env, clear=True):
assert find_chromium_executable() == installed
def test_misf_use_installed_chrome_skips_portable():
portable = str(PROJECT_ROOT / "chrome-win" / "chrome-win" / "chrome.exe")
installed = r"C:\Program Files\Chromium\Application\chrome.exe"
def fake_isfile(path: str) -> bool:
return path in {portable, installed}
env = {
"MISF_USE_INSTALLED_CHROME": "1",
"USERPROFILE": r"C:\Users\test",
"LOCALAPPDATA": r"C:\Users\test\AppData\Local",
}
with patch("chrome._portable_chromium_executable", return_value=portable):
with patch("chrome.os.path.isfile", side_effect=fake_isfile):
with patch.dict("os.environ", env, clear=True):
assert find_chromium_executable() == installed
def test_find_chromium_can_use_google_with_allow_flag():
google_path = r"C:\Program Files\Google\Chrome\Application\chrome.exe"
env = {
"MISF_ALLOW_CHROME": "1",
"USERPROFILE": r"C:\Users\test",
"LOCALAPPDATA": r"C:\Users\test\AppData\Local",
}
with patch("chrome._portable_chromium_executable", return_value=None):
with patch("chrome.os.path.isfile", side_effect=lambda p: p == google_path):
with patch.dict("os.environ", env, clear=True):
assert find_chromium_executable() == google_path
def test_bundled_chromedriver_next_to_portable_chrome():
browser = PROJECT_ROOT / "chrome-win" / "chrome-win" / "chrome.exe"
if not browser.is_file():
return
driver = _bundled_chromedriver_for(str(browser))
assert driver is not None
assert driver.endswith("chromedriver.exe")
def test_portable_chromium_executable_on_disk():
exe = _portable_chromium_executable()
expected = PROJECT_ROOT / "chrome-win" / "chrome-win" / "chrome.exe"
if expected.is_file():
assert exe == str(expected)
def test_foreground_default_background_opt_in():
with patch.dict("os.environ", {}, clear=True):
assert _browser_runs_in_background() is False
with patch.dict("os.environ", {"MISF_BROWSER_BACKGROUND": "1"}, clear=True):
assert _browser_runs_in_background() is True
def test_allow_chrome_flag():
with patch.dict("os.environ", {}, clear=True):
assert _allow_google_chrome_fallback() is False
with patch.dict("os.environ", {"MISF_ALLOW_CHROME": "yes"}, clear=True):
assert _allow_google_chrome_fallback() is True
def test_prefer_installed_flag():
with patch.dict("os.environ", {}, clear=True):
assert _prefer_installed_chromium() is False
with patch.dict("os.environ", {"MISF_USE_INSTALLED_CHROME": "1"}, clear=True):
assert _prefer_installed_chromium() is True
def test_fullscreen_screenshot_maximizes_and_uses_desktop_when_not_black(tmp_path):
driver = MagicMock()
shot = tmp_path / "shot.png"
shot.write_bytes(b"\xff" * 100)
with patch.dict("os.environ", {}, clear=True):
with patch("chrome.capture_desktop_screenshot", return_value=True) as cap:
with patch("chrome._is_image_mostly_black", return_value=False):
assert capture_fullscreen_screenshot(driver, shot) is True
cap.assert_called_once_with(shot)
driver.maximize_window.assert_called()
def test_fullscreen_screenshot_falls_back_when_desktop_is_black(tmp_path):
driver = MagicMock()
shot = tmp_path / "shot.png"
shot.write_bytes(b"\x00" * 100)
with patch.dict("os.environ", {}, clear=True):
with patch("chrome.capture_desktop_screenshot", return_value=True):
with patch("chrome._is_image_mostly_black", return_value=True):
with patch("chrome._capture_browser_with_timestamp_bar", return_value=True) as fb:
assert capture_fullscreen_screenshot(driver, shot) is True
fb.assert_called_once()
+48
View File
@@ -0,0 +1,48 @@
"""Config and facility list tests (no browser)."""
from __future__ import annotations
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parent.parent
SRC = ROOT / "src"
sys.path.insert(0, str(SRC))
from config import ( # noqa: E402
read_app_config,
read_facility_list,
read_onedrive_config,
)
from onedrive_planner import _sanitize_folder_name, _xpath_literal # noqa: E402
@pytest.fixture
def example_config() -> Path:
path = ROOT / "Input" / "input.xlsx.example"
assert path.exists(), f"Missing template: {path}"
return path
def test_helpers() -> None:
assert _sanitize_folder_name("Test/Facility") == "Test_Facility"
assert _xpath_literal("Plain Name") == "'Plain Name'"
assert "concat" in _xpath_literal('O\'Brien "East" Wing')
def test_read_facility_list_skips_placeholder(example_config: Path) -> None:
facilities = read_facility_list(example_config, warn_if_not_expected_count=False)
assert facilities
assert not any("REPLACE_WITH" in f for f in facilities)
def test_read_app_config_planner_url(example_config: Path) -> None:
cfg = read_app_config(example_config)
assert "planner.cloud.microsoft" in cfg.planner_url
def test_read_onedrive_config_tuple_length(example_config: Path) -> None:
row = read_onedrive_config(example_config)
assert len(row) == 10
assert "planner.cloud.microsoft" in row[9]
+36
View File
@@ -0,0 +1,36 @@
"""Smoke tests for OneDrive download config (no browser)."""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
SRC = ROOT / "src"
sys.path.insert(0, str(SRC))
from config import CONFIG_FILE, read_app_config, read_facility_list # noqa: E402
from onedrive_planner import _sanitize_folder_name, _xpath_literal # noqa: E402
def test_helpers() -> None:
assert _sanitize_folder_name("Test/Facility") == "Test_Facility"
assert _xpath_literal("Plain Name") == "'Plain Name'"
assert _xpath_literal("O'Brien Care") == '"O\'Brien Care"'
assert "concat" in _xpath_literal('O\'Brien "East" Wing')
print("OK: helper functions")
def test_config_loads() -> None:
path = CONFIG_FILE if CONFIG_FILE.exists() else ROOT / "Input" / "input.xlsx.example"
assert path.exists(), f"Missing config: {path}"
facilities = read_facility_list(path)
assert len(facilities) >= 1
cfg = read_app_config(path)
assert cfg.planner_url
print(f"OK: {len(facilities)} facilities from {path.name}")
if __name__ == "__main__":
test_helpers()
test_config_loads()
print("All smoke tests passed.")
+105
View File
@@ -0,0 +1,105 @@
"""Tests for download summary Excel columns."""
from __future__ import annotations
from pathlib import Path
import pandas as pd
from planner_download import (
_build_workable_files,
_format_status_changed,
_success_status,
_write_download_summary,
)
def test_success_status_labels():
assert _success_status(0, 0) == "No tasks"
assert _success_status(0, 3, tasks_skipped_no_attachment=3) == "No attachments"
assert _success_status(1, 3, tasks_with_downloads=1, tasks_with_attachments=1) == "Success"
assert _success_status(2, 4, tasks_with_downloads=2, tasks_with_attachments=2) == "Success"
assert _success_status(1, 4, tasks_with_downloads=1, tasks_with_attachments=2) == "Partial"
assert _success_status(0, 2, tasks_with_attachments=2) == "Download failed"
def test_format_status_changed():
assert _format_status_changed(0, 0) == "N/A"
assert _format_status_changed(2, 2, via_task_details=2) == "Yes - 2/2 tasks (2 via task details)"
assert _format_status_changed(3, 1, via_task_details=1) == "Partial - 1/3 tasks (1 via task details)"
assert _format_status_changed(1, 0) == "No - 0/1 tasks"
assert _format_status_changed(2, 1, via_task_details=1) == "Partial - 1/2 tasks (1 via task details)"
def test_write_download_summary_columns(tmp_path: Path, monkeypatch):
monkeypatch.setattr("planner_download.PROJECT_ROOT", tmp_path)
results = {
"Alpha": {
"original": [tmp_path / "a.pdf", tmp_path / "b.pdf"],
"tasks_total": 4,
"tasks_with_attachments": 2,
"tasks_skipped_no_attachment": 2,
"tasks_with_downloads": 2,
"status_changed": 1,
"status_via_task_details": 1,
},
"Beta": {
"original": [],
"tasks_total": 2,
"tasks_with_attachments": 0,
"tasks_skipped_no_attachment": 2,
"tasks_with_downloads": 0,
"status_changed": 0,
"status_via_task_details": 0,
},
}
failures = [("Gamma", "not found", "Plan not found")]
summary_path = tmp_path / "Summary" / "Download_Summary_2026-06-05.xlsx"
_write_download_summary(
results,
failures,
summary_path,
planner_url="https://example.com",
run_timestamp="2026-06-05 12:00:00",
)
df = pd.read_excel(summary_path, keep_default_na=False)
assert list(df.columns) == [
"Run Timestamp",
"Facility Name",
"Files Downloaded",
"Status",
"Status Changed",
]
alpha = df[df["Facility Name"] == "Alpha"].iloc[0]
assert alpha["Status"] == "Success"
assert "1/2 tasks" in alpha["Status Changed"]
assert "via task details" in alpha["Status Changed"]
beta = df[df["Facility Name"] == "Beta"].iloc[0]
assert beta["Status"] == "No attachments"
assert beta["Status Changed"] == "N/A"
gamma = df[df["Facility Name"] == "Gamma"].iloc[0]
assert gamma["Status"] == "Plan not found"
assert gamma["Status Changed"] == "N/A"
def test_build_workable_files_mirrors_to_medicare(tmp_path: Path):
src_dir = tmp_path / "original" / "FacA"
src_dir.mkdir(parents=True)
pdf = src_dir / "doc.pdf"
pdf.write_bytes(b"%PDF-1.4")
paths, workable_root = _build_workable_files(
facility_name="Fac A",
downloaded=[pdf],
output_root=tmp_path / "Output",
today_iso="2026-06-16",
)
medicare_root = tmp_path / "Output" / "2026-06-16" / "Workable_Medicare"
assert len(paths) == 1
assert paths[0].parent == workable_root
assert paths[0].is_file()
assert medicare_root.is_dir()
medicare_files = list(medicare_root.glob("*.pdf"))
assert len(medicare_files) == 1
assert medicare_files[0].name == paths[0].name
assert medicare_files[0].read_bytes() == pdf.read_bytes()
+55
View File
@@ -0,0 +1,55 @@
"""Unit tests for Shared plans facility search helpers."""
from __future__ import annotations
from onedrive_planner import (
FACILITY_NOT_APPEAR_MSG,
_facility_search_terms,
_row_matches_facility_search,
)
def test_facility_search_terms_full_then_short():
terms = _facility_search_terms("Golden Hill Post Acute Care")
assert terms == [
"Golden Hill Post Acute Care",
"Golden Hill Post",
"Golden Hill",
]
def test_facility_search_terms_two_words_only():
terms = _facility_search_terms("Arbor Hills")
assert terms == ["Arbor Hills"]
def test_facility_search_terms_dedupes():
terms = _facility_search_terms("Golden Hill")
assert terms == ["Golden Hill"]
def test_row_matches_full_name_in_plan_title():
assert _row_matches_facility_search(
"Golden Hill Subacute and Rehab",
"Golden Hill Post Acute Care",
"Golden Hill",
)
def test_row_matches_exact():
assert _row_matches_facility_search(
"Arbor Hills",
"Arbor Hills",
"Arbor Hills",
)
def test_row_matches_rejects_unrelated():
assert not _row_matches_facility_search(
"Sunrise Manor",
"Golden Hill Post Acute Care",
"Golden Hill",
)
def test_not_appear_message_constant():
assert "shared plans" in FACILITY_NOT_APPEAR_MSG.lower()
+202
View File
@@ -0,0 +1,202 @@
"""Unit tests for Planner Grid view helpers."""
from __future__ import annotations
from unittest.mock import MagicMock, patch
from selenium.webdriver.common.by import By
from onedrive_planner import (
_clean_grid_task_title,
_grid_row_status,
_grid_row_task_name,
_is_attachments_empty,
_is_panel_placeholder_text,
_is_status_sorted_descending,
_parse_status_blob,
_sort_descending_menu_selected,
)
def _grid_row(*, status_value: str = "", status_text: str = "", task_name: str = ""):
row = MagicMock()
row.is_displayed.return_value = True
row.text = task_name
status_btn = MagicMock()
status_btn.is_displayed.return_value = True
status_btn.get_attribute.side_effect = lambda name: (
status_value if name == "value" else ""
)
status_btn.text = status_text
name_cell = MagicMock()
name_cell.is_displayed.return_value = True
name_cell.get_attribute.return_value = f"Task Name {task_name}"
name_span = MagicMock()
name_span.is_displayed.return_value = True
name_span.text = task_name
def find_elements(by, xpath):
xp = xpath or ""
if "combobox" in xp:
return [status_btn]
if "gridcell" in xp and "span" not in xp:
return [name_cell]
if "span" in xp:
return [name_span]
return []
row.find_elements.side_effect = find_elements
name_cell.find_elements.side_effect = lambda by, xpath: [name_span]
return row
def test_grid_row_status_not_started():
row = _grid_row(status_value="notStarted", status_text="Not started")
assert _grid_row_status(row) == "not started"
def test_grid_row_status_reads_status_column_not_bucket():
row = MagicMock()
row.is_displayed.return_value = True
bucket_btn = MagicMock()
bucket_btn.is_displayed.return_value = True
bucket_btn.get_attribute.side_effect = lambda name: "todo" if name == "value" else ""
bucket_btn.text = "To do"
status_btn = MagicMock()
status_btn.is_displayed.return_value = True
status_btn.get_attribute.side_effect = lambda name: "notStarted" if name == "value" else ""
status_btn.text = "Not started"
status_cell = MagicMock()
status_cell.is_displayed.return_value = True
status_cell.get_attribute.return_value = "Status Not started"
status_cell.text = "Not started"
status_cell.find_elements.side_effect = lambda by, xpath: (
[status_btn] if "combobox" in xpath else []
)
bucket_cell = MagicMock()
bucket_cell.is_displayed.return_value = True
bucket_cell.get_attribute.return_value = "Bucket To do"
bucket_cell.text = "To do"
bucket_cell.find_elements.side_effect = lambda by, xpath: (
[bucket_btn] if "combobox" in xpath else []
)
def find_elements(by, xpath):
xp = xpath or ""
if "gridcell" in xp:
return [status_cell, bucket_cell]
if "combobox" in xp:
return [bucket_btn, status_btn]
return []
row.find_elements.side_effect = find_elements
assert _grid_row_status(row) == "not started"
def test_grid_row_status_in_progress():
row = _grid_row(status_value="inProgress", status_text="In progress")
assert _grid_row_status(row) == "in progress"
def test_grid_row_task_name_from_aria():
row = _grid_row(task_name="Missing Insurance Info")
assert _grid_row_task_name(row) == "Missing Insurance Info"
def test_clean_grid_task_title_strips_a11y_hint():
raw = "Missing Insurance Info. Press Alt + I to open task details, or pre"
assert _clean_grid_task_title(raw) == "Missing Insurance Info"
def test_grid_row_task_name_strips_a11y_from_span():
row = MagicMock()
row.is_displayed.return_value = True
row.text = ""
name_cell = MagicMock()
name_cell.is_displayed.return_value = True
name_cell.get_attribute.return_value = ""
name_span = MagicMock()
name_span.is_displayed.return_value = True
name_span.text = "6.10 priority :). Press Alt + I to open task details"
def find_elements(by, xpath):
xp = xpath or ""
if "combobox" in xp:
return []
if "gridcell" in xp and "span" not in xp:
return [name_cell]
if "span" in xp:
return [name_span]
return []
row.find_elements.side_effect = find_elements
name_cell.find_elements.side_effect = lambda by, xpath: [name_span]
assert _grid_row_task_name(row) == "6.10 priority :)"
def test_is_panel_placeholder_text():
assert _is_panel_placeholder_text("Type a description or add notes here")
assert not _is_panel_placeholder_text("Missing Insurance Info")
def test_is_status_sorted_descending():
header = MagicMock()
header.get_attribute.side_effect = lambda name: (
"descending" if name == "aria-sort" else ""
)
assert _is_status_sorted_descending(header) is True
header.get_attribute.side_effect = lambda name: (
"ascending" if name == "aria-sort" else ""
)
assert _is_status_sorted_descending(header) is False
header.get_attribute.side_effect = lambda name: "" if name == "aria-sort" else ""
assert _is_status_sorted_descending(header) is False
def test_sort_descending_menu_selected():
driver = MagicMock()
checked_btn = MagicMock()
checked_btn.is_displayed.return_value = True
driver.find_elements.side_effect = lambda by, xpath: (
[checked_btn]
if "aria-checked='true'" in xpath
else []
)
assert _sort_descending_menu_selected(driver) is True
driver.find_elements.side_effect = lambda by, xpath: []
assert _sort_descending_menu_selected(driver) is False
def test_parse_status_blob():
assert _parse_status_blob("Not started") == "not started"
assert _parse_status_blob("Status Completed") == "completed"
assert _parse_status_blob("To do") is None
def test_is_attachments_empty_detects_empty_panel():
driver = MagicMock()
panel = MagicMock()
panel.is_displayed.return_value = True
panel.text = "Select, upload, or add a link to a file or website\nAdd attachment"
driver.find_elements.return_value = [panel]
with patch("onedrive_planner._attachment_tab_count", return_value=0), patch(
"onedrive_planner._discover_listed_attachment_names", return_value=[]
), patch("onedrive_planner._list_attachment_rows", return_value=[]):
assert _is_attachments_empty(driver) is True
def test_is_attachments_empty_false_when_tab_count():
driver = MagicMock()
with patch("onedrive_planner._attachment_tab_count", return_value=2):
assert _is_attachments_empty(driver) is False
+64
View File
@@ -0,0 +1,64 @@
"""Unit tests for Microsoft login page detection."""
from __future__ import annotations
from unittest.mock import MagicMock
from selenium.webdriver.common.by import By
from onedrive_planner import (
_email_form_visible,
_needs_microsoft_login,
_password_form_visible,
_planner_session_ready,
)
def _driver(*, url: str = "", elements: dict | None = None):
drv = MagicMock()
drv.current_url = url
elements = elements or {}
def find_elements(by, sel):
key = (by, sel)
return elements.get(key, [])
drv.find_elements.side_effect = find_elements
return drv
def _input(displayed: bool = True):
el = MagicMock()
el.is_displayed.return_value = displayed
return el
class TestLoginHelpers:
def test_password_only_login_detected(self):
drv = _driver(
url="https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
elements={
(By.CSS_SELECTOR, "input[type='password']"): [_input()],
},
)
assert _password_form_visible(drv)
assert not _email_form_visible(drv)
assert _needs_microsoft_login(drv)
assert not _planner_session_ready(drv)
def test_planner_ready_when_no_login_prompt(self):
drv = _driver(
url="https://planner.cloud.microsoft/webui/mytasks/assignedtome/view/grid",
elements={},
)
assert not _needs_microsoft_login(drv)
assert _planner_session_ready(drv)
def test_planner_url_with_password_still_needs_login(self):
drv = _driver(
url="https://planner.cloud.microsoft/webui/mytasks/assignedtome/view/grid",
elements={
(By.CSS_SELECTOR, "input[type='password']"): [_input()],
},
)
assert _needs_microsoft_login(drv)
assert not _planner_session_ready(drv)
+68
View File
@@ -0,0 +1,68 @@
"""Unit tests for Planner left-nav helpers."""
from __future__ import annotations
from unittest.mock import MagicMock
from selenium.webdriver.common.by import By
from onedrive_planner import (
_left_nav_pane_collapsed,
_my_plans_nav_visible,
)
def _nav_toggle(*, expanded: str = "false", displayed: bool = True):
btn = MagicMock()
btn.is_displayed.return_value = displayed
btn.get_attribute.side_effect = lambda name: expanded if name == "aria-expanded" else ""
return btn
def _nav_link(*, displayed: bool = True, enabled: bool = True):
el = MagicMock()
el.is_displayed.return_value = displayed
el.is_enabled.return_value = enabled
return el
def _mock_driver(*, elements: dict):
drv = MagicMock()
drv.find_elements.side_effect = lambda by, sel: elements.get((by, sel), [])
return drv
class TestNavHelpers:
def test_collapsed_when_aria_expanded_false(self):
drv = _mock_driver(
elements={
(By.XPATH, "//button[@aria-label='Left navigation pane']"): [
_nav_toggle(expanded="false")
],
}
)
assert _left_nav_pane_collapsed(drv)
def test_not_collapsed_when_aria_expanded_true(self):
drv = _mock_driver(
elements={
(By.XPATH, "//button[@aria-label='Left navigation pane']"): [
_nav_toggle(expanded="true")
],
}
)
assert not _left_nav_pane_collapsed(drv)
def test_my_plans_visible(self):
drv = _mock_driver(
elements={
(
By.XPATH,
"//*[self::span or self::div][normalize-space()='My plans']",
): [_nav_link()],
}
)
assert _my_plans_nav_visible(drv)
def test_my_plans_hidden(self):
drv = _mock_driver(elements={})
assert not _my_plans_nav_visible(drv)
+23
View File
@@ -0,0 +1,23 @@
"""Tests for Planner tab URL classification."""
from __future__ import annotations
from onedrive_planner import _is_facility_plan_url, _is_planner_hub_url
def test_facility_plan_url():
assert _is_facility_plan_url(
"https://planner.cloud.microsoft/webui/plan/PID_ABC/view/board?tid=x"
)
assert not _is_facility_plan_url(
"https://planner.cloud.microsoft/webui/mytasks/assignedtome/view/grid?tid=x"
)
def test_planner_hub_url():
assert _is_planner_hub_url(
"https://planner.cloud.microsoft/webui/mytasks/assignedtome/view/grid"
)
assert not _is_planner_hub_url(
"https://planner.cloud.microsoft/webui/plan/PID_ABC/view/board"
)
assert not _is_planner_hub_url("about:blank")
+13
View File
@@ -0,0 +1,13 @@
"""Tests for step logging helper."""
from __future__ import annotations
import logging
from run_log import log_step
def test_log_step_formats_message(caplog):
caplog.set_level(logging.INFO, logger="misf.run")
log_step("Open facility", facility="Amaya", rows=2)
assert any("[STEP] Open facility" in r.message for r in caplog.records)
assert any("facility=Amaya" in r.message for r in caplog.records)
+306
View File
@@ -0,0 +1,306 @@
"""Unit tests for board task filtering (not started + attachment)."""
from __future__ import annotations
from unittest.mock import MagicMock
import pytest
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By
from onedrive_planner import (
_card_has_attachment,
_is_active_bucket_title,
_is_completed_tasks_header,
_is_in_active_planner_column_group,
_is_in_completed_tasks_section,
_is_probable_single_task_card,
_qualifies_not_started_with_attachment,
_task_card_matches_title,
_task_has_unchecked_complete_checkbox,
_task_is_in_progress,
_task_is_not_started,
)
def _card(
*,
text: str = "",
aria: str = "",
attachment_child: bool = False,
checkbox_checked: bool = False,
progress_status: str | None = None,
):
el = MagicMock()
el.tag_name = "div"
el.text = text
el.is_displayed.return_value = True
el.get_attribute.return_value = aria
textbox = MagicMock()
textbox.is_displayed.return_value = True
textbox.text = text
textbox.get_attribute.return_value = ""
attach = MagicMock()
attach.is_displayed.return_value = True
attach.text = ""
attach.get_attribute.return_value = "1 attachment"
checkbox = MagicMock()
checkbox.is_displayed.return_value = True
checkbox.text = ""
checkbox.get_attribute.side_effect = lambda name: (
"true" if checkbox_checked else "false"
if name == "aria-checked"
else "Complete task"
if name == "aria-label"
else ""
)
progress_btn = None
if progress_status == "in_progress":
progress_btn = MagicMock()
progress_btn.is_displayed.return_value = True
progress_btn.get_attribute.side_effect = lambda name: (
"In progress" if name == "aria-label" else ""
)
elif progress_status == "not_started":
progress_btn = MagicMock()
progress_btn.is_displayed.return_value = True
progress_btn.get_attribute.side_effect = lambda name: (
"Not started" if name == "aria-label" else ""
)
def find_element(by, xpath):
if xpath == "./..":
raise NoSuchElementException
raise NoSuchElementException
el.find_element.side_effect = find_element
def _find_elements(by, xpath):
xp = xpath or ""
if xpath == "./..":
raise NoSuchElementException
if "textbox" in xp:
return [textbox] if text else []
if "attachment-indicator" in xp:
return [attach] if attachment_child else []
if "role='checkbox'" in xp or '@role="checkbox"' in xp:
if "@aria-checked='true'" in xp or 'aria-checked="true"' in xp:
return [checkbox] if checkbox_checked else []
if "@aria-checked='false'" in xp or 'aria-checked="false"' in xp:
return [] if checkbox_checked else [checkbox]
if "aria-checked='false'" in xp and "|" in xp:
return [] if checkbox_checked else [checkbox]
return [checkbox]
if progress_btn is not None and "button" in xp and "aria-label" in xp:
if progress_status == "in_progress" and "In progress" in xp:
return [progress_btn]
if progress_status == "not_started" and "Not started" in xp:
return [progress_btn]
return []
if "Attachment" in xp or "Attach" in xp or "attachment" in xp:
return [attach] if attachment_child else []
if "completed tasks" in xp.lower():
return []
return []
el.find_elements.side_effect = _find_elements
return el
class TestTaskFilter:
def test_not_started_with_paperclip_no_label_text(self):
card = _card(text="5.29.26", aria="Task card", attachment_child=True)
assert _card_has_attachment(card)
assert _task_is_not_started(card)
assert not _task_is_in_progress(card)
def test_skips_in_progress(self):
card = _card(text="Patient", aria="In progress, 1 attachment", attachment_child=True)
assert _task_is_in_progress(card)
assert not _task_is_not_started(card)
def test_skips_half_blue_in_progress_button(self):
"""Planner half-blue ring: button aria-label='In progress' (not the complete checkbox)."""
card = _card(
text="Abaite, Stephen 1/30/54-Need Insurance",
attachment_child=True,
progress_status="in_progress",
)
from onedrive_planner import _task_has_in_progress_status_button
assert _task_has_in_progress_status_button(card)
assert _task_is_in_progress(card)
assert not _task_is_not_started(card)
assert not _qualifies_not_started_with_attachment(card, from_indicator=True)
def test_processes_not_started_without_progress_button(self):
card = _card(
text="Richardson, Steve 1/21/56-Need Insurance",
attachment_child=True,
)
from onedrive_planner import _task_has_in_progress_status_button
assert not _task_has_in_progress_status_button(card)
assert _task_is_not_started(card)
assert _qualifies_not_started_with_attachment(card)
def test_skips_mixed_checkbox_in_progress(self):
card = _card(text="6.1.26", attachment_child=True)
checkbox = card.find_elements(By.XPATH, ".//label[@role='checkbox']")[0]
checkbox.get_attribute.side_effect = lambda name: (
"mixed"
if name == "aria-checked"
else "Complete task"
if name == "aria-label"
else ""
)
from onedrive_planner import _task_is_in_progress_scoped
assert _task_is_in_progress_scoped(card)
assert not _task_is_not_started(card)
def test_skips_completed(self):
card = _card(text="Done", aria="Completed", attachment_child=True, checkbox_checked=True)
assert not _task_is_not_started(card)
def test_checkbox_unchecked_is_not_started(self):
card = _card(text="5.29.26", attachment_child=True, checkbox_checked=False)
assert _task_has_unchecked_complete_checkbox(card)
assert _task_is_not_started(card)
def test_not_started_progress_ring_checkbox_qualifies(self):
"""Planner progress ring uses role=checkbox aria-label='Not started'."""
card = _card(text="6.3.26", attachment_child=True, progress_status="not_started")
checkbox = card.find_elements(By.XPATH, ".//label[@role='checkbox']")[0]
checkbox.get_attribute.side_effect = lambda name: (
"false"
if name == "aria-checked"
else "Not started"
if name == "aria-label"
else ""
)
from onedrive_planner import _filter_reject_reason, _task_is_strictly_not_started
assert _task_has_unchecked_complete_checkbox(card)
assert _task_is_strictly_not_started(card)
assert _filter_reject_reason(card, from_indicator=True) is None
assert _qualifies_not_started_with_attachment(card, from_indicator=True)
def test_planner_button_complete_checkbox_is_not_started(self):
"""Planner 2024+ uses button[role=checkbox] aria-label='Complete task'."""
card = _card(text="6.8.26", attachment_child=True, checkbox_checked=False)
checkbox = card.find_elements(
By.XPATH,
".//label[@role='checkbox'] | .//button[@role='checkbox']",
)[0]
checkbox.get_attribute.side_effect = lambda name: (
"false"
if name == "aria-checked"
else "Complete task"
if name == "aria-label"
else ""
)
from onedrive_planner import _filter_reject_reason, _task_has_unchecked_complete_checkbox
assert _task_has_unchecked_complete_checkbox(card)
assert _filter_reject_reason(card, from_indicator=True) is None
def test_in_progress_rejected_on_board_without_opening(self):
"""Half-blue In progress ring on the card — skip at board scan."""
card = _card(
text="Endersby, Mary",
attachment_child=True,
progress_status="in_progress",
)
from onedrive_planner import (
_filter_reject_reason,
_task_card_in_progress_on_board,
_task_is_strictly_not_started,
)
assert _task_card_in_progress_on_board(card)
assert not _task_is_strictly_not_started(card)
assert _filter_reject_reason(card, from_indicator=True) == "in_progress"
assert _filter_reject_reason(card, from_indicator=False) == "in_progress"
assert not _qualifies_not_started_with_attachment(card, from_indicator=True)
def test_requires_attachment(self):
card = _card(text="5.29.26", aria="Not started")
assert not _card_has_attachment(card)
def test_completed_header_matcher(self):
assert _is_completed_tasks_header("Completed tasks")
assert _is_completed_tasks_header("Completed tasks 37")
assert not _is_completed_tasks_header("To do 5.29.26 Completed tasks section")
def test_indicator_qualifies_without_board_completed_noise(self):
ind = _card(text="6.3.26", aria="1 attachment", attachment_child=True)
assert _is_probable_single_task_card(ind)
assert _qualifies_not_started_with_attachment(ind, from_indicator=True)
def test_unbucketed_active_group_not_completed_section(self):
"""Tasks in taskBoardColumnGroup-unbucketed must not match completed subsection."""
from selenium.webdriver.common.by import By
indicator = MagicMock()
active_group = MagicMock()
active_group.get_attribute.side_effect = lambda name: (
"taskBoardColumnGroup-unbucketed" if name == "data-testid" else ""
)
indicator.get_attribute.return_value = ""
def _find_elements(by, xp):
if by == By.XPATH and "taskBoardColumnGroup-" in xp:
if "contains(@data-testid,'-completed')" in xp:
return []
return [active_group]
return []
indicator.find_elements.side_effect = _find_elements
assert _is_in_active_planner_column_group(indicator)
assert not _is_in_completed_tasks_section(indicator)
def test_active_bucket_titles(self):
assert _is_active_bucket_title("To do")
assert _is_active_bucket_title("TO DO")
assert _is_active_bucket_title("New Facesheets")
assert _is_active_bucket_title("New Facesheets 12")
assert _is_active_bucket_title("New Facesheet")
assert _is_active_bucket_title("Heather gets facesheets")
assert _is_active_bucket_title("No bucket")
assert not _is_active_bucket_title("Completed tasks")
assert not _is_active_bucket_title("Completed tasks 875")
def test_bucket_column_detected(self):
col = _card(
text="To do",
aria="To do, Use tab key to access a task card, left and right arrow",
attachment_child=True,
)
from onedrive_planner import _is_planner_bucket_column
assert _is_planner_bucket_column(col)
def test_weak_title_key_ignores_paperclip_count(self):
from onedrive_planner import _is_weak_task_title_key
assert _is_weak_task_title_key("3")
assert _is_weak_task_title_key("")
assert not _is_weak_task_title_key("6.3.26 Missing Insurance Info")
def test_board_wrapper_not_single_task_card(self):
board = _card(text="To do\n6.3.26\nCompleted tasks 37", attachment_child=True)
board.find_elements.side_effect = lambda by, xpath: (
[MagicMock(is_displayed=lambda: True)] * 3
if "attachment-indicator" in xpath
else [MagicMock(is_displayed=lambda: True)] * 3
if "checkbox" in xpath
else []
)
assert not _is_probable_single_task_card(board)
def test_task_card_matches_title(self):
card = _card(text="5.29.26\n1 attachment", attachment_child=True)
assert _task_card_matches_title(card, "5.29.26")
assert not _task_card_matches_title(card, "Alvarado, Shante")
+99
View File
@@ -0,0 +1,99 @@
"""Unit tests for task-details panel status detection (scoped to panel, not board)."""
from __future__ import annotations
from unittest.mock import MagicMock
from selenium.webdriver.common.by import By
from onedrive_planner import (
_panel_progress_status_label,
_task_details_shows_in_progress,
_task_details_shows_not_started,
)
def _panel(*, status_aria: str):
panel = MagicMock()
panel.is_displayed.return_value = True
status_btn = MagicMock()
status_btn.is_displayed.return_value = True
status_btn.get_attribute.side_effect = lambda name: (
status_aria if name == "aria-label" else ""
)
title_box = MagicMock()
title_box.is_displayed.return_value = True
title_box.text = "6.5.26"
def find_elements(by, xpath):
xp = xpath or ""
if "Not started" in xp or "In progress" in xp or "Completed" in xp:
return [status_btn]
if "textbox" in xp:
return [title_box]
if "Status" in xp:
return []
return []
panel.find_elements.side_effect = find_elements
return panel
def _driver_with_panel(panel, *, board_in_progress: bool = False):
drv = MagicMock()
board_btn = MagicMock()
board_btn.is_displayed.return_value = board_in_progress
board_btn.get_attribute.side_effect = lambda name: (
"In progress" if name == "aria-label" else ""
)
def find_elements(by, xpath):
xp = xpath or ""
if "@role='dialog'" in xp or "TaskDetails" in xp or "Close task" in xp:
return [panel]
if "In progress" in xp and xp.startswith("//button"):
return [board_btn] if board_in_progress else []
if "Close" in xp:
return []
return []
drv.find_elements.side_effect = find_elements
return drv
class TestTaskPanelStatus:
def test_panel_not_started(self):
panel = _panel(status_aria="Not started")
assert _panel_progress_status_label(panel) == "not started"
def test_panel_in_progress(self):
panel = _panel(status_aria="In progress")
assert _panel_progress_status_label(panel) == "in progress"
def test_details_not_started_ignores_board_buttons(self):
panel = _panel(status_aria="Not started")
drv = _driver_with_panel(panel, board_in_progress=True)
assert _task_details_shows_not_started(drv)
assert not _task_details_shows_in_progress(drv)
def test_details_in_progress_from_panel_only(self):
panel = _panel(status_aria="In progress")
drv = _driver_with_panel(panel, board_in_progress=False)
assert _task_details_shows_in_progress(drv)
assert not _task_details_shows_not_started(drv)
def test_panel_in_progress_from_combobox_value(self):
panel = MagicMock()
panel.is_displayed.return_value = True
combo = MagicMock()
combo.is_displayed.return_value = True
combo.get_attribute.side_effect = lambda name: "inProgress" if name == "value" else ""
combo.text = ""
def find_elements(by, xpath):
xp = xpath or ""
if "combobox" in xp or "ComboBox" in xp:
return [combo]
return []
panel.find_elements.side_effect = find_elements
assert _panel_progress_status_label(panel) == "in progress"
+29
View File
@@ -0,0 +1,29 @@
"""Unit tests for TOTP secret sanitization."""
from __future__ import annotations
import sys
from pathlib import Path
import pytest
SRC = Path(__file__).resolve().parent.parent / "src"
sys.path.insert(0, str(SRC))
from totp_utils import sanitize_totp_secret, validate_totp_secret # noqa: E402
def test_sanitize_strips_separators() -> None:
assert sanitize_totp_secret("jbsw y3dp-ehpk3pxp") == "JBSWY3DPEHPK3PXP"
def test_sanitize_maps_zero_one() -> None:
assert sanitize_totp_secret("JBSW03DP") == "JBSWO3DP"
def test_validate_rejects_invalid_chars() -> None:
with pytest.raises(ValueError, match="not valid Base32"):
validate_totp_secret("not-valid-secret!")
def test_validate_accepts_standard_secret() -> None:
assert validate_totp_secret("JBSWY3DPEHPK3PXP") == "JBSWY3DPEHPK3PXP"
+149
View File
@@ -0,0 +1,149 @@
"""UiPath migration helper tests and pilot output structure checks."""
from __future__ import annotations
import json
from datetime import datetime
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parent.parent
UIPATH = ROOT / "uipath" / "MISF_Planner_Download"
HELPERS_CS = UIPATH / "Helpers"
def _read_cs(name: str) -> str:
return (HELPERS_CS / name).read_text(encoding="utf-8")
class TestUiPathProjectStructure:
def test_project_json_exists(self):
assert (UIPATH / "project.json").is_file()
def test_main_workflows_exist(self):
for name in (
"Main.xaml",
"01_ReadConfig.xaml",
"02_LoginPlanner.xaml",
"03_ProcessFacility.xaml",
"04_DownloadTaskAttachments.xaml",
"05_SetTaskInProgress.xaml",
"06_WriteSummary.xaml",
):
assert (UIPATH / name).is_file(), f"missing {name}"
def test_helpers_exist(self):
assert (UIPATH / "Helpers" / "GenerateTotp.xaml").is_file()
assert (UIPATH / "Helpers" / "SanitizeFolderName.xaml").is_file()
assert (UIPATH / "Helpers" / "TotpGenerator.cs").is_file()
def test_selectors_documented(self):
assert (UIPATH / "Selectors" / "PlannerSelectors.json").is_file()
assert (UIPATH / "Selectors" / "IFRAME_SETUP.md").is_file()
data = json.loads((UIPATH / "Selectors" / "PlannerSelectors.json").read_text())
assert "planner_iframe" in data
assert "my_plans" in data["planner_iframe"]
class TestHelperLogicMirrorsPython:
def test_sanitize_folder_name(self):
src = _read_cs("MisfHelpers.cs")
assert "SanitizeFolderName" in src
assert "InvalidFolderChars" in src
def test_totp_sanitize_matches_python(self):
src = _read_cs("MisfHelpers.cs")
assert 'Replace("0", "O")' in src
assert 'Replace("1", "I")' in src
def test_download_url_transform(self):
src = _read_cs("MisfHelpers.cs")
assert "web=1" in src
assert "download=1" in src
def test_default_planner_url(self):
src = _read_cs("MisfConstants.cs")
assert "planner.cloud.microsoft" in src
assert "0c15ad2c-19dd-4ded-9cd1-20d344386414" in src
class TestPilotOutputLayout:
"""Compare output folder layout with Python bot expectations."""
@pytest.fixture
def today_iso(self) -> str:
return datetime.now().strftime("%Y-%m-%d")
def test_output_directories_can_be_created(self, today_iso: str, tmp_path: Path):
original = tmp_path / "Output" / today_iso / "Original" / "Test_Facility"
workable = tmp_path / "Output" / today_iso / "Workable"
summary = tmp_path / "Output" / today_iso / "Summary"
for d in (original, workable, summary):
d.mkdir(parents=True, exist_ok=True)
assert original.is_dir()
assert workable.is_dir()
assert summary.is_dir()
def test_checkpoint_json_shape(self, tmp_path: Path):
ckpt = {
"last_completed_index": 2,
"last_facility": "Amaya",
"completed_facilities": ["Amaya", "Brighton Place San Diego"],
}
path = tmp_path / "checkpoint.json"
path.write_text(json.dumps(ckpt), encoding="utf-8")
loaded = json.loads(path.read_text())
assert loaded["last_completed_index"] == 2
assert isinstance(loaded["completed_facilities"], list)
class TestMainXamlExpressions:
def test_main_has_facility_limit_argument(self):
main = (UIPATH / "Main.xaml").read_text(encoding="utf-8")
assert "in_FacilityLimit" in main
assert "FilterFacilities" in main
def test_login_invokes_generate_totp(self):
login = (UIPATH / "02_LoginPlanner.xaml").read_text(encoding="utf-8")
assert "GenerateTotp.xaml" in login
assert "loginfmt" in login
def test_iframe_prefix_in_process_facility(self):
proc = (UIPATH / "03_ProcessFacility.xaml").read_text(encoding="utf-8")
assert "IFRAME" in proc
assert "My plans" in proc
def test_attachments_tab_download_flow(self):
py = (ROOT / "src" / "onedrive_planner.py").read_text(encoding="utf-8")
assert "_open_attachments_view" in py
assert "_ensure_board_scan_context" in py
assert "attachment-indicator" in py
assert "_card_has_attachment" in py
assert "_task_is_not_started" in py
assert "_open_task_via_attachment" in py
assert "_click_exact_attachment_name" in py
assert "_collect_attachment_hrefs" in py
assert "_collect_attachment_download_urls" in py
assert "_download_href_via_http" in py
assert "_download_attachment_row" in py
assert "_list_attachment_rows" in py
assert "_close_extra_download_tabs" in py
assert "_download_href_with_fallback" in py
assert "_download_one_href_browser_new_tab" in py
assert "HTTP only last" in py or "HTTP last" in py
assert "configure_http_download_proxy" in py
assert "_url_from_attachment_element" in py
assert "_scroll_attachments_panel" in py
dl = (UIPATH / "04_DownloadTaskAttachments.xaml").read_text(encoding="utf-8")
assert "Attachments" in dl
assert "sharepoint" in dl.lower()
def test_in_progress_task_details_only(self):
proc = (UIPATH / "05_SetTaskInProgress.xaml").read_text(encoding="utf-8")
assert "Task details" in proc
assert "In progress" in proc
py = (ROOT / "src" / "onedrive_planner.py").read_text(encoding="utf-8")
assert "_set_task_in_progress_from_task_details" in py
assert "_mark_task_in_progress" in py
assert "task details only" in py
assert "_set_task_in_progress_from_board(driver, card" not in py