feat: step_integrations module with validate() + tier-filtered available list
This commit is contained in:
parent
eb0e7883b8
commit
6b093522bf
2 changed files with 68 additions and 0 deletions
36
app/wizard/step_integrations.py
Normal file
36
app/wizard/step_integrations.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
"""Step 7 — Optional integrations (cloud storage, calendars, notifications).
|
||||
|
||||
This step is never mandatory — validate() always returns [].
|
||||
Helper functions support the wizard UI for tier-filtered integration cards.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def validate(data: dict) -> list[str]:
|
||||
"""Integrations step is optional — never blocks Finish."""
|
||||
return []
|
||||
|
||||
|
||||
def get_available(tier: str) -> list[str]:
|
||||
"""Return list of integration names available for the given tier.
|
||||
|
||||
An integration is available if the user's tier meets or exceeds the
|
||||
integration's minimum required tier (as declared by cls.tier).
|
||||
"""
|
||||
from scripts.integrations import REGISTRY
|
||||
from app.wizard.tiers import TIERS
|
||||
|
||||
available = []
|
||||
for name, cls in REGISTRY.items():
|
||||
try:
|
||||
if TIERS.index(tier) >= TIERS.index(cls.tier):
|
||||
available.append(name)
|
||||
except ValueError:
|
||||
pass # unknown tier string — skip
|
||||
return available
|
||||
|
||||
|
||||
def is_connected(name: str, config_dir: Path) -> bool:
|
||||
"""Return True if a live config file exists for this integration."""
|
||||
return (config_dir / "integrations" / f"{name}.yaml").exists()
|
||||
|
|
@ -110,3 +110,35 @@ def test_search_missing_both():
|
|||
def test_search_none_values():
|
||||
d = {"job_titles": None, "locations": None}
|
||||
assert search_validate(d) != []
|
||||
|
||||
# ── Step Integrations ──────────────────────────────────────────────────────────
|
||||
from app.wizard.step_integrations import validate as int_validate, get_available, is_connected
|
||||
|
||||
def test_integrations_always_passes():
|
||||
assert int_validate({}) == []
|
||||
assert int_validate({"connected": ["notion", "slack"]}) == []
|
||||
|
||||
def test_get_available_free_tier_includes_free():
|
||||
available = get_available("free")
|
||||
# Free integrations must always be available
|
||||
for name in ["google_drive", "dropbox", "discord", "home_assistant"]:
|
||||
assert name in available, f"{name} should be in free tier available list"
|
||||
|
||||
def test_get_available_free_tier_excludes_paid():
|
||||
available = get_available("free")
|
||||
# Paid integrations should NOT be available on free tier
|
||||
for name in ["notion", "google_calendar", "slack"]:
|
||||
assert name not in available, f"{name} should NOT be in free tier available list"
|
||||
|
||||
def test_get_available_paid_tier_includes_paid():
|
||||
available = get_available("paid")
|
||||
for name in ["notion", "google_sheets", "airtable", "slack", "google_calendar"]:
|
||||
assert name in available, f"{name} should be in paid tier available list"
|
||||
|
||||
def test_is_connected_false_when_no_file(tmp_path):
|
||||
assert is_connected("notion", tmp_path) is False
|
||||
|
||||
def test_is_connected_true_when_file_exists(tmp_path):
|
||||
(tmp_path / "integrations").mkdir()
|
||||
(tmp_path / "integrations" / "notion.yaml").write_text("token: x\n")
|
||||
assert is_connected("notion", tmp_path) is True
|
||||
|
|
|
|||
Loading…
Reference in a new issue