fix(settings): reject fractional refresh intervals
This commit is contained in:
@@ -70,6 +70,15 @@ def settings_page(workspace_id: str) -> None:
|
|||||||
return str(int(parsed))
|
return str(int(parsed))
|
||||||
return str(parsed)
|
return str(parsed)
|
||||||
|
|
||||||
|
def as_positive_int(value: object) -> int | None:
|
||||||
|
try:
|
||||||
|
parsed = float(value)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
if parsed < 1 or not parsed.is_integer():
|
||||||
|
return None
|
||||||
|
return int(parsed)
|
||||||
|
|
||||||
def last_saved_status_text(config: PortfolioConfig) -> str:
|
def last_saved_status_text(config: PortfolioConfig) -> str:
|
||||||
return save_status_text(config).replace("Saved:", "Last saved:", 1)
|
return save_status_text(config).replace("Saved:", "Last saved:", 1)
|
||||||
|
|
||||||
@@ -78,6 +87,10 @@ def settings_page(workspace_id: str) -> None:
|
|||||||
if parsed_loan_amount is None:
|
if parsed_loan_amount is None:
|
||||||
raise ValueError("Loan amount must be zero or greater")
|
raise ValueError("Loan amount must be zero or greater")
|
||||||
|
|
||||||
|
parsed_refresh_interval = as_positive_int(refresh_interval.value)
|
||||||
|
if parsed_refresh_interval is None:
|
||||||
|
raise ValueError("Refresh interval must be a whole number of seconds")
|
||||||
|
|
||||||
return PortfolioConfig(
|
return PortfolioConfig(
|
||||||
gold_value=as_positive_float(gold_value.value),
|
gold_value=as_positive_float(gold_value.value),
|
||||||
entry_price=as_positive_float(entry_price.value),
|
entry_price=as_positive_float(entry_price.value),
|
||||||
@@ -89,7 +102,7 @@ def settings_page(workspace_id: str) -> None:
|
|||||||
ltv_warning=float(ltv_warning.value),
|
ltv_warning=float(ltv_warning.value),
|
||||||
primary_source=str(primary_source.value),
|
primary_source=str(primary_source.value),
|
||||||
fallback_source=str(fallback_source.value),
|
fallback_source=str(fallback_source.value),
|
||||||
refresh_interval=int(refresh_interval.value),
|
refresh_interval=parsed_refresh_interval,
|
||||||
volatility_spike=float(vol_alert.value),
|
volatility_spike=float(vol_alert.value),
|
||||||
spot_drawdown=float(price_alert.value),
|
spot_drawdown=float(price_alert.value),
|
||||||
email_alerts=bool(email_alerts.value),
|
email_alerts=bool(email_alerts.value),
|
||||||
|
|||||||
@@ -20,8 +20,15 @@ def test_settings_reject_invalid_loan_amount_without_silent_zero_fallback() -> N
|
|||||||
expect(page.locator("text=Settings").first).to_be_visible(timeout=15000)
|
expect(page.locator("text=Settings").first).to_be_visible(timeout=15000)
|
||||||
expect(page.locator("text=Last saved:").first).to_be_visible(timeout=15000)
|
expect(page.locator("text=Last saved:").first).to_be_visible(timeout=15000)
|
||||||
|
|
||||||
page.get_by_label("Refresh interval (seconds)").fill("7")
|
refresh_interval_input = page.get_by_label("Refresh interval (seconds)")
|
||||||
|
refresh_interval_input.fill("7")
|
||||||
expect(page.locator("text=Unsaved changes — Last saved:").first).to_be_visible(timeout=15000)
|
expect(page.locator("text=Unsaved changes — Last saved:").first).to_be_visible(timeout=15000)
|
||||||
|
refresh_interval_input.fill("5.5")
|
||||||
|
refresh_interval_input.press("Tab")
|
||||||
|
expect(page.locator("text=INVALID").first).to_be_visible(timeout=15000)
|
||||||
|
expect(page.locator("text=Refresh interval must be a whole number of seconds").first).to_be_visible(
|
||||||
|
timeout=15000
|
||||||
|
)
|
||||||
page.reload(wait_until="domcontentloaded", timeout=30000)
|
page.reload(wait_until="domcontentloaded", timeout=30000)
|
||||||
expect(page.get_by_label("Refresh interval (seconds)")).to_have_value("5")
|
expect(page.get_by_label("Refresh interval (seconds)")).to_have_value("5")
|
||||||
expect(page.locator("text=Last saved:").first).to_be_visible(timeout=15000)
|
expect(page.locator("text=Last saved:").first).to_be_visible(timeout=15000)
|
||||||
|
|||||||
Reference in New Issue
Block a user