Merge remote-tracking branch 'origin/dev'

This commit is contained in:
pewdiepie-archdaemon
2026-06-30 10:26:46 +00:00
34 changed files with 607 additions and 316 deletions
+2
View File
@@ -34,6 +34,8 @@ def atomic_write_json(path: str, data: Any, *, indent: Optional[int] = None) ->
def atomic_write_text(path: str, text: str) -> None:
if not isinstance(text, str):
raise TypeError("atomic_write_text expects a string")
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
tmp = f"{path}.tmp.{os.getpid()}"
with open(tmp, "w", encoding="utf-8") as f:
+15 -11
View File
@@ -38,23 +38,27 @@ def _preview_text(value, limit: int = 200) -> str:
return text[:limit]
def _text_field(value) -> str:
return value if isinstance(value, str) else ""
def _serialize_image(i: "GalleryImage") -> dict:
return {
"id": i.id,
"filename": i.filename,
"filename": _text_field(i.filename),
"prompt": _preview_text(i.prompt),
"model": i.model or "",
"size": i.size or "",
"tags": i.tags or "",
"model": _text_field(i.model),
"size": _text_field(i.size),
"tags": _text_field(i.tags),
"favorite": bool(i.favorite),
"album_id": i.album_id or "",
"session_id": i.session_id or "",
"album_id": _text_field(i.album_id),
"session_id": _text_field(i.session_id),
"width": i.width,
"height": i.height,
"file_size": i.file_size,
"taken_at": i.taken_at.isoformat() if i.taken_at else "",
"camera_make": i.camera_make or "",
"camera_model": i.camera_model or "",
"camera_make": _text_field(i.camera_make),
"camera_model": _text_field(i.camera_model),
"created_at": i.created_at.isoformat() if i.created_at else "",
}
@@ -93,11 +97,11 @@ def cmd_show(args):
if not i:
fail(f"no image with id {args.id!r}")
out = _serialize_image(i)
out["prompt_full"] = i.prompt or ""
out["ai_tags"] = i.ai_tags or ""
out["prompt_full"] = _text_field(i.prompt)
out["ai_tags"] = _text_field(i.ai_tags)
out["gps_lat"] = i.gps_lat or ""
out["gps_lng"] = i.gps_lng or ""
out["file_hash"] = i.file_hash or ""
out["file_hash"] = _text_field(i.file_hash)
emit(out, args)
finally:
db.close()
+2
View File
@@ -108,6 +108,8 @@ def _q(name: str) -> str:
def _split_recipients(value: str) -> list[str]:
if not isinstance(value, str):
return []
return [r.strip() for r in (value or "").split(",") if r.strip()]
+3 -1
View File
@@ -36,7 +36,9 @@ def _load_items(raw) -> list:
items = json.loads(raw)
except (TypeError, json.JSONDecodeError):
return []
return items if isinstance(items, list) else []
if not isinstance(items, list):
return []
return [item for item in items if isinstance(item, dict)]
def _serialize(n: "Note") -> dict:
+3
View File
@@ -103,6 +103,9 @@ def compute_serve_profiles(system, model, serve_weights_gb=None, serve_quant=Non
in the actual serving knobs (n_cpu_moe, KV-cache type, context). serve_quant
is the file's quant label (e.g. "Q4_K_M") just for display.
"""
if not isinstance(system, dict) or not isinstance(model, dict):
return []
vram = float(system.get("gpu_vram_gb") or 0)
if vram <= 0:
return []
+9 -2
View File
@@ -18,6 +18,13 @@ DEFAULT_BUDGET = 6000
DEFAULT_HEADROOM = 0.85
def _int_or_zero(value) -> int:
try:
return int(value or 0)
except (TypeError, ValueError):
return 0
def compute_input_token_budget(
configured: int,
context_length: int,
@@ -48,8 +55,8 @@ def compute_input_token_budget(
- When the window is unknown (context_length <= 0), use the conservative
``default`` budget and do NOT scale off the fallback.
"""
configured = int(configured or 0)
context_length = int(context_length or 0)
configured = _int_or_zero(configured)
context_length = _int_or_zero(context_length)
if explicit and configured > 0:
return min(configured, context_length) if context_length > 0 else configured
+15 -3
View File
@@ -345,6 +345,18 @@ def _normalize_ollama_url(url: str) -> str:
return base.rstrip("/") + "/chat"
def _normalize_openai_chat_url(url: str) -> str:
"""Ensure an OpenAI-compatible base URL points at /chat/completions."""
base = (url or "").strip().rstrip("/")
if not base:
return base
if base.endswith("/chat/completions") or base.endswith("/completions"):
return base
if base.endswith("/models"):
base = base[: -len("/models")].rstrip("/")
return base + "/chat/completions"
def _ollama_normalize_messages(messages: List[Dict]) -> List[Dict]:
"""Adapt Odysseus' canonical OpenAI-style messages to native Ollama /api/chat.
@@ -1564,7 +1576,7 @@ def llm_call(url: str, model: str, messages: List[Dict], temperature: float = LL
stream=False, num_ctx=get_context_length(url, model),
)
else:
target_url = url
target_url = _normalize_openai_chat_url(url)
if provider == "copilot":
from src.copilot import apply_request_headers
apply_request_headers(h, messages_copy)
@@ -1768,7 +1780,7 @@ async def llm_call_async(
stream=False, num_ctx=get_context_length(url, model),
)
else:
target_url = url
target_url = _normalize_openai_chat_url(url)
h = _provider_headers(provider, headers)
if provider == "copilot":
from src.copilot import apply_request_headers
@@ -1891,7 +1903,7 @@ async def stream_llm(url: str, model: str, messages: List[Dict], temperature: fl
h = _provider_headers(provider, headers)
payload = _build_chatgpt_responses_payload(model, messages_copy, temperature, max_tokens, stream=True)
else:
target_url = url
target_url = _normalize_openai_chat_url(url)
payload = {
"model": model,
"messages": messages_copy,
+4 -1
View File
@@ -68,6 +68,8 @@ def read_text_file(path: str) -> str:
def split_chunks(text: str, size: int = config.CHUNK_SIZE, overlap: int = config.CHUNK_OVERLAP) -> List[str]:
"""Split text into overlapping chunks."""
if not isinstance(text, str):
return []
text = text.strip()
if not text:
return []
@@ -87,7 +89,8 @@ def split_chunks(text: str, size: int = config.CHUNK_SIZE, overlap: int = config
def tokenize(s: str) -> Set[str]:
"""Tokenize string into words, excluding stop words."""
tokens = re.findall(r"[A-Za-z0-9_\-]+", (s or "").lower())
text = s if isinstance(s, str) else ""
tokens = re.findall(r"[A-Za-z0-9_\-]+", text.lower())
return set(t for t in tokens if t not in config.STOP_WORDS and len(t) > 1)
def load_personal_index(
+6
View File
@@ -79,12 +79,18 @@ def check_outbound_url(
if not raw_ips:
return False, "host does not resolve"
saw_ip = False
for raw in raw_ips:
if not isinstance(raw, str):
continue
try:
ip = ipaddress.ip_address(raw.split("%")[0]) # strip IPv6 zone id
except ValueError:
continue
saw_ip = True
reason = _classify(ip, block_private=block_private)
if reason:
return False, reason
if not saw_ip:
return False, "host does not resolve to an IP"
return True, "ok"
+1
View File
@@ -32,6 +32,7 @@
* @returns {HTMLCanvasElement|null}
*/
export function layerUnionAlpha(w, h, layers) {
if (!Array.isArray(layers)) return null;
const visible = layers.filter(l => l.visible);
if (visible.length < 2) return null;
const bgId = visible[0].id;
+4 -2
View File
@@ -23,8 +23,10 @@
* @returns {{x: number, y: number, guides: Array}}
*/
export function computeSnap(layer, nx, ny, ctx) {
const SNAP_PX = 6 / Math.max(ctx.zoom, 0.0001);
const cw = ctx.canvasW, ch = ctx.canvasH;
if (!layer || !layer.canvas || !ctx) return { x: nx, y: ny, guides: [] };
const zoom = Number.isFinite(Number(ctx.zoom)) ? Number(ctx.zoom) : 1;
const SNAP_PX = 6 / Math.max(zoom, 0.0001);
const cw = Number(ctx.canvasW) || 0, ch = Number(ctx.canvasH) || 0;
const w = layer.canvas.width, h = layer.canvas.height;
const vTargets = [
+7 -2
View File
@@ -15,16 +15,21 @@ def test_preview_text_ignores_non_string(monkeypatch):
assert cli._preview_text(None) == ""
assert cli._preview_text(123) == ""
assert cli._preview_text("p" * 250) == "p" * 200
assert cli._text_field("ok") == "ok"
assert cli._text_field(123) == ""
def test_serialize_image_does_not_crash_on_non_string_prompt(monkeypatch):
make_core_db_stub(monkeypatch, models=["GalleryImage", "GalleryAlbum"])
cli = load_script("odysseus-gallery")
img = SimpleNamespace(
id="i1", filename="a.png", prompt=123, model=None, size=None, tags=None,
id="i1", filename=123, prompt=123, model=123, size=None, tags=["bad"],
favorite=0, album_id=None, session_id=None, width=1, height=1, file_size=1,
taken_at=None, camera_make=None, camera_model=None, created_at=None,
taken_at=None, camera_make=123, camera_model=None, created_at=None,
)
out = cli._serialize_image(img)
assert out["prompt"] == ""
assert out["filename"] == ""
assert out["model"] == ""
assert out["tags"] == ""
assert out["id"] == "i1"
+7
View File
@@ -48,3 +48,10 @@ def test_recipient_list_rejects_empty_envelope(monkeypatch):
assert exc.code == 1
else:
raise AssertionError("expected empty recipient list to exit")
def test_split_recipients_ignores_non_string_values(monkeypatch):
cli = _load_mail_cli(monkeypatch)
assert cli._split_recipients(None) == []
assert cli._split_recipients(["a@example.test"]) == []
+22
View File
@@ -46,3 +46,25 @@ def test_serialize_keeps_list_note_items(monkeypatch):
)
assert cli._serialize(note)["items"] == [{"text": "done"}]
def test_serialize_skips_invalid_note_item_rows(monkeypatch):
make_core_db_stub(monkeypatch, models=["Note"])
cli = load_script("odysseus-notes")
note = SimpleNamespace(
id="n1",
title="Checklist",
content="",
items='[{"text": "done"}, "bad", null, 3]',
note_type="checklist",
color=None,
label=None,
pinned=False,
archived=False,
due_date=None,
source=None,
created_at=None,
updated_at=None,
)
assert cli._serialize(note)["items"] == [{"text": "done"}]
+8
View File
@@ -0,0 +1,8 @@
"""Shared imports for calendar route tests."""
def import_calendar_routes():
"""Import the calendar routes module after test stubs are installed."""
import routes.calendar_routes as cal
return cal
+10
View File
@@ -138,6 +138,16 @@ def test_atomic_write_text_leaves_no_tmp_file(tmp_path):
assert _tmp_siblings(tmp_path, "note.txt") == []
def test_atomic_write_text_rejects_non_string_before_tmp_file(tmp_path):
target = tmp_path / "note.txt"
with pytest.raises(TypeError):
atomic_write_text(str(target), 123)
assert not target.exists()
assert _tmp_siblings(tmp_path, "note.txt") == []
# ---------------------------------------------------------------------------
# atomic_write_text — failure path: target preserved when replace fails.
# ---------------------------------------------------------------------------
+4 -4
View File
@@ -13,7 +13,7 @@ The fallback now normalizes to UTC and strips tz, exactly like the ISO path.
"""
import pytest
from tests.test_null_owner_gates import _import_calendar_helpers
from tests.helpers.calendar_routes import import_calendar_routes
# Inputs datetime.fromisoformat() rejects (so they hit the dateutil fallback)
# but that carry a numeric UTC offset dateutil resolves to tz-aware.
@@ -25,7 +25,7 @@ _OFFSET_NONISO = [
@pytest.mark.parametrize("s", _OFFSET_NONISO)
def test_parse_dt_dateutil_fallback_returns_naive(s):
cal = _import_calendar_helpers()
cal = import_calendar_routes()
d = cal._parse_dt(s)
assert d.tzinfo is None, f"{s!r} leaked tz-aware: {d!r}"
# +0900 14:00 -> 05:00 UTC, naive.
@@ -34,13 +34,13 @@ def test_parse_dt_dateutil_fallback_returns_naive(s):
@pytest.mark.parametrize("s", _OFFSET_NONISO)
def test_parse_dt_pair_fallback_returns_naive(s):
cal = _import_calendar_helpers()
cal = import_calendar_routes()
dt, _is_utc = cal._parse_dt_pair(s)
assert dt.tzinfo is None, f"{s!r} leaked tz-aware via _parse_dt_pair: {dt!r}"
def test_parse_dt_naive_input_unchanged():
cal = _import_calendar_helpers()
cal = import_calendar_routes()
d = cal._parse_dt("January 5, 2026 14:00") # no offset -> stays as parsed
assert d.tzinfo is None
assert (d.hour, d.minute) == (14, 0)
+22 -22
View File
@@ -1,8 +1,8 @@
"""Regression tests for calendar recurrence expansion.
Tests _expand_rrule and _resolve_base_uid imported directly from
routes/calendar_routes using the same stub-friendly import pattern
as test_null_owner_gates.py. No live DB or FastAPI test client needed.
routes/calendar_routes using the shared stub-friendly test helper.
No live DB or FastAPI test client needed.
"""
from datetime import datetime, timedelta
@@ -10,34 +10,34 @@ from types import SimpleNamespace
import pytest
from tests.test_null_owner_gates import _import_calendar_helpers
from tests.helpers.calendar_routes import import_calendar_routes
# ── _resolve_base_uid ──────────────────────────────────────────────────
def test_resolve_base_uid_plain_passthrough():
cal = _import_calendar_helpers()
cal = import_calendar_routes()
assert cal._resolve_base_uid("evt-123") == "evt-123"
def test_resolve_base_uid_compound_strips_suffix_date():
cal = _import_calendar_helpers()
cal = import_calendar_routes()
assert cal._resolve_base_uid("evt-123::2026-06-15") == "evt-123"
def test_resolve_base_uid_compound_strips_suffix_datetime():
cal = _import_calendar_helpers()
cal = import_calendar_routes()
assert cal._resolve_base_uid("evt-123::2026-06-15T09:00") == "evt-123"
def test_resolve_base_uid_rejects_empty():
cal = _import_calendar_helpers()
cal = import_calendar_routes()
with pytest.raises(ValueError, match="empty uid"):
cal._resolve_base_uid("")
def test_resolve_base_uid_rejects_missing_base():
cal = _import_calendar_helpers()
cal = import_calendar_routes()
with pytest.raises(ValueError, match="malformed compound UID"):
cal._resolve_base_uid("::2026-06-15")
@@ -74,7 +74,7 @@ def _make_event(**overrides):
def test_expand_non_recurring_returns_single():
"""Non-recurring events pass through unchanged with series_uid=uid."""
cal = _import_calendar_helpers()
cal = import_calendar_routes()
ev = _make_event(rrule="")
results = cal._expand_rrule(ev, datetime(2026, 5, 1), datetime(2026, 7, 1))
@@ -108,7 +108,7 @@ def test_expand_yearly_old_dtstart_later_year_single_occurrence():
This is the explicit regression case from PR review feedback.
"""
cal = _import_calendar_helpers()
cal = import_calendar_routes()
ev = _make_event(
uid="evt-bday-001",
summary="Annual Review",
@@ -136,7 +136,7 @@ def test_expand_yearly_narrow_window_after_dtstart_returns_one():
"""DTSTART=2020, query just two months in 2029 — should return
exactly one occurrence (the one that falls in that window).
"""
cal = _import_calendar_helpers()
cal = import_calendar_routes()
ev = _make_event(
uid="evt-ann",
dtstart=datetime(2020, 3, 1),
@@ -155,7 +155,7 @@ def test_expand_yearly_strict_before_window_returns_empty():
"""DTSTART=2020, query a window that ends before the yearly
occurrence in that year. Should return zero.
"""
cal = _import_calendar_helpers()
cal = import_calendar_routes()
ev = _make_event(
uid="evt-late",
dtstart=datetime(2020, 12, 25),
@@ -172,7 +172,7 @@ def test_expand_yearly_strict_after_window_returns_empty():
"""DTSTART=2020. Query a window that starts after the occurrence in
that year. Should return zero.
"""
cal = _import_calendar_helpers()
cal = import_calendar_routes()
ev = _make_event(
uid="evt-early",
dtstart=datetime(2020, 1, 15),
@@ -189,7 +189,7 @@ def test_expand_weekly_unique_no_overwrites():
"""Multiple occurrences from the same series must have unique UIDs
so _allEvents[uid] = ev doesn't overwrite earlier ones.
"""
cal = _import_calendar_helpers()
cal = import_calendar_routes()
ev = _make_event(
uid="evt-wk",
dtstart=datetime(2026, 6, 1, 9, 0),
@@ -210,7 +210,7 @@ def test_expand_weekly_unique_no_overwrites():
def test_expand_monthly_all_day():
cal = _import_calendar_helpers()
cal = import_calendar_routes()
ev = _make_event(
uid="evt-rent",
dtstart=datetime(2026, 1, 1),
@@ -228,7 +228,7 @@ def test_expand_monthly_all_day():
def test_expand_bad_rrule_graceful():
"""Malformed rrule should fall back to returning the base event,
but only when the base event overlaps the requested window."""
cal = _import_calendar_helpers()
cal = import_calendar_routes()
ev = _make_event(
uid="evt-broken",
rrule="FREQ=GARBAGE",
@@ -243,7 +243,7 @@ def test_expand_bad_rrule_graceful():
def test_expand_bad_rrule_fallback_rejects_non_overlapping():
"""Malformed rrule with a base event outside the requested window
must return zero results, not leak the event into an unrelated range."""
cal = _import_calendar_helpers()
cal = import_calendar_routes()
ev = _make_event(
uid="evt-old-broken",
dtstart=datetime(2020, 1, 1, 9, 0),
@@ -261,7 +261,7 @@ def test_expand_bad_rrule_fallback_rejects_non_overlapping():
def test_expand_exclusive_end_boundary():
"""An occurrence whose start equals the window end must be excluded.
The contract is [start, end), same as the non-recurring SQL filter."""
cal = _import_calendar_helpers()
cal = import_calendar_routes()
ev = _make_event(
uid="evt-daily",
dtstart=datetime(2026, 6, 1, 9, 0),
@@ -278,7 +278,7 @@ def test_expand_exclusive_end_boundary():
def test_expand_multi_day_crossing_range_start():
"""A multi-day occurrence that starts before the window but ends inside
it must be included (matching non-recurring overlap: dtend > start)."""
cal = _import_calendar_helpers()
cal = import_calendar_routes()
ev = _make_event(
uid="evt-weekly-multi",
summary="Weekend Trip",
@@ -303,7 +303,7 @@ def test_expand_multi_day_crossing_range_start():
def test_expand_multi_day_fully_before_window():
"""A multi-day occurrence that ends exactly at the window start
must be excluded (occ_end <= start)."""
cal = _import_calendar_helpers()
cal = import_calendar_routes()
ev = _make_event(
uid="evt-multi",
dtstart=datetime(2026, 5, 29, 18, 0),
@@ -319,7 +319,7 @@ def test_expand_multi_day_fully_before_window():
def test_expand_metadata_inheritance():
"""Occurrence dicts must carry the base event's metadata
(summary, importance, event_type, color, location)."""
cal = _import_calendar_helpers()
cal = import_calendar_routes()
ev = _make_event(
uid="evt-meta",
summary="Board Meeting",
@@ -341,7 +341,7 @@ def test_expand_metadata_inheritance():
def test_expand_daily_rrule_large_window_is_capped_and_marked_truncated():
"""Wide recurring windows must not materialize unbounded occurrence lists."""
cal = _import_calendar_helpers()
cal = import_calendar_routes()
ev = _make_event(
uid="evt-daily-cap",
dtstart=datetime(2020, 1, 1, 9, 0),
+2 -2
View File
@@ -24,7 +24,7 @@ UNTIL must expand to all of its occurrences.
from datetime import datetime
from types import SimpleNamespace
from tests.test_null_owner_gates import _import_calendar_helpers
from tests.helpers.calendar_routes import import_calendar_routes
_MOCK_CAL = SimpleNamespace(name="Personal", color="#5b8abf")
@@ -55,7 +55,7 @@ def _make_event(**overrides):
def test_expand_rrule_with_utc_until_keeps_all_occurrences():
"""FREQ=DAILY;UNTIL=...Z must expand to every occurrence, not collapse
to a single non-recurring event."""
cal = _import_calendar_helpers()
cal = import_calendar_routes()
ev = _make_event(rrule="FREQ=DAILY;UNTIL=20240105T090000Z")
results = cal._expand_rrule(ev, datetime(2024, 1, 1), datetime(2024, 1, 10))
+5
View File
@@ -33,6 +33,11 @@ def test_unknown_window_falls_back_to_configured():
assert compute_input_token_budget(0, 0, explicit=False) == 6000 # default
def test_invalid_numeric_inputs_fall_back_cleanly():
assert compute_input_token_budget("bad", "also bad", explicit=False) == 6000
assert compute_input_token_budget("bad", 128000, explicit=True) == int(128000 * 0.85)
def test_is_setting_overridden_reads_raw_saved_file(tmp_path, monkeypatch):
import src.settings as settings
@@ -0,0 +1,32 @@
"""Pin harmonize mask helpers against invalid layer lists.
Driven through `node --input-type=module`; skips without node.
"""
import json
import shutil
import subprocess
from pathlib import Path
import pytest
_REPO = Path(__file__).resolve().parent.parent
_HELPER = _REPO / "static" / "js" / "editor" / "harmonize-masks.js"
_HAS_NODE = shutil.which("node") is not None
@pytest.mark.skipif(not _HAS_NODE, reason="node binary not on PATH")
def test_layer_union_alpha_returns_null_for_non_array_layers():
js = f"""
import {{ layerUnionAlpha, seamMask, layerBodyMask }} from '{_HELPER.as_posix()}';
console.log(JSON.stringify([
layerUnionAlpha(10, 10, null),
seamMask(10, 10, {{"bad": true}}),
layerBodyMask(10, 10, "bad")
]));
"""
proc = subprocess.run(
["node", "--input-type=module"],
input=js, capture_output=True, text=True, cwd=str(_REPO), timeout=30,
)
assert proc.returncode == 0, proc.stderr
assert json.loads(proc.stdout.strip()) == [None, None, None]
+4 -4
View File
@@ -1,9 +1,9 @@
"""Tests for iCalendar TEXT escaping in calendar export (RFC 5545 §3.3.11)."""
from tests.test_null_owner_gates import _import_calendar_helpers
from tests.helpers.calendar_routes import import_calendar_routes
def _esc():
return _import_calendar_helpers()._ics_escape
return import_calendar_routes()._ics_escape
def test_escapes_comma_and_semicolon():
@@ -26,7 +26,7 @@ def test_empty_and_none_safe():
def test_safe_ics_filename_strips_header_metacharacters():
safe_filename = _import_calendar_helpers()._safe_ics_filename
safe_filename = import_calendar_routes()._safe_ics_filename
assert (
safe_filename('Work\r\nX-Injected: yes";/..\\evil')
@@ -35,7 +35,7 @@ def test_safe_ics_filename_strips_header_metacharacters():
def test_safe_ics_filename_falls_back_for_empty_names():
safe_filename = _import_calendar_helpers()._safe_ics_filename
safe_filename = import_calendar_routes()._safe_ics_filename
assert safe_filename("////") == "calendar.ics"
assert safe_filename(None) == "calendar.ics"
+82
View File
@@ -9,6 +9,12 @@ def test_detects_ollama_cloud_native_provider():
assert llm_core._detect_provider("https://ollama.com/api/chat") == "ollama"
def test_detects_bare_local_ollama_as_native_provider():
assert llm_core._detect_provider("http://localhost:11434") == "ollama"
assert llm_core._detect_provider("http://127.0.0.1:11434/") == "ollama"
assert llm_core._detect_provider("http://localhost:11434/v1") == "openai"
def test_llm_call_posts_native_ollama_payload(monkeypatch):
seen = {}
@@ -43,6 +49,82 @@ def test_llm_call_posts_native_ollama_payload(monkeypatch):
assert seen["json"]["options"] == {"temperature": 0.2, "num_predict": 7}
def test_llm_call_posts_bare_local_ollama_to_native_api(monkeypatch):
seen = {}
def fake_post(url, headers=None, json=None, timeout=None):
seen["url"] = url
seen["json"] = json
request = httpx.Request("POST", url)
return httpx.Response(
200,
request=request,
json={"message": {"content": "OK"}, "done": True},
)
monkeypatch.setattr(llm_core.httpx, "post", fake_post)
result = llm_core.llm_call(
"http://localhost:11434",
"llama3.2",
[{"role": "user", "content": "Say OK"}],
)
assert result == "OK"
assert seen["url"] == "http://localhost:11434/api/chat"
assert seen["json"]["stream"] is False
def test_openai_compatible_chat_url_shapes(monkeypatch):
seen = []
def fake_post(url, headers=None, json=None, timeout=None):
seen.append(url)
request = httpx.Request("POST", url)
return httpx.Response(
200,
request=request,
json={"choices": [{"message": {"content": "OK"}}]},
)
monkeypatch.setattr(llm_core.httpx, "post", fake_post)
llm_core._response_cache.clear()
cases = [
("http://localhost:11434/v1", "http://localhost:11434/v1/chat/completions"),
(
"http://localhost:11434/v1/chat/completions",
"http://localhost:11434/v1/chat/completions",
),
]
for i, (base_url, expected_url) in enumerate(cases):
result = llm_core.llm_call(
base_url,
f"openai-compatible-{i}",
[{"role": "user", "content": f"Say OK {i}"}],
)
assert result == "OK"
assert seen[-1] == expected_url
def test_list_model_ids_from_openai_compatible_v1(monkeypatch):
seen = {}
def fake_get(url, headers=None, timeout=None):
seen["url"] = url
request = httpx.Request("GET", url)
return httpx.Response(
200,
request=request,
json={"data": [{"id": "qwen2.5-coder:7b"}]},
)
monkeypatch.setattr(llm_core.httpx, "get", fake_get)
assert llm_core.list_model_ids("http://localhost:11434/v1") == ["qwen2.5-coder:7b"]
assert seen["url"] == "http://localhost:11434/v1/models"
# ---------------------------------------------------------------------------
# Tool-call argument serialization for native Ollama
#
+7 -17
View File
@@ -18,6 +18,8 @@ import pytest
from types import SimpleNamespace
from unittest.mock import MagicMock
from tests.helpers.calendar_routes import import_calendar_routes
# `tests/conftest.py` stubs the heavy optional deps. We additionally
# stub `core.database` here because the real module instantiates
# SQLAlchemy declarative classes at import-time — which blows up under
@@ -64,20 +66,8 @@ from fastapi import HTTPException
# calendar._get_or_404_calendar / _get_or_404_event
# ---------------------------------------------------------------------------
def _import_calendar_helpers():
"""Import the two private gate helpers without booting the full
calendar router. We patch sys.modules so the module-load side
effects (DB import) don't blow up under the conftest stubs."""
mod_name = "routes.calendar_routes"
if mod_name in sys.modules:
return sys.modules[mod_name]
# core.database is stubbed by conftest already; the module should
# import cleanly.
return __import__(mod_name, fromlist=["_get_or_404_calendar", "_get_or_404_event"])
def test_calendar_gate_rejects_null_owner_for_authenticated_user():
cal_mod = _import_calendar_helpers()
cal_mod = import_calendar_routes()
db = MagicMock()
cal = SimpleNamespace(id="c1", owner=None)
db.query.return_value.filter.return_value.first.return_value = cal
@@ -87,7 +77,7 @@ def test_calendar_gate_rejects_null_owner_for_authenticated_user():
def test_calendar_gate_rejects_cross_owner():
cal_mod = _import_calendar_helpers()
cal_mod = import_calendar_routes()
db = MagicMock()
cal = SimpleNamespace(id="c1", owner="bob")
db.query.return_value.filter.return_value.first.return_value = cal
@@ -97,7 +87,7 @@ def test_calendar_gate_rejects_cross_owner():
def test_calendar_gate_accepts_matching_owner():
cal_mod = _import_calendar_helpers()
cal_mod = import_calendar_routes()
db = MagicMock()
cal = SimpleNamespace(id="c1", owner="alice")
db.query.return_value.filter.return_value.first.return_value = cal
@@ -106,7 +96,7 @@ def test_calendar_gate_accepts_matching_owner():
def test_calendar_event_gate_rejects_null_owner_calendar():
cal_mod = _import_calendar_helpers()
cal_mod = import_calendar_routes()
db = MagicMock()
cal = SimpleNamespace(owner=None)
ev = SimpleNamespace(uid="e1", calendar=cal)
@@ -117,7 +107,7 @@ def test_calendar_event_gate_rejects_null_owner_calendar():
def test_calendar_event_gate_rejects_cross_owner():
cal_mod = _import_calendar_helpers()
cal_mod = import_calendar_routes()
db = MagicMock()
cal = SimpleNamespace(owner="bob")
ev = SimpleNamespace(uid="e1", calendar=cal)
+15 -1
View File
@@ -1,4 +1,4 @@
from src.personal_docs import retrieve_personal_keyword
from src.personal_docs import retrieve_personal_keyword, split_chunks
def test_retrieve_personal_keyword_skips_non_dict_rows():
@@ -19,3 +19,17 @@ def test_retrieve_personal_keyword_tolerates_missing_chunks_key():
index = [{"name": "empty.txt"}, {"name": "doc.txt", "chunks": ["alpha beta gamma"]}]
out = retrieve_personal_keyword(index, "beta", k=5)
assert out == ["[doc.txt :: chunk 1]\nalpha beta gamma"]
def test_retrieve_personal_keyword_ignores_non_string_text():
index = [{"name": "doc.txt", "chunks": [None, ["beta"], "alpha beta gamma"]}]
assert retrieve_personal_keyword(index, ["beta"], k=5) == []
assert retrieve_personal_keyword(index, "beta", k=5) == [
"[doc.txt :: chunk 3]\nalpha beta gamma"
]
def test_split_chunks_ignores_non_string_text():
assert split_chunks(None, size=1000, overlap=200) == []
assert split_chunks(["hello"], size=1000, overlap=200) == []
-244
View File
@@ -1,244 +0,0 @@
"""Provider / endpoint resolution tests against the REAL resolver.
`test_endpoint_resolver.py` deliberately *copies* the pure functions to avoid
import side effects. The downside is that those copies silently drift from the
shipped code they already lag `src/endpoint_resolver.py` (no OpenRouter
headers, no `anthropic.com` host matching). This module instead imports the
real `src.endpoint_resolver`, so it fails the moment the shipped resolution
logic stops matching documented provider behavior. `conftest.py` stubs the
heavy deps (sqlalchemy, `src.database`), so the import is side-effect free.
Covers every provider named in ROADMAP.md "Provider setup/probing audit":
Anthropic, Gemini, Groq, xAI, OpenRouter, OpenAI, DeepSeek plus Ollama
(local + cloud) and the Tailscale self-host fallback.
"""
import json
import socket
import types
import pytest
from src import endpoint_resolver as er
@pytest.fixture
def no_dns(monkeypatch):
"""Neutralize resolve_url so URL-building tests never touch DNS/Tailscale.
build_chat_url/build_models_url call the module-global resolve_url first;
patching it on the module makes those calls a no-op (functions resolve
globals by name at call time).
"""
monkeypatch.setattr(er, "resolve_url", lambda u: u)
# (id, base_url, expected_chat_url, expected_models_url)
PROVIDER_CASES = [
("openai", "https://api.openai.com/v1",
"https://api.openai.com/v1/chat/completions",
"https://api.openai.com/v1/models"),
("openai_pathless", "https://api.openai.com",
"https://api.openai.com/v1/chat/completions",
"https://api.openai.com/v1/models"),
("anthropic", "https://api.anthropic.com",
"https://api.anthropic.com/v1/messages",
"https://api.anthropic.com/v1/models"),
# Anthropic base that already carries /v1 must not become /v1/v1/messages.
("anthropic_v1", "https://api.anthropic.com/v1",
"https://api.anthropic.com/v1/messages",
"https://api.anthropic.com/v1/models"),
("openrouter", "https://openrouter.ai/api/v1",
"https://openrouter.ai/api/v1/chat/completions",
"https://openrouter.ai/api/v1/models"),
("groq", "https://api.groq.com/openai/v1",
"https://api.groq.com/openai/v1/chat/completions",
"https://api.groq.com/openai/v1/models"),
("nvidia", "https://integrate.api.nvidia.com/v1",
"https://integrate.api.nvidia.com/v1/chat/completions",
"https://integrate.api.nvidia.com/v1/models"),
("xai", "https://api.x.ai/v1",
"https://api.x.ai/v1/chat/completions",
"https://api.x.ai/v1/models"),
("deepseek", "https://api.deepseek.com",
"https://api.deepseek.com/chat/completions",
"https://api.deepseek.com/v1/models"),
# Gemini's OpenAI-compatible surface — treated as a generic OpenAI endpoint.
("gemini_openai", "https://generativelanguage.googleapis.com/v1beta/openai",
"https://generativelanguage.googleapis.com/v1beta/openai/chat/completions",
"https://generativelanguage.googleapis.com/v1beta/openai/models"),
("ollama_local", "http://localhost:11434/api",
"http://localhost:11434/api/chat",
"http://localhost:11434/api/tags"),
("ollama_cloud", "https://ollama.com",
"https://ollama.com/api/chat",
"https://ollama.com/api/tags"),
]
@pytest.mark.parametrize(
"base,expected", [(c[1], c[2]) for c in PROVIDER_CASES],
ids=[c[0] for c in PROVIDER_CASES],
)
def test_build_chat_url(no_dns, base, expected):
assert er.build_chat_url(base) == expected
@pytest.mark.parametrize(
"base,expected", [(c[1], c[3]) for c in PROVIDER_CASES],
ids=[c[0] for c in PROVIDER_CASES],
)
def test_build_models_url(no_dns, base, expected):
assert er.build_models_url(base) == expected
def test_chat_url_never_double_prefixes_anthropic(no_dns):
"""Regression guard: the /v1 collapse must not produce /v1/v1/messages."""
url = er.build_chat_url("https://api.anthropic.com/v1")
assert "/v1/v1/" not in url
assert url.count("/v1/messages") == 1
# ── Auth headers per provider ──
def test_headers_anthropic_uses_x_api_key():
h = er.build_headers("secret", "https://api.anthropic.com")
assert h["x-api-key"] == "secret"
assert h["anthropic-version"] == "2023-06-01"
assert "Authorization" not in h
def test_headers_anthropic_without_key_still_sends_version():
h = er.build_headers(None, "https://api.anthropic.com")
assert h["anthropic-version"] == "2023-06-01"
assert "x-api-key" not in h
@pytest.mark.parametrize("base", [
"https://api.openai.com/v1",
"https://api.x.ai/v1",
"https://api.deepseek.com",
"https://api.groq.com/openai/v1",
"https://integrate.api.nvidia.com/v1",
"https://generativelanguage.googleapis.com/v1beta/openai",
])
def test_headers_openai_style_use_bearer(base):
h = er.build_headers("secret", base)
assert h["Authorization"] == "Bearer secret"
assert "HTTP-Referer" not in h
assert "x-api-key" not in h
def test_headers_openrouter_adds_attribution():
h = er.build_headers("secret", "https://openrouter.ai/api/v1")
assert h["Authorization"] == "Bearer secret"
# OpenRouter ranks/labels apps via these headers.
assert h["HTTP-Referer"].startswith("https://github.com/")
assert h["X-OpenRouter-Title"] == "Odysseus"
def test_headers_omit_authorization_when_no_key():
assert er.build_headers(None, "https://api.openai.com/v1") == {}
# ── normalize_base: strip whatever path the user pasted ──
@pytest.mark.parametrize("raw,expected", [
("https://api.openai.com/v1/chat/completions", "https://api.openai.com/v1"),
("https://api.openai.com/v1/completions", "https://api.openai.com/v1"),
("https://api.openai.com/v1/models/", "https://api.openai.com/v1"),
("https://api.anthropic.com/v1/messages", "https://api.anthropic.com"),
("http://localhost:11434/api/chat", "http://localhost:11434/api"),
("http://localhost:11434/api/tags", "http://localhost:11434/api"),
("http://localhost:11434/api/generate", "http://localhost:11434/api"),
("https://api.openai.com/v1/", "https://api.openai.com/v1"),
(" https://api.openai.com/v1 ", "https://api.openai.com/v1"),
("", ""),
(None, ""),
])
def test_normalize_base(raw, expected):
assert er.normalize_base(raw) == expected
# ── _first_chat_model: never auto-pick an embedding/tts/etc. model ──
def test_first_chat_model_skips_non_chat():
models = ["text-embedding-ada-002", "whisper-1", "gpt-4o", "dall-e-3"]
assert er._first_chat_model(models) == "gpt-4o"
def test_first_chat_model_falls_back_to_first_when_all_non_chat():
models = ["text-embedding-3-large", "text-embedding-3-small"]
assert er._first_chat_model(models) == "text-embedding-3-large"
@pytest.mark.parametrize("models", [[], None])
def test_first_chat_model_empty(models):
assert er._first_chat_model(models) is None
# ── provider-root helpers ──
@pytest.mark.parametrize("base,expected", [
("https://api.anthropic.com/v1", "https://api.anthropic.com"),
("https://api.anthropic.com", "https://api.anthropic.com"),
# /v1 on a non-Anthropic host (OpenAI-compatible) must be preserved.
("https://api.openai.com/v1", "https://api.openai.com/v1"),
])
def test_anthropic_api_root(base, expected):
assert er._anthropic_api_root(base) == expected
@pytest.mark.parametrize("base,expected", [
("https://ollama.com", "https://ollama.com/api"),
("http://localhost:11434/api", "http://localhost:11434/api"),
# A non-Ollama host is returned untouched.
("https://api.openai.com/v1", "https://api.openai.com/v1"),
])
def test_ollama_api_root(base, expected):
assert er._ollama_api_root(base) == expected
# ── resolve_url: Tailscale self-host fallback ──
# ROADMAP flags plain-HTTP Tailscale URLs as a self-host trap; resolve_url is
# the hop that rewrites an unresolvable hostname to its Tailscale IP.
class TestResolveUrlTailscale:
def setup_method(self):
# The module memoizes hostname→IP; clear it so cases don't bleed.
er._tailscale_cache.clear()
def test_dns_success_returns_url_unchanged(self, monkeypatch):
monkeypatch.setattr(
er.socket, "getaddrinfo",
lambda *a, **k: [(2, 1, 6, "", ("1.2.3.4", 0))],
)
assert er.resolve_url("http://myhost:7000/api") == "http://myhost:7000/api"
def test_dns_failure_rewrites_to_tailscale_ip(self, monkeypatch):
def _fail(*a, **k):
raise socket.gaierror("no DNS")
monkeypatch.setattr(er.socket, "getaddrinfo", _fail)
peers = {"Peer": {"x": {
"HostName": "myhost",
"DNSName": "myhost.tail.ts.net.",
"TailscaleIPs": ["100.64.0.5"],
}}}
monkeypatch.setattr(
er.subprocess, "run",
lambda *a, **k: types.SimpleNamespace(returncode=0, stdout=json.dumps(peers)),
)
# Port is preserved, host swapped for the Tailscale IP.
assert er.resolve_url("http://myhost:7000/api") == "http://100.64.0.5:7000/api"
def test_dns_failure_no_peer_match_keeps_url(self, monkeypatch):
def _fail(*a, **k):
raise socket.gaierror("no DNS")
monkeypatch.setattr(er.socket, "getaddrinfo", _fail)
monkeypatch.setattr(
er.subprocess, "run",
lambda *a, **k: types.SimpleNamespace(returncode=0, stdout=json.dumps({"Peer": {}})),
)
assert er.resolve_url("http://myhost:7000/api") == "http://myhost:7000/api"
def test_url_without_hostname_is_returned_as_is(self):
assert er.resolve_url("") == ""
+49
View File
@@ -0,0 +1,49 @@
"""Provider endpoint auth-header tests.
Covers ``build_headers`` for every provider: Anthropic (x-api-key + version
header), OpenAI-style providers (Bearer token), OpenRouter (Bearer + attribution
headers), and the no-key case.
"""
import pytest
from src import endpoint_resolver as er
def test_headers_anthropic_uses_x_api_key():
h = er.build_headers("secret", "https://api.anthropic.com")
assert h["x-api-key"] == "secret"
assert h["anthropic-version"] == "2023-06-01"
assert "Authorization" not in h
def test_headers_anthropic_without_key_still_sends_version():
h = er.build_headers(None, "https://api.anthropic.com")
assert h["anthropic-version"] == "2023-06-01"
assert "x-api-key" not in h
@pytest.mark.parametrize("base", [
"https://api.openai.com/v1",
"https://api.x.ai/v1",
"https://api.deepseek.com",
"https://api.groq.com/openai/v1",
"https://integrate.api.nvidia.com/v1",
"https://generativelanguage.googleapis.com/v1beta/openai",
])
def test_headers_openai_style_use_bearer(base):
h = er.build_headers("secret", base)
assert h["Authorization"] == "Bearer secret"
assert "HTTP-Referer" not in h
assert "x-api-key" not in h
def test_headers_openrouter_adds_attribution():
h = er.build_headers("secret", "https://openrouter.ai/api/v1")
assert h["Authorization"] == "Bearer secret"
# OpenRouter ranks/labels apps via these headers.
assert h["HTTP-Referer"].startswith("https://github.com/")
assert h["X-OpenRouter-Title"] == "Odysseus"
def test_headers_omit_authorization_when_no_key():
assert er.build_headers(None, "https://api.openai.com/v1") == {}
+25
View File
@@ -0,0 +1,25 @@
"""Provider endpoint model-selection tests.
Covers ``_first_chat_model``: auto-picking the first usable chat model from a
provider's model list, skipping embedding/tts/image models when possible.
"""
import pytest
from src import endpoint_resolver as er
# ── _first_chat_model: never auto-pick an embedding/tts/etc. model ──
def test_first_chat_model_skips_non_chat():
models = ["text-embedding-ada-002", "whisper-1", "gpt-4o", "dall-e-3"]
assert er._first_chat_model(models) == "gpt-4o"
def test_first_chat_model_falls_back_to_first_when_all_non_chat():
models = ["text-embedding-3-large", "text-embedding-3-small"]
assert er._first_chat_model(models) == "text-embedding-3-large"
@pytest.mark.parametrize("models", [[], None])
def test_first_chat_model_empty(models):
assert er._first_chat_model(models) is None
@@ -0,0 +1,49 @@
"""Provider endpoint normalization tests.
Covers ``normalize_base`` (strip whatever path the user pasted), and the
provider-root helpers ``_anthropic_api_root`` and ``_ollama_api_root``.
"""
import pytest
from src import endpoint_resolver as er
# ── normalize_base: strip whatever path the user pasted ──
@pytest.mark.parametrize("raw,expected", [
("https://api.openai.com/v1/chat/completions", "https://api.openai.com/v1"),
("https://api.openai.com/v1/completions", "https://api.openai.com/v1"),
("https://api.openai.com/v1/models/", "https://api.openai.com/v1"),
("https://api.anthropic.com/v1/messages", "https://api.anthropic.com"),
("http://localhost:11434/api/chat", "http://localhost:11434/api"),
("http://localhost:11434/api/tags", "http://localhost:11434/api"),
("http://localhost:11434/api/generate", "http://localhost:11434/api"),
("https://api.openai.com/v1/", "https://api.openai.com/v1"),
(" https://api.openai.com/v1 ", "https://api.openai.com/v1"),
("", ""),
(None, ""),
])
def test_normalize_base(raw, expected):
assert er.normalize_base(raw) == expected
# ── provider-root helpers ──
@pytest.mark.parametrize("base,expected", [
("https://api.anthropic.com/v1", "https://api.anthropic.com"),
("https://api.anthropic.com", "https://api.anthropic.com"),
# /v1 on a non-Anthropic host (OpenAI-compatible) must be preserved.
("https://api.openai.com/v1", "https://api.openai.com/v1"),
])
def test_anthropic_api_root(base, expected):
assert er._anthropic_api_root(base) == expected
@pytest.mark.parametrize("base,expected", [
("https://ollama.com", "https://ollama.com/api"),
("http://localhost:11434/api", "http://localhost:11434/api"),
# A non-Ollama host is returned untouched.
("https://api.openai.com/v1", "https://api.openai.com/v1"),
])
def test_ollama_api_root(base, expected):
assert er._ollama_api_root(base) == expected
@@ -0,0 +1,59 @@
"""Provider endpoint Tailscale URL-resolution tests.
Covers ``resolve_url``: the hop that rewrites an unresolvable hostname to its
Tailscale IP. ROADMAP flags plain-HTTP Tailscale URLs as a self-host trap;
resolve_url is the gate that handles that fallback.
"""
import json
import socket
import types
import pytest
from src import endpoint_resolver as er
# ── resolve_url: Tailscale self-host fallback ──
# ROADMAP flags plain-HTTP Tailscale URLs as a self-host trap; resolve_url is
# the hop that rewrites an unresolvable hostname to its Tailscale IP.
class TestResolveUrlTailscale:
def setup_method(self):
# The module memoizes hostname→IP; clear it so cases don't bleed.
er._tailscale_cache.clear()
def test_dns_success_returns_url_unchanged(self, monkeypatch):
monkeypatch.setattr(
er.socket, "getaddrinfo",
lambda *a, **k: [(2, 1, 6, "", ("1.2.3.4", 0))],
)
assert er.resolve_url("http://myhost:7000/api") == "http://myhost:7000/api"
def test_dns_failure_rewrites_to_tailscale_ip(self, monkeypatch):
def _fail(*a, **k):
raise socket.gaierror("no DNS")
monkeypatch.setattr(er.socket, "getaddrinfo", _fail)
peers = {"Peer": {"x": {
"HostName": "myhost",
"DNSName": "myhost.tail.ts.net.",
"TailscaleIPs": ["100.64.0.5"],
}}}
monkeypatch.setattr(
er.subprocess, "run",
lambda *a, **k: types.SimpleNamespace(returncode=0, stdout=json.dumps(peers)),
)
# Port is preserved, host swapped for the Tailscale IP.
assert er.resolve_url("http://myhost:7000/api") == "http://100.64.0.5:7000/api"
def test_dns_failure_no_peer_match_keeps_url(self, monkeypatch):
def _fail(*a, **k):
raise socket.gaierror("no DNS")
monkeypatch.setattr(er.socket, "getaddrinfo", _fail)
monkeypatch.setattr(
er.subprocess, "run",
lambda *a, **k: types.SimpleNamespace(returncode=0, stdout=json.dumps({"Peer": {}})),
)
assert er.resolve_url("http://myhost:7000/api") == "http://myhost:7000/api"
def test_url_without_hostname_is_returned_as_is(self):
assert er.resolve_url("") == ""
@@ -0,0 +1,86 @@
"""Provider endpoint URL-building tests.
Covers ``build_chat_url`` and ``build_models_url`` for every provider named in
ROADMAP.md: Anthropic, Gemini, Groq, xAI, OpenRouter, OpenAI, DeepSeek, Ollama
(local + cloud).
"""
import pytest
from src import endpoint_resolver as er
@pytest.fixture
def no_dns(monkeypatch):
"""Neutralize resolve_url so URL-building tests never touch DNS/Tailscale.
build_chat_url/build_models_url call the module-global resolve_url first;
patching it on the module makes those calls a no-op (functions resolve
globals by name at call time).
"""
monkeypatch.setattr(er, "resolve_url", lambda u: u)
# (id, base_url, expected_chat_url, expected_models_url)
PROVIDER_CASES = [
("openai", "https://api.openai.com/v1",
"https://api.openai.com/v1/chat/completions",
"https://api.openai.com/v1/models"),
("openai_pathless", "https://api.openai.com",
"https://api.openai.com/v1/chat/completions",
"https://api.openai.com/v1/models"),
("anthropic", "https://api.anthropic.com",
"https://api.anthropic.com/v1/messages",
"https://api.anthropic.com/v1/models"),
# Anthropic base that already carries /v1 must not become /v1/v1/messages.
("anthropic_v1", "https://api.anthropic.com/v1",
"https://api.anthropic.com/v1/messages",
"https://api.anthropic.com/v1/models"),
("openrouter", "https://openrouter.ai/api/v1",
"https://openrouter.ai/api/v1/chat/completions",
"https://openrouter.ai/api/v1/models"),
("groq", "https://api.groq.com/openai/v1",
"https://api.groq.com/openai/v1/chat/completions",
"https://api.groq.com/openai/v1/models"),
("nvidia", "https://integrate.api.nvidia.com/v1",
"https://integrate.api.nvidia.com/v1/chat/completions",
"https://integrate.api.nvidia.com/v1/models"),
("xai", "https://api.x.ai/v1",
"https://api.x.ai/v1/chat/completions",
"https://api.x.ai/v1/models"),
("deepseek", "https://api.deepseek.com",
"https://api.deepseek.com/chat/completions",
"https://api.deepseek.com/v1/models"),
# Gemini's OpenAI-compatible surface — treated as a generic OpenAI endpoint.
("gemini_openai", "https://generativelanguage.googleapis.com/v1beta/openai",
"https://generativelanguage.googleapis.com/v1beta/openai/chat/completions",
"https://generativelanguage.googleapis.com/v1beta/openai/models"),
("ollama_local", "http://localhost:11434/api",
"http://localhost:11434/api/chat",
"http://localhost:11434/api/tags"),
("ollama_cloud", "https://ollama.com",
"https://ollama.com/api/chat",
"https://ollama.com/api/tags"),
]
@pytest.mark.parametrize(
"base,expected", [(c[1], c[2]) for c in PROVIDER_CASES],
ids=[c[0] for c in PROVIDER_CASES],
)
def test_build_chat_url(no_dns, base, expected):
assert er.build_chat_url(base) == expected
@pytest.mark.parametrize(
"base,expected", [(c[1], c[3]) for c in PROVIDER_CASES],
ids=[c[0] for c in PROVIDER_CASES],
)
def test_build_models_url(no_dns, base, expected):
assert er.build_models_url(base) == expected
def test_chat_url_never_double_prefixes_anthropic(no_dns):
"""Regression guard: the /v1 collapse must not produce /v1/v1/messages."""
url = er.build_chat_url("https://api.anthropic.com/v1")
assert "/v1/v1/" not in url
assert url.count("/v1/messages") == 1
+6
View File
@@ -28,6 +28,12 @@ def _sys(vram, family="rdna"):
return {"backend": "rocm", "gpu_vram_gb": vram, "gpu_family": family}
def test_compute_serve_profiles_ignores_invalid_inputs():
assert compute_serve_profiles(None, _DENSE_8B) == []
assert compute_serve_profiles(_sys(8), None) == []
assert compute_serve_profiles(["bad"], _DENSE_8B) == []
def test_big_moe_on_small_card_offloads_not_fails():
"""A 35B MoE can't hold its weights on 16 GB, so the Quality profile must
offload experts to CPU (n_cpu_moe > 0) rather than be dropped."""
@@ -36,6 +36,28 @@ def test_compute_snap_tolerates_non_array_other_layers():
assert r["x"] == 10 and r["y"] == 10 and r["guides"] == []
@pytest.mark.skipif(not _HAS_NODE, reason="node binary not on PATH")
def test_compute_snap_tolerates_missing_layer_or_context():
js = f"""
import {{ computeSnap }} from '{_HELPER.as_posix()}';
console.log(JSON.stringify([
computeSnap(null, 10, 20, {{ zoom: 1, canvasW: 800, canvasH: 600 }}),
computeSnap({{ id: 'L1' }}, 11, 21, {{ zoom: 1, canvasW: 800, canvasH: 600 }}),
computeSnap({{ id: 'L1', canvas: {{ width: 100, height: 50 }} }}, 12, 22, null)
]));
"""
proc = subprocess.run(
["node", "--input-type=module"],
input=js, capture_output=True, text=True, cwd=str(_REPO), timeout=30,
)
assert proc.returncode == 0, proc.stderr
assert json.loads(proc.stdout.strip()) == [
{"x": 10, "y": 20, "guides": []},
{"x": 11, "y": 21, "guides": []},
{"x": 12, "y": 22, "guides": []},
]
@pytest.mark.skipif(not _HAS_NODE, reason="node binary not on PATH")
def test_compute_snap_still_snaps_to_a_layer_edge():
other = [{"id": "L2", "visible": True, "offset": {"x": 12, "y": 300},
+20
View File
@@ -68,3 +68,23 @@ def test_unresolvable_host_blocked():
ok, reason = check_outbound_url("http://does-not-resolve.invalid", resolver=PUBLIC)
assert ok is False
assert "resolve" in reason
def test_resolver_values_must_include_a_parseable_ip():
ok, reason = check_outbound_url(
"https://example.test",
resolver=lambda _host: [None, 123, "not-an-ip"],
)
assert ok is False
assert "does not resolve to an IP" in reason
def test_resolver_skips_invalid_values_but_accepts_public_ip():
ok, reason = check_outbound_url(
"https://example.test",
resolver=lambda _host: [None, "not-an-ip", "93.184.216.34"],
)
assert ok is True
assert reason == "ok"