import os import subprocess from pathlib import Path import pytest from core.platform_compat import find_bash, git_bash_path from routes.cookbook_helpers import ( ODYSSEUS_LLAMA_SHIM_MARKER, _append_llama_server_shim_definition_lines, _append_llama_server_shim_reconciliation_lines, _append_llama_server_finalization_lines, ) from tests.helpers.stylesheets import app_css ROOT = Path(__file__).resolve().parents[1] def _read(rel_path: str) -> str: return (ROOT / rel_path).read_text(encoding="utf-8") def test_backend_status_treats_download_exit_zero_as_completed(): source = _read("routes/cookbook_routes.py") assert "exit_match = re.search(r\"=== process exited with code\\s+(-?\\d+)\"" in source assert "elif has_exit and task_type == \"download\":" in source assert "status = \"completed\" if exit_code == 0 else \"error\"" in source def test_background_status_poll_reconciles_into_local_tasks(): source = _read("static/js/cookbookRunning.js") assert "const statusById = new Map(tasks.map(t => [t.session_id, t]));" in source assert "const completedByOutput = depDone || downloadDone;" in source assert "const nextStatus = completedByOutput" in source assert "live.status === 'completed'" in source assert "? 'done'" in source assert ": (live.status === 'error'" in source assert "? 'error'" in source assert "_saveTasks(localTasks);" in source assert "completedDeps.forEach(t => _refreshDepsAfterInstall(t));" in source def test_windows_session_commands_use_shared_powershell_wrapper_and_local_log_dir(): source = _read("static/js/cookbookRunning.js") assert "const host = task.remoteHost;" in source assert "host ? '$env:TEMP\\\\odysseus-sessions' : '$env:TEMP\\\\odysseus-tmux'" in source assert "function _winPowerShellCmd(task, ps)" in source assert "const command = `powershell -Command \"${ps}\"`;" in source assert "if (!host) return command;" in source assert "return `ssh ${_sshPrefix(_getPort(task))}${host} ${_shQuote(command)}`;" in source def test_dep_install_success_recognized_from_exit_sentinel(): """A pip dependency install reports success via the runner's exit-0 sentinel / pip's "Successfully installed" line, not the HuggingFace download markers. The shared helper must key off those, so an install whose tmux pane is gone isn't misread as crashed.""" source = _read("static/js/cookbookRunning.js") assert "function _depInstallSucceeded(output) {" in source assert "=== Process exited with code" in source assert "Successfully installed" in source def test_session_gone_heuristic_honors_dep_install_success(): """The reconnect loop's session-gone branch (download tasks need an HF marker to look successful) must also accept a finished dependency install, otherwise a clean pip install with no HF markers is marked crashed.""" source = _read("static/js/cookbookRunning.js") assert "const depInstallSucceeded = !!task.payload?._dep && _depInstallSucceeded(lastOutput);" in source # Whitespace-normalized so the check survives line-wrapping/formatting while # still proving the invariant: a finished dependency install short-circuits # looksSuccessful ahead of the download/serve branch. normalized = " ".join(source.split()) assert ( "const looksSuccessful = depInstallSucceeded " "|| (task.type === 'download'" ) in normalized def test_background_poll_recovers_done_for_stopped_dependency_install(): """When the backend reports a finished dependency install as "stopped" (its pip package is never in the HF cache the dead-session check inspects), the reconciler must recover "done" from the retained output instead of downgrading the card to crashed.""" source = _read("static/js/cookbookRunning.js") assert "const combinedOutput = `${task.output || ''}\\n${live.output_tail || ''}`;" in source assert "const depDone = !!task.payload?._dep && _depInstallSucceeded(combinedOutput);" in source assert "(depDone || downloadDone) ? 'done' : (task.type === 'download' ? 'crashed' : 'stopped')" in source def test_background_poll_recovers_done_for_completed_download(): """When the backend reports a finished model download as "stopped" (its tmux pane is gone after DOWNLOAD_OK, so the dead-session check can miss the landed snapshot), the reconciler must recover "done" from the terminal DOWNLOAD_OK sentinel instead of downgrading the card to crashed. The background poll keys off DOWNLOAD_OK only (not the "/snapshots/" path, which can appear mid-stream for multi-file downloads).""" source = _read("static/js/cookbookRunning.js") normalized = " ".join(source.split()) assert ( "const downloadDone = task.type === 'download' " "&& String(combinedOutput || '').includes('DOWNLOAD_OK');" ) in normalized def test_running_tab_render_disposes_card_owned_work_before_rebuild(): source = _read("static/js/cookbookRunning.js") assert "function _disposeTaskCard(el) {" in source assert "el._abort.abort()" in source assert "clearInterval(el._uptimeInterval)" in source assert "body.querySelectorAll('.cookbook-task').forEach(_disposeTaskCard);" in source def test_existing_download_card_uses_finished_label_for_done_and_completed(): source = _read("static/js/cookbookRunning.js") assert "const isDoneDl = _isFinishedDownload(task.status, task.type);" in source assert source.count("const isDoneDl = _isFinishedDownload(task.status, task.type);") == 1 def test_background_monitor_requests_have_timeouts(): source = _read("static/js/cookbookRunning.js") assert "function _fetchWithTimeout(input, init = {}, timeoutMs = 15000)" in source assert "const res = await _fetchWithTimeout('/api/cookbook/tasks/status'" in source assert "_fetchWithTimeout('/api/cookbook/state'" in source assert "_fetchWithTimeout('/api/model-endpoints/probe-local'" in source def test_stale_task_self_heal_is_single_flight_and_bounded(): source = _read("static/js/cookbookRunning.js") assert "let _selfHealInFlight = null;" in source assert "async function _selfHealStaleTasksImpl(opts = {})" in source assert "if (_selfHealInFlight) return _selfHealInFlight;" in source assert "_fetchWithTimeout('/api/shell/exec'" in source def test_cookbook_state_sync_is_single_flight(): source = _read("static/js/cookbookRunning.js") assert "let _syncFromServerInFlight = null;" in source assert "async function _syncFromServerImpl()" in source assert "if (_syncFromServerInFlight) return _syncFromServerInFlight;" in source assert "pending.then(clearPending, clearPending);" in source assert "const tracked = run.finally(() =>" in source def test_cookbook_state_sync_requests_are_bounded(): source = _read("static/js/cookbookRunning.js") assert "_fetchWithTimeout('/api/cookbook/state', { credentials: 'same-origin' }, 15000)" in source assert "_fetchWithTimeout('/api/cookbook/state', {" in source assert "}, 15000);" in source assert source.count("fetch(") == 1 # the helper's own underlying request def test_dependency_probe_cache_is_scoped_and_invalidated_after_install(): source = _read("routes/shell_routes.py") assert "_PACKAGE_STATUS_CACHE" in source assert "package_cache_key = (" in source assert "(host or \"\").strip()" in source assert "(venv or \"\").strip()" in source assert "_PACKAGE_STATUS_CACHE[package_cache_key]" in source assert "_PACKAGE_STATUS_CACHE_MAX = 64" in source assert "oldest_key = min(_PACKAGE_STATUS_CACHE" in source assert "_PACKAGE_STATUS_CACHE.clear()" in source def test_terminal_download_status_skips_remote_cache_probes(): source = _read("routes/cookbook_routes.py") assert "_persisted_terminal = _task_status in" in source assert "or (not _persisted_terminal and _download_cache_incomplete(" in source assert "and not _persisted_terminal\n and not download_has_incomplete_evidence" in source def test_download_start_has_concurrent_click_guard(): source = _read("static/js/cookbookDownload.js") assert "const _downloadStartsInFlight = new Set();" in source assert "_downloadStartsInFlight.has(startKey)" in source assert "_downloadStartsInFlight.add(startKey);" in source assert "_downloadStartsInFlight.delete(startKey);" in source def test_dependency_scan_only_latest_response_can_render(): source = _read("static/js/cookbook.js") assert "let _dependenciesFetchId = 0;" in source assert "const fetchId = ++_dependenciesFetchId;" in source assert "if (fetchId !== _dependenciesFetchId) return;" in source assert source.count("if (fetchId !== _dependenciesFetchId) return;") >= 2 def test_dependency_refresh_preserves_same_target_rows(): source = _read("static/js/cookbook.js") assert "const preserveRows = !!list.querySelector('.cookbook-dep-row')" in source assert "list.dataset.cookbookDepsScanSig === scanSig" in source assert "Refreshing packages…" in source assert "list.prepend(loading);" in source assert "list.replaceChildren(loading);" in source def test_dependency_probe_has_bounded_http_request_and_status_error(): source = _read("static/js/cookbook.js") assert "let _dependenciesRequestController = null;" in source assert "_dependenciesRequestController?.abort();" in source assert "const depController = new AbortController();" in source assert "setTimeout(() => depController.abort(), 20000)" in source assert "signal: depController.signal" in source assert "if (!resp.ok) throw new Error(`HTTP ${resp.status}" in source assert "} finally {\n clearTimeout(depTimer);" in source assert "_dependenciesRequestController === depController" in source def test_server_color_picker_cleanup_precedes_settings_row_rebuild(): picker = _read("static/js/cookbook-hwfit.js") render = _read("static/js/cookbook.js") assert "const onDocumentClick = () => close();" in picker assert "document.removeEventListener('click', onDocumentClick);" in picker assert "entry._cleanupColorPicker = () =>" in picker assert "body.querySelectorAll('.cookbook-server-entry').forEach(entry =>" in render assert "entry._cleanupColorPicker?.();" in render def test_hwfit_custom_picker_listeners_are_cleaned_before_render(): picker = _read("static/js/cookbook-hwfit.js") render = _read("static/js/cookbook.js") assert "wrap._cleanupUsecasePicker = () =>" in picker assert "wrap._cleanupEnginePicker = () =>" in picker assert "document.removeEventListener('keydown', onDocumentKeydown);" in picker assert "body.querySelectorAll('.hwfit-usecase-wrap').forEach(wrap =>" in render assert "body.querySelectorAll('.hwfit-engine-wrap').forEach(wrap =>" in render def test_hwfit_scan_controls_are_idempotently_bound(): source = _read("static/js/cookbook-hwfit.js") assert source.count("dataset.hwfitScanBound") >= 5 assert "if (uc && !uc.dataset.hwfitScanBound)" in source assert "if (sort && !sort.dataset.hwfitScanBound)" in source assert "if (qpref && !qpref.dataset.hwfitScanBound)" in source assert "if (engine && !engine.dataset.hwfitScanBound)" in source assert "if (search && !search.dataset.hwfitScanBound)" in source def test_cookbook_close_cancels_active_scans(): cookbook = _read("static/js/cookbook.js") hwfit = _read("static/js/cookbook-hwfit.js") serve = _read("static/js/cookbookServe.js") assert "export function _cancelHwfitRequests()" in hwfit assert "export function _cancelCachedModelScan()" in serve assert "_cancelHwfitRequests();" in cookbook assert "_cancelCachedModelScan();" in cookbook def test_cookbook_close_cancels_dependency_refresh(): source = _read("static/js/cookbook.js") close_start = source.index("function _doClose()") close_end = source.index(" const modal =", close_start) close_block = source[close_start:close_end] assert "_dependenciesFetchId++;" in close_block assert "_dependenciesRequestController?.abort();" in close_block assert "_dependenciesRequestController = null;" in close_block def test_launch_dependency_preflight_preserves_backend_and_model_context(): source = _read("static/js/cookbook-hwfit.js") assert "params.set('backend', runBackend);" in source assert "params.set('model_hint', modelName);" in source assert "if (!r.ok) return true; // An unavailable probe must not falsely block launch." in source def test_dependency_refresh_passes_cached_models_without_family_filtering(): source = _read("static/js/cookbook.js") assert "Array.from(_cachedModelIds)" in source assert ".filter(id => /krea/i.test" not in source assert "_pkgParams.set('model_hint', _hint);" in source def test_dependency_api_does_not_hide_image_capabilities_by_model_name(): source = _read("routes/shell_routes.py") assert "has_krea_model" not in source assert "has_lama_mlx_model" not in source assert "has_ddcolor_mlx_model" not in source assert 'p.get("name") not in {"krea_diffusers", "transformers"}' not in source def test_dependency_status_cache_is_not_fragmented_by_unused_model_hint(): source = _read("routes/shell_routes.py") start = source.index("package_cache_key = (") end = source.index(")\n cached_status", start) assert "model_hint" not in source[start:end] def test_model_lists_fade_after_complete_rows_are_painted(): hwfit = _read("static/js/cookbook-hwfit.js") cookbook = _read("static/js/cookbook.js") style = app_css() assert "cookbook-model-list-fade" in hwfit assert "cookbook-model-list-fade" in cookbook assert "@keyframes cookbook-model-list-fade-in" in style assert "cookbook-hwfit-models-just-loaded" in hwfit assert "cookbook-hwfit-models-just-loaded" in style assert "prefers-reduced-motion" in style def test_dependency_panel_has_stable_categories_and_mobile_build_action(): cookbook = _read("static/js/cookbook.js") style = app_css() assert "const _depCategoryOrder = ['System', 'Tools', 'LLM', 'Image'" in cookbook assert "const _orderedDepCategories = (byCat)" in cookbook assert "String(p.name || '').startsWith('mlx_')" in cookbook assert "cookbook-dep-build-deps" in cookbook assert ".cookbook-dep-build-deps" in style assert "white-space: normal" in style assert ".cookbook-dep-row > .cookbook-dep-info" in style assert ".cookbook-dep-row > .cookbook-dep-tag" in style assert "transform: translateY(2px)" in style assert ".cookbook-dep-row > .cookbook-dep-installed-btn" in style assert ".cookbook-dep-row > .cookbook-dep-install" in style def test_direct_download_auto_fold_requires_deliberate_boundary_gesture(): source = _read("static/js/cookbook.js") assert "y > prev + 12" in source assert "e.deltaY < -8" in source assert "endY - _touchStartY > 24" in source def test_launch_command_box_uses_accent_focus_glow(): style = app_css() assert ".hwfit-serve-cmd-details[open] .hwfit-serve-cmd" in style assert "box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent" in style def test_server_settings_color_picker_uses_dot_trigger(): cookbook = _read("static/js/cookbook.js") hwfit = _read("static/js/cookbook-hwfit.js") style = app_css() assert 'aria-label="Change server color (currently ${esc(selectedColorLabel)})"' in cookbook assert "btn.setAttribute('aria-label', `Change server color (currently ${label})`)" in hwfit assert ".cookbook-server-row .cookbook-srv-color-dot" in style assert "width: 12px" in style assert "background: transparent" in style assert ".cookbook-server-row .cookbook-srv-color-btn:hover" in style def test_default_server_matches_exclusive_model_directory_selector(): cookbook = _read("static/js/cookbook.js") hwfit = _read("static/js/cookbook-hwfit.js") style = app_css() assert "const icon = active ? _MODELDIR_CHECK_ON : _MODELDIR_CHECK_OFF;" in cookbook assert "_envState.defaultServer = key;" in hwfit assert "Toggle off if it's already the default" not in hwfit assert ".cookbook-srv-default-icon svg" in style def test_trending_models_use_headroom_when_filtering_vram(): source = _read("routes/cookbook_routes.py") assert "usable_vram = vram_gb * 0.90" in source assert "needed_vram > usable_vram" in source def test_dependency_deeplink_preserves_the_failed_model_hint(): cookbook = _read("static/js/cookbook.js") diagnosis = _read("static/js/cookbook-diagnosis.js") assert "let _dependenciesModelHint = '';" in cookbook assert "_dependenciesModelHint," in cookbook assert "dependencyModel: opts.model || ''" in diagnosis assert "_dependenciesModelHint = String(opts?.dependencyModel || '').trim();" in cookbook def test_services_exports_are_lazy_for_cookbook_startup(): source = _read("services/__init__.py") assert "from importlib import import_module" in source assert "_LAZY_EXPORTS" in source assert "def __getattr__(name):" in source assert "from .search import" not in source assert "from .docs import" not in source assert "from .research import" not in source assert "from .memory import" not in source assert "from .shell import" not in source def test_image_catalog_uses_persistent_stale_cache(): source = _read("services/hwfit/image_models.py") assert "_IMAGE_COLLECTION_DISK_CACHE" in source assert "_IMAGE_COLLECTION_DISK_TTL" in source assert "cached_models" in source assert "tmp.replace(_IMAGE_COLLECTION_DISK_CACHE)" in source assert "Preserve stale results if the network is unavailable" in source def test_schedule_api_calls_are_bounded(): source = _read("static/js/cookbookSchedule.js") assert "function fetchWithTimeout(input, init = {}, timeoutMs = 20000)" in source assert "return fetch(input, { ...init, signal: controller.signal }).finally" in source assert source.count("fetchWithTimeout(") >= 5 assert "await fetch(\"/api/tasks\"" not in source assert "await fetch(\"/api/calendar/calendars\"" not in source assert "await fetch(\"/api/calendar/events\"" not in source def test_diagnosis_quick_commands_have_a_bounded_request(): source = _read("static/js/cookbook-diagnosis.js") assert "function _diagFetchWithTimeout(input, init = {}, timeoutMs = 75000)" in source assert "const timer = setTimeout(() => controller.abort(), timeoutMs);" in source assert "await _diagFetchWithTimeout('/api/shell/exec'" in source def test_serve_scans_have_timeouts_and_do_not_overlap_gpu_polls(): source = _read("static/js/cookbookServe.js") assert "function _fetchCookbookWithTimeout(input, init = {}, timeoutMs = 20000)" in source assert "setTimeout(() => controller.abort(), timeoutMs)" in source assert "_fetchCookbookWithTimeout(`/api/model/cached${params}`" in source assert "_fetchCookbookWithTimeout('/api/cookbook/gpus'" in source assert "let _vramMonitorInFlight = false;" in source assert "if (_vramMonitorInFlight) return true;" in source assert "_vramMonitorInFlight = false;" in source def test_cached_model_scans_are_single_flight_per_target(): source = _read("static/js/cookbookServe.js") assert "async function _fetchCachedModelsImpl(fresh = false, opts = {})" in source assert "const _cachedModelsInFlight = new Map();" in source assert "const existing = _cachedModelsInFlight.get(key);" in source assert "if (existing) return existing;" in source assert "pending.then(clearPending, clearPending);" in source def test_superseded_cached_model_scans_are_cancelled(): source = _read("static/js/cookbookServe.js") assert "let _cachedModelsRequestController = null;" in source assert "_cachedModelsRequestController?.abort();" in source assert "const _requestController = new AbortController();" in source assert "signal: _requestController.signal" in source def test_hwfit_network_actions_have_bounded_requests(): source = _read("static/js/cookbook-hwfit.js") assert "function _fetchHwfitWithTimeout(input, init = {}, timeoutMs = 20000)" in source assert "setTimeout(() => controller.abort(), timeoutMs)" in source assert "_fetchHwfitWithTimeout(endpoint, { signal: _requestController.signal }, 30000)" in source assert "const _cacheFetchToken = _tk;" in source assert "_fetchHwfitWithTimeout(`/api/model/cached?${_cacheParams}`" in source assert "if (_cacheFetchToken !== _hwfitFetchToken) return;" in source assert "_fetchHwfitWithTimeout('/api/model/serve'" in source assert "_fetchHwfitWithTimeout('/api/cookbook/test-ssh'" in source assert "await fetch(" not in source def test_serve_gpu_and_context_actions_use_bounded_requests(): source = _read("static/js/cookbookServe.js") assert "_fetchCookbookWithTimeout(`/api/hwfit/profiles?${params}`, {}, 30000)" in source assert "_fetchCookbookWithTimeout(url, { credentials: 'same-origin' }, 10000)" in source assert source.count("_fetchCookbookWithTimeout('/api/cookbook/gpus'") >= 3 assert source.count("_fetchCookbookWithTimeout('/api/cookbook/kill-pid'") >= 3 assert "await fetch(" not in source def test_download_control_requests_are_bounded_but_sse_stream_remains_cancellable(): source = _read("static/js/cookbookDownload.js") assert "function _fetchDownloadControlWithTimeout(input, init = {}, timeoutMs = 20000)" in source assert "setTimeout(() => controller.abort(), timeoutMs)" in source assert "_fetchDownloadControlWithTimeout('/api/shell/exec'" in source assert "_fetchDownloadControlWithTimeout('/api/model/download'" in source assert "const res = await fetch('/api/shell/stream'" in source assert "signal: controller.signal" in source def test_main_cookbook_catalog_lookups_surface_http_failures_and_timeout(): source = _read("static/js/cookbook.js") assert "function _fetchCookbookUiWithTimeout(input, init = {}, timeoutMs = 20000)" in source assert "_fetchCookbookUiWithTimeout(`/api/hwfit/system?${qp}`, {}, 20000)" in source assert "_fetchCookbookUiWithTimeout(`/api/cookbook/hf-latest?${params}`, {}, 30000)" in source assert "_fetchCookbookUiWithTimeout(`/api/cookbook/ollama/library${refresh ? '?refresh=1' : ''}`, {}, 30000)" in source assert "if (!res.ok) throw new Error(`HTTP ${res.status}`);" in source def test_hwfit_superseded_scans_are_cancelled_and_cannot_paint_errors(): source = _read("static/js/cookbook-hwfit.js") assert "let _hwfitRequestController = null;" in source assert "_hwfitRequestController?.abort();" in source assert "const _requestController = new AbortController();" in source assert "signal: _requestController.signal" in source assert source.count("_tk !== _hwfitFetchToken") >= 4 def test_ollama_catalog_lookup_is_single_flight_and_checks_http_status(): source = _read("static/js/cookbook-hwfit.js") assert "let _ollamaLibInFlight = null;" in source assert "if (_ollamaLibInFlight) return _ollamaLibInFlight;" in source assert "if (!res.ok) throw new Error(`HTTP ${res.status}`);" in source assert "pending.then(clearPending, clearPending);" in source def test_bulk_task_clear_uses_shared_card_disposer(): source = _read("static/js/cookbookRunning.js") assert "toRemove.forEach(t => {\n const el = document.querySelector(`.cookbook-task[data-task-id=\"${t.sessionId}\"]`);\n if (el) {\n _disposeTaskCard(el);" in source assert "_disposeTaskCard(el);\n el.remove();" in source def test_task_card_disposer_cancels_mobile_long_press(): source = _read("static/js/cookbookRunning.js") assert "if (el._longPressTimer)" in source assert "el._longPressTimer = setTimeout" in source assert "el._longPressTimer = null;" in source def test_cached_model_rerender_cancels_mobile_long_press(): source = _read("static/js/cookbookServe.js") assert "list.querySelectorAll('.memory-item').forEach(item =>" in source assert "item._longPressTimer" in source assert "clearTimeout(item._longPressTimer);" in source def test_main_cookbook_control_actions_use_bounded_requests(): source = _read("static/js/cookbook.js") assert source.count("_fetchCookbookUiWithTimeout('/api/model/serve'") >= 3 assert "_fetchCookbookUiWithTimeout('/api/cookbook/install-system-deps'" in source assert "_fetchCookbookUiWithTimeout('/api/cookbook/rebuild-engine'" in source assert "_fetchCookbookUiWithTimeout(`/api/cookbook/hf-gguf-files?repo_id=" in source def test_cookbook_opens_from_cached_state_before_background_sync(): source = _read("static/js/cookbook.js") assert "const _stateSync = _syncFromServer();" in source assert source.index("_renderCookbookShell();") < source.index("const _stateSync = _syncFromServer();") assert "modal.classList.remove('hidden');" in source assert "_stateSync.then((synced) =>" in source assert "never replace a form while the" in source assert "let _cookbookOpenGeneration = 0;" in source assert "_openGeneration !== _cookbookOpenGeneration" in source assert "if (activeTab === 'Running' || activeTab === 'Serve') return;" in source assert "const _initialStateSignature = _cookbookStateSignature();" in source assert "if (_cookbookStateSignature() === _initialStateSignature) return;" in source def test_advanced_scan_dismissal_is_single_document_binding(): source = _read("static/js/cookbook.js") assert "document._cookbookAdvancedDismissWired" in source assert "const currentPanel = document.getElementById('hwfit-advanced-panel');" in source assert "document.addEventListener('click', () => setAdvancedOpen(false));" not in source assert "document.addEventListener('keydown', (ev) => {\n if (ev.key === 'Escape') setAdvancedOpen(false);" not in source def test_serve_panel_removes_global_handlers_when_collapsed(): source = _read("static/js/cookbookServe.js") cookbook_source = _read("static/js/cookbook.js") assert "let _cleanupBackendDismiss = () => {};" in source assert "panel._cleanupServePanel = () => {" in source assert "_cleanupBackendDismiss?.();" in source assert "document.removeEventListener('keydown', _onEscClose, true);" in source assert "existingPanel?._cleanupServePanel?.();" in source assert "openPanel?._cleanupServePanel?.();" in source assert "clearInterval(_vramTimer);" in source assert "expandedPanel?._cleanupServePanel?.();" in cookbook_source def test_gpu_probe_popup_removes_deferred_outside_listener_on_close(): source = _read("static/js/cookbookServe.js") assert "panel._gpuProbe.outsideAttachTimer" in source assert "panel._gpuProbe.outsideHandler" in source assert "document.removeEventListener('mousedown', panel._gpuProbe.outsideHandler, true);" in source assert "_closeProbePopup();" in source def test_dependency_install_payload_keeps_env_path_for_refresh(): source = _read("static/js/cookbook.js") assert "env_path: targetEnvPath || ''" in source def test_local_dependency_probe_refreshes_user_site_visibility(): source = _read("routes/shell_routes.py") assert "importlib.invalidate_caches()" in source assert "user_site = site.getusersitepackages()" in source # addsitedir (not a bare sys.path.append) so user-site `.pth` hooks are # replayed when a package is installed into an already-running process — # otherwise setuptools' distutils shim never activates and basicsr-based # deps (realesrgan) probe as not-installed until a restart. See #4810. assert "if user_site and os.path.isdir(user_site):" in source assert "site.addsitedir(user_site)" in source def test_llama_runner_path_preserves_standard_user_bin_order(): """The runner PATH must preserve standard user $HOME/bin precedence while relying on ownership-aware reconciliation to retire Odysseus shims when native builds exist.""" source = _read("routes/cookbook_routes.py") assert ( 'export PATH="$HOME/.local/bin:$HOME/bin:$HOME/llama.cpp/build/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"' in source ) def _run_shell(tmp_path, lines, cwd=None, path=None, extra_env=None): from core.platform_compat import find_bash, git_bash_path bash = find_bash() or "bash" env = dict(os.environ, HOME=git_bash_path(tmp_path)) if path is not None: env["PATH"] = path if extra_env: env.update(extra_env) return subprocess.run( [bash, "-c", "\n".join(lines)], cwd=str(cwd) if cwd else None, env=env, capture_output=True, text=True, ) def _write_odysseus_shim(path: Path) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text( f"#!/usr/bin/env bash\n{ODYSSEUS_LLAMA_SHIM_MARKER}\nexec python3 -m llama_cpp.server \"$@\"\n", encoding="utf-8", ) path.chmod(0o755) def _write_mock_native(path: Path, succeeds: bool = True) -> None: path.parent.mkdir(parents=True, exist_ok=True) if succeeds: content = '#!/usr/bin/env bash\nif [ "$1" = "--version" ]; then echo "version: 0.1.0-dev"; exit 0; fi\necho "RUN_NATIVE: $@"\nexit 0\n' else: content = '#!/usr/bin/env bash\necho "error: missing libllama.so" >&2\nexit 127\n' path.write_text(content, encoding="utf-8") path.chmod(0o755) def _write_mock_python3(path: Path, succeeds: bool = True) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(f"#!/usr/bin/env bash\nexit {0 if succeeds else 1}\n", encoding="utf-8") path.chmod(0o755) def test_odysseus_create_llama_shim_never_overwrites_user_regular_file(tmp_path): """Creation boundary: never overwrite a non-executable user-owned regular file.""" user_file = tmp_path / "bin/llama-server" user_file.parent.mkdir(parents=True, exist_ok=True) original_content = "user configuration or binary data without marker" user_file.write_text(original_content, encoding="utf-8") lines = [] _append_llama_server_shim_definition_lines(lines) lines.append('_odysseus_create_llama_shim') res = _run_shell(tmp_path, lines) assert res.returncode != 0, "Creation should return non-zero when target is a user file" assert user_file.read_text(encoding="utf-8") == original_content, "User file must remain byte-for-byte unchanged" def test_odysseus_create_llama_shim_never_overwrites_user_symlink(tmp_path): """Creation boundary: never follow or overwrite a user-owned symlink.""" bin_dir = tmp_path / "bin" bin_dir.mkdir(parents=True, exist_ok=True) target_file = tmp_path / "user_target.txt" target_file.write_text("precious user target content", encoding="utf-8") user_symlink = bin_dir / "llama-server" user_symlink.symlink_to(target_file) lines = [] _append_llama_server_shim_definition_lines(lines) lines.append('_odysseus_create_llama_shim') res = _run_shell(tmp_path, lines) assert res.returncode != 0, "Creation should return non-zero when target is a user symlink" assert user_symlink.is_symlink(), "Symlink must remain intact" assert target_file.read_text(encoding="utf-8") == "precious user target content", "Target file must not be modified" def test_odysseus_create_llama_shim_never_overwrites_dangling_symlink(tmp_path): """Creation boundary: never overwrite a dangling user-owned symlink.""" bin_dir = tmp_path / "bin" bin_dir.mkdir(parents=True, exist_ok=True) dangling_symlink = bin_dir / "llama-server" dangling_symlink.symlink_to(tmp_path / "nonexistent_target") lines = [] _append_llama_server_shim_definition_lines(lines) lines.append('_odysseus_create_llama_shim') res = _run_shell(tmp_path, lines) assert res.returncode != 0, "Creation should return non-zero when target is a dangling symlink" assert dangling_symlink.is_symlink(), "Dangling symlink must remain intact" def test_odysseus_create_llama_shim_safely_replaces_existing_odysseus_shim(tmp_path): """Creation boundary: safely replace an existing Odysseus-owned compatibility shim.""" shim_file = tmp_path / "bin/llama-server" shim_file.parent.mkdir(parents=True, exist_ok=True) shim_file.write_text(f"#!/bin/bash\n{ODYSSEUS_LLAMA_SHIM_MARKER}\n# old shim body\n", encoding="utf-8") lines = [] _append_llama_server_shim_definition_lines(lines) lines.append('_odysseus_create_llama_shim') res = _run_shell(tmp_path, lines) assert res.returncode == 0 new_content = shim_file.read_text(encoding="utf-8") assert ODYSSEUS_LLAMA_SHIM_MARKER in new_content assert "exec python3 -m llama_cpp.server" in new_content def test_odysseus_create_llama_shim_secure_temp_prevents_symlink_attack(tmp_path): """Creation boundary: secure mktemp prevents predictable $$ temp symlink attacks. Given: - A victim file at ~/victim containing DO_NOT_TOUCH - A pre-created symlink at ~/bin/.llama-server.odysseus.tmp.$$ pointing to ~/victim - _odysseus_create_llama_shim executed within that exact same bash PID ($$) Prove: - ~/victim remains unmodified - Pre-existing crafted symlink is NOT followed or overwritten - Legitimate shim is successfully created at ~/bin/llama-server via secure temp path """ lines = [ 'victim="$HOME/victim"', 'printf "DO_NOT_TOUCH\\n" > "$victim"', 'mkdir -p "$HOME/bin"', 'old_tmp="$HOME/bin/.llama-server.odysseus.tmp.$$";', 'ln -s "$victim" "$old_tmp"', ] _append_llama_server_shim_definition_lines(lines) lines.append('_odysseus_create_llama_shim') lines.append('res=$?') lines.append('echo "CREATION_RES=$res"') res = _run_shell(tmp_path, lines) assert res.returncode == 0 assert "CREATION_RES=0" in res.stdout victim_file = tmp_path / "victim" assert victim_file.exists() assert victim_file.read_text(encoding="utf-8") == "DO_NOT_TOUCH\n", "Victim file must remain untouched" target_shim = tmp_path / "bin/llama-server" assert target_shim.exists() assert ODYSSEUS_LLAMA_SHIM_MARKER in target_shim.read_text(encoding="utf-8") assert target_shim.stat().st_mode & 0o111 != 0, "Shim must be executable" def test_stale_odysseus_shim_removed_when_python_server_unusable(tmp_path): """Scenario 1: Stale Odysseus shim + missing Python server -> owned shim removed.""" shim_file = tmp_path / "bin/llama-server" _write_odysseus_shim(shim_file) lines = [] _append_llama_server_shim_reconciliation_lines(lines) res = _run_shell(tmp_path, lines) assert res.returncode == 0 assert not shim_file.exists(), "Stale Odysseus shim must be removed when python server is unavailable" def test_healthy_odysseus_shim_retained_when_no_native_server(tmp_path): """Scenario 2: Healthy Odysseus shim + no native server -> shim preserved.""" shim_file = tmp_path / "bin/llama-server" _write_odysseus_shim(shim_file) mock_py_dir = tmp_path / "mock_bin" _write_mock_python3(mock_py_dir / "python3", succeeds=True) lines = [] _append_llama_server_shim_reconciliation_lines(lines) res = _run_shell(tmp_path, lines, path=f"{git_bash_path(mock_py_dir)}:{os.environ.get('PATH', '')}") assert res.returncode == 0 assert shim_file.exists(), "Healthy Odysseus shim must be preserved when no native server is present" def test_reconciliation_does_not_retire_healthy_shim_for_broken_native_binary(tmp_path): """Reconciliation health check: a broken native candidate executable must NOT retire a healthy Odysseus Python shim.""" shim_file = tmp_path / "bin/llama-server" _write_odysseus_shim(shim_file) _write_mock_native(tmp_path / "llama.cpp/build/bin/llama-server", succeeds=False) mock_py_dir = tmp_path / "mock_bin" _write_mock_python3(mock_py_dir / "python3", succeeds=True) lines = [f'export PATH="$HOME/llama.cpp/build/bin:{git_bash_path(mock_py_dir)}:$PATH"'] _append_llama_server_shim_reconciliation_lines(lines) res = _run_shell(tmp_path, lines) assert res.returncode == 0 assert shim_file.exists(), "Healthy Odysseus shim must NOT be retired when native binary is broken" def test_odysseus_shim_retired_when_native_server_healthy(tmp_path): """Scenario 3: Odysseus shim + healthy native server available -> native server wins.""" shim_file = tmp_path / "bin/llama-server" _write_odysseus_shim(shim_file) _write_mock_native(tmp_path / "llama.cpp/build/bin/llama-server", succeeds=True) lines = ['export PATH="$HOME/bin:$HOME/llama.cpp/build/bin:$PATH"'] _append_llama_server_shim_reconciliation_lines(lines) res = _run_shell(tmp_path, lines) assert res.returncode == 0 assert not shim_file.exists(), "Odysseus shim must be retired when healthy native server is available" def test_user_owned_bin_llama_server_never_touched(tmp_path): """Scenario 4: Arbitrary user ~/bin/llama-server -> untouched and not deprioritized.""" user_bin = tmp_path / "bin/llama-server" user_bin.parent.mkdir(parents=True, exist_ok=True) user_bin.write_text("#!/bin/bash\n# Custom user build script\nexec /opt/custom/llama-server\n", encoding="utf-8") user_bin.chmod(0o755) _write_mock_native(tmp_path / "llama.cpp/build/bin/llama-server", succeeds=True) lines = ['export PATH="$HOME/bin:$HOME/llama.cpp/build/bin:$PATH"'] _append_llama_server_shim_reconciliation_lines(lines) res = _run_shell(tmp_path, lines) assert res.returncode == 0 assert user_bin.exists(), "User-owned ~/bin/llama-server must never be deleted" assert "Custom user build script" in user_bin.read_text(encoding="utf-8") def test_reconciliation_ignores_native_binary_outside_effective_path(tmp_path): """Native candidate outside effective PATH must NOT be considered.""" shim_file = tmp_path / "bin/llama-server" _write_odysseus_shim(shim_file) # Native binary placed in /usr_custom/bin which is NOT in PATH _write_mock_native(tmp_path / "usr_custom/bin/llama-server", succeeds=True) mock_py_dir = tmp_path / "mock_bin" _write_mock_python3(mock_py_dir / "python3", succeeds=True) lines = [] _append_llama_server_shim_reconciliation_lines(lines) # PATH only contains ~/bin and mock_py_dir — usr_custom is omitted path = f"{git_bash_path(tmp_path / 'bin')}:{git_bash_path(mock_py_dir)}" res = _run_shell(tmp_path, lines, path=path) assert res.returncode == 0 assert shim_file.exists(), "Shim must NOT be retired by native binary outside effective PATH" def test_reconciliation_skips_home_bin_with_or_without_trailing_slash(tmp_path): """Reconciliation must exclude $HOME/bin and $HOME/bin/ from native candidate discovery.""" shim_file = tmp_path / "bin/llama-server" _write_odysseus_shim(shim_file) mock_py_dir = tmp_path / "mock_bin" _write_mock_python3(mock_py_dir / "python3", succeeds=True) lines = [] _append_llama_server_shim_reconciliation_lines(lines) # PATH has ~/bin/ with trailing slash path = f"{git_bash_path(tmp_path / 'bin')}/:{git_bash_path(mock_py_dir)}" res = _run_shell(tmp_path, lines, path=path) assert res.returncode == 0 assert shim_file.exists(), "Shim must NOT rediscover itself when PATH has trailing slash in $HOME/bin/" def test_reconciliation_does_not_discover_cwd_executable_when_path_omits_dot(tmp_path): """Reconciliation must NOT synthesize an empty/trailing PATH element that searches cwd. Given: - Current working directory contains an executable `llama-server` - Healthy Odysseus Python shim in ~/bin/llama-server - PATH does NOT contain '.' or cwd - No other healthy native binary on PATH Prove: - Reconciliation does not discover ./llama-server - The healthy Odysseus shim in ~/bin/llama-server is preserved """ shim_file = tmp_path / "bin/llama-server" _write_odysseus_shim(shim_file) mock_py_dir = tmp_path / "mock_bin" _write_mock_python3(mock_py_dir / "python3", succeeds=True) cwd_dir = tmp_path / "workdir" _write_mock_native(cwd_dir / "llama-server", succeeds=True) lines = [] _append_llama_server_shim_reconciliation_lines(lines) path = f"{git_bash_path(tmp_path / 'bin')}:{git_bash_path(mock_py_dir)}:/usr/bin:/bin" res = _run_shell(tmp_path, lines, cwd=cwd_dir, path=path) assert res.returncode == 0 assert shim_file.exists(), "Healthy Odysseus shim must NOT be retired by finding ./llama-server in cwd" def test_reconciliation_discovers_healthy_native_on_explicit_other_path_entry(tmp_path): """Reconciliation must discover healthy native binaries placed on explicit PATH entries.""" shim_file = tmp_path / "bin/llama-server" _write_odysseus_shim(shim_file) custom_opt_bin = tmp_path / "custom_opt/bin" _write_mock_native(custom_opt_bin / "llama-server", succeeds=True) lines = [] _append_llama_server_shim_reconciliation_lines(lines) path = f"{git_bash_path(tmp_path / 'bin')}:{git_bash_path(custom_opt_bin)}:/usr/bin:/bin" res = _run_shell(tmp_path, lines, path=path) assert res.returncode == 0 assert not shim_file.exists(), "Odysseus shim must be retired when healthy native server is on explicit PATH entry" def test_reconciliation_safely_iterates_path_with_glob_metacharacters(tmp_path): """Reconciliation must not perform pathname expansion / globbing on PATH components. Given: - A directory path containing shell glob characters: ~/custom[dir]/bin - A native binary ~/custom[dir]/bin/llama-server - A working directory containing matching glob entries - An existing Odysseus shim in ~/bin/llama-server Prove: - Reconciliation correctly parses the literal path without expanding [dir] - Discovers the healthy native server and retires the Odysseus shim """ shim_file = tmp_path / "bin/llama-server" _write_odysseus_shim(shim_file) glob_dir = tmp_path / "custom[dir]/bin" _write_mock_native(glob_dir / "llama-server", succeeds=True) cwd_dir = tmp_path / "cwd" cwd_dir.mkdir(parents=True, exist_ok=True) (cwd_dir / "customd").mkdir(parents=True, exist_ok=True) (cwd_dir / "customi").mkdir(parents=True, exist_ok=True) lines = [] _append_llama_server_shim_reconciliation_lines(lines) path = f"{git_bash_path(tmp_path / 'bin')}:{git_bash_path(glob_dir)}:/usr/bin:/bin" res = _run_shell(tmp_path, lines, cwd=cwd_dir, path=path) assert res.returncode == 0 assert not shim_file.exists(), "Reconciliation must discover literal bracketed PATH without globbing" def test_fallback_install_success_creates_usable_launcher(tmp_path): """Scenario 5: No native + no Python server -> fallback install succeeds -> usable compatibility launcher exists and resolves command -v llama-server.""" mock_py_dir = tmp_path / "mock_bin" _write_mock_python3(mock_py_dir / "python3", succeeds=True) lines = [f'export PATH="$HOME/bin:{git_bash_path(mock_py_dir)}:$PATH"'] _append_llama_server_shim_definition_lines(lines) _append_llama_server_finalization_lines(lines) lines.append('command -v llama-server') res = _run_shell(tmp_path, lines) assert res.returncode == 0 created_shim = tmp_path / "bin/llama-server" assert created_shim.exists() assert ODYSSEUS_LLAMA_SHIM_MARKER in created_shim.read_text(encoding="utf-8") assert str(tmp_path / "bin/llama-server") in res.stdout def test_failed_fallback_sets_preflight_exit_127(tmp_path): """Scenario 6: No native + failed fallback installation -> ODYSSEUS_PREFLIGHT_EXIT=127.""" lines = [ 'ODYSSEUS_PREFLIGHT_EXIT=""', 'export PATH="$HOME/bin:$PATH"', ] _append_llama_server_shim_definition_lines(lines) _append_llama_server_finalization_lines(lines) lines.append('echo "EXIT=$ODYSSEUS_PREFLIGHT_EXIT"') res = _run_shell(tmp_path, lines) assert res.returncode == 0 assert "EXIT=127" in res.stdout def test_python_server_readiness_requires_llama_cpp_server_module(): """Scenario 7 & 8: Python server readiness requires `import llama_cpp.server`, while GPU offload capability checks still use base `import llama_cpp`.""" routes_source = _read("routes/cookbook_routes.py") helpers_source = _read("routes/cookbook_helpers.py") # Windows fallback readiness check requires .server assert 'try { python -c "import llama_cpp.server" 2>$null } catch {}' in routes_source # Termux fallback readiness check requires .server assert 'if ! python3 -c "import llama_cpp.server" 2>/dev/null; then' in routes_source # Build-failure fallback pip check requires .server assert 'if [ -z "$_odysseus_llama_bin" ] && ! python3 -c "import llama_cpp.server" 2>/dev/null; then' in routes_source # Shim reconciliation and post-install finalization require .server assert 'elif python3 -c "import llama_cpp.server" 2>/dev/null; then' in helpers_source assert 'if [ -z "$_odysseus_llama_bin" ] && python3 -c "import llama_cpp.server" 2>/dev/null; then' in helpers_source # GPU offload support checks still intentionally inspect base llama_cpp assert 'llama_cpp.llama_supports_gpu_offload()' in routes_source def test_darwin_native_build_does_not_force_write_bin_symlink(): """Darwin source build must not overwrite ~/bin/llama-server with a forced symlink.""" source = _read("routes/cookbook_routes.py") assert "&& ln -sf ~/llama.cpp/build/bin/llama-server ~/bin/llama-server" not in source assert 'cmake --build build -j"$NPROC" --target llama-server' in source @pytest.mark.asyncio async def test_generated_runner_preflight_retires_stale_shim_before_backend_selection(monkeypatch, tmp_path): """Integration regression: The actual generated Cookbook runner must execute ownership reconciliation BEFORE evaluating the backend selection (`if Termux ... elif ! command -v llama-server`). Given: - Executable stale Odysseus shim in ~/bin/llama-server (with canonical marker) - Healthy native llama-server in ~/llama.cpp/build/bin/llama-server - ~/bin before ~/llama.cpp/build/bin on PATH Prove: 1. Generated runner contains reconciliation shell lines strictly before the backend branching 2. Executing the generated runner script retires the stale ~/bin/llama-server shim 3. Resolves to the healthy native binary without attempting source build or pip fallback. """ from unittest.mock import AsyncMock, MagicMock from routes.cookbook_helpers import ServeRequest import routes.cookbook_routes as cookbook_routes from starlette.requests import Request fake_home = tmp_path / "fake_home" stale_shim = fake_home / "bin/llama-server" _write_odysseus_shim(stale_shim) native_bin = fake_home / "llama.cpp/build/bin/llama-server" _write_mock_native(native_bin, succeeds=True) # Mock environment to generate the real runner file with isolated DB tmux_log_dir = tmp_path / "tmux_logs" tmux_log_dir.mkdir(parents=True) monkeypatch.setattr(cookbook_routes, "TMUX_LOG_DIR", tmux_log_dir) monkeypatch.setattr(cookbook_routes, "require_admin", lambda request: None) monkeypatch.setattr(cookbook_routes, "_binary_available", AsyncMock(return_value=True)) monkeypatch.setattr(cookbook_routes, "load_stored_hf_token", lambda **kwargs: "") monkeypatch.setattr("core.database.SessionLocal", MagicMock()) class _Proc: returncode = 0 stderr = None async def wait(self): return 0 monkeypatch.setattr(cookbook_routes.asyncio, "create_subprocess_shell", AsyncMock(return_value=_Proc())) router = cookbook_routes.setup_cookbook_routes() endpoint = None for route in router.routes: if route.path == "/api/model/serve" and "POST" in route.methods: endpoint = route.endpoint break assert endpoint is not None from types import SimpleNamespace http_req = Request({ "type": "http", "method": "POST", "path": "/api/model/serve", "headers": [], "app": SimpleNamespace(state=SimpleNamespace(auth_manager=SimpleNamespace( is_configured=True, is_admin=lambda user: user == "admin"))), }) http_req.state.current_user = "admin" http_req.state.local_model_authority = True req = ServeRequest( repo_id="test/repo", cmd="llama-server -m /dummy/model.gguf --port 8085", remote_host=None, ) resp = await endpoint(http_req, req) assert resp["ok"] is True session_id = resp["session_id"] runner_file = tmux_log_dir / f"{session_id}_run.sh" assert runner_file.exists() runner_script = runner_file.read_text(encoding="utf-8") # Assert structural ordering in the generated script: # Reconciliation must appear strictly BEFORE the Termux/backend check recon_idx = runner_script.find("Native llama-server detected — retiring Odysseus compatibility shim in ~/bin") termux_idx = runner_script.find("if [ -d /data/data/com.termux ]; then") backend_idx = runner_script.find('elif [ -z "$_odysseus_llama_bin" ]; then') assert recon_idx != -1, "Reconciliation block missing from generated runner" assert termux_idx != -1, "Termux check missing from generated runner" assert backend_idx != -1, "Backend selection missing from generated runner" assert recon_idx < termux_idx < backend_idx, "Reconciliation must execute strictly before backend selection" # Execute the generated runner preflight in a real subshell res = _run_shell(fake_home, [str(runner_file)], path=f"{git_bash_path(fake_home / 'bin')}:{git_bash_path(fake_home / 'llama.cpp/build/bin')}:/usr/bin:/bin") assert res.returncode == 0 assert not stale_shim.exists(), "Stale ~/bin/llama-server must have been retired by the generated preflight" assert "RUN_NATIVE: -m /dummy/model.gguf --port 8085" in res.stdout assert "Native llama-server not found — building from source" not in res.stdout def test_broken_first_native_candidate_healthy_second_candidate_executes_healthy(tmp_path): """Regression finding P2: When PATH contains an earlier broken candidate followed by a healthy candidate, reconciliation and launch must select and execute the healthy candidate.""" from core.platform_compat import git_bash_path broken_dir = tmp_path / "broken_bin" _write_mock_native(broken_dir / "llama-server", succeeds=False) healthy_dir = tmp_path / "healthy_bin" _write_mock_native(healthy_dir / "llama-server", succeeds=True) shim_file = tmp_path / "bin/llama-server" _write_odysseus_shim(shim_file) lines = [ f'export PATH="$HOME/bin:{git_bash_path(broken_dir)}:{git_bash_path(healthy_dir)}:/usr/bin:/bin"', ] _append_llama_server_shim_reconciliation_lines(lines) lines.append('echo "RESOLVED=$_odysseus_llama_bin"') lines.append('command -v llama-server') lines.append('llama-server -m /dummy/model.gguf --port 8080') res = _run_shell(tmp_path, lines) assert res.returncode == 0 assert not shim_file.exists(), "Stale Odysseus shim must be retired" assert f"RESOLVED={git_bash_path(healthy_dir / 'llama-server')}" in res.stdout assert str(healthy_dir / "llama-server") in res.stdout assert "RUN_NATIVE: -m /dummy/model.gguf --port 8080" in res.stdout assert "error: missing libllama.so" not in res.stdout def test_paths_with_spaces_broken_first_healthy_second_launch(tmp_path): """Regression: Paths containing spaces must be safely resolved, prioritized and launched.""" from core.platform_compat import git_bash_path broken_dir = tmp_path / "broken path with spaces/bin" _write_mock_native(broken_dir / "llama-server", succeeds=False) healthy_dir = tmp_path / "healthy path with spaces/bin" _write_mock_native(healthy_dir / "llama-server", succeeds=True) shim_file = tmp_path / "bin/llama-server" _write_odysseus_shim(shim_file) model_dir = tmp_path / "models with spaces" model_dir.mkdir(parents=True, exist_ok=True) model_file = model_dir / "my model.gguf" model_file.write_text("dummy", encoding="utf-8") lines = [ f'export PATH="$HOME/bin:{git_bash_path(broken_dir)}:{git_bash_path(healthy_dir)}:/usr/bin:/bin"', ] _append_llama_server_shim_reconciliation_lines(lines) lines.append(f'llama-server -m "{git_bash_path(model_file)}" --port 8080') res = _run_shell(tmp_path, lines) assert res.returncode == 0 assert not shim_file.exists(), "Stale shim must be retired" assert f"RUN_NATIVE: -m {git_bash_path(model_file)} --port 8080" in res.stdout assert "error: missing libllama.so" not in res.stdout def test_command_hash_invalidation_switches_from_cached_broken_to_healthy(tmp_path): """Regression: Command cache invalidation (`hash -r`) must prevent bash from reusing a previously cached lookup to an earlier broken executable.""" from core.platform_compat import git_bash_path broken_dir = tmp_path / "broken_bin" _write_mock_native(broken_dir / "llama-server", succeeds=False) healthy_dir = tmp_path / "healthy_bin" _write_mock_native(healthy_dir / "llama-server", succeeds=True) lines = [ # Populate bash hash cache with broken binary f'export PATH="{git_bash_path(broken_dir)}:/usr/bin:/bin"', 'llama-server >/dev/null 2>&1 || true', 'hash', # Now add healthy candidate later in PATH and run reconciliation f'export PATH="$HOME/bin:{git_bash_path(broken_dir)}:{git_bash_path(healthy_dir)}:/usr/bin:/bin"', ] _append_llama_server_shim_reconciliation_lines(lines) lines.append('llama-server -m /dummy/test.gguf') lines.append('hash') res = _run_shell(tmp_path, lines) assert res.returncode == 0 assert "RUN_NATIVE: -m /dummy/test.gguf" in res.stdout # Verify hash table ended with healthy binary, not broken assert str(healthy_dir / "llama-server") in res.stdout def test_broken_native_candidate_falls_back_to_healthy_python_compatibility_shim(tmp_path): """Regression: A broken native candidate must not shadow or retire a healthy Python shim.""" from core.platform_compat import git_bash_path broken_dir = tmp_path / "broken_bin" _write_mock_native(broken_dir / "llama-server", succeeds=False) shim_file = tmp_path / "bin/llama-server" _write_odysseus_shim(shim_file) mock_py_dir = tmp_path / "mock_bin" _write_mock_python3(mock_py_dir / "python3", succeeds=True) lines = [ f'export PATH="$HOME/bin:{git_bash_path(broken_dir)}:{git_bash_path(mock_py_dir)}:/usr/bin:/bin"', ] _append_llama_server_shim_reconciliation_lines(lines) lines.append('echo "RESOLVED=$_odysseus_llama_bin"') lines.append('command -v llama-server') res = _run_shell(tmp_path, lines) assert res.returncode == 0 assert shim_file.exists(), "Healthy Python compatibility shim must be preserved" assert f"RESOLVED={git_bash_path(shim_file)}" in res.stdout assert str(shim_file) in res.stdout def test_broken_native_and_missing_python_backend_sets_preflight_127(tmp_path): """Regression: When the only native candidates are broken and Python server is unavailable, finalization must fail closed with ODYSSEUS_PREFLIGHT_EXIT=127 rather than claiming readiness.""" from core.platform_compat import git_bash_path broken_dir = tmp_path / "broken_bin" _write_mock_native(broken_dir / "llama-server", succeeds=False) mock_py_dir = tmp_path / "mock_bin" _write_mock_python3(mock_py_dir / "python3", succeeds=False) lines = [ 'ODYSSEUS_PREFLIGHT_EXIT=""', f'export PATH="$HOME/bin:{git_bash_path(broken_dir)}:{git_bash_path(mock_py_dir)}:/usr/bin:/bin"', ] _append_llama_server_shim_definition_lines(lines) _append_llama_server_shim_reconciliation_lines(lines) _append_llama_server_finalization_lines(lines) lines.append('echo "EXIT=$ODYSSEUS_PREFLIGHT_EXIT"') res = _run_shell(tmp_path, lines) assert res.returncode == 0 assert "EXIT=127" in res.stdout @pytest.mark.asyncio async def test_generated_runner_broken_first_healthy_second_e2e(monkeypatch, tmp_path): """Integration regression: The full generated Cookbook runner script must launch the healthy native candidate even when an earlier broken candidate exists on PATH.""" from unittest.mock import AsyncMock, MagicMock from types import SimpleNamespace from routes.cookbook_helpers import ServeRequest import routes.cookbook_routes as cookbook_routes from starlette.requests import Request from core.platform_compat import git_bash_path fake_home = tmp_path / "fake_home" stale_shim = fake_home / "bin/llama-server" _write_odysseus_shim(stale_shim) broken_bin = fake_home / "broken/bin/llama-server" _write_mock_native(broken_bin, succeeds=False) healthy_bin = fake_home / "healthy/bin/llama-server" _write_mock_native(healthy_bin, succeeds=True) tmux_log_dir = tmp_path / "tmux_logs" tmux_log_dir.mkdir(parents=True) monkeypatch.setattr(cookbook_routes, "TMUX_LOG_DIR", tmux_log_dir) monkeypatch.setattr(cookbook_routes, "require_admin", lambda request: None) monkeypatch.setattr(cookbook_routes, "_binary_available", AsyncMock(return_value=True)) monkeypatch.setattr(cookbook_routes, "load_stored_hf_token", lambda **kwargs: "") monkeypatch.setattr("core.database.SessionLocal", MagicMock()) class _Proc: returncode = 0 stderr = None async def wait(self): return 0 monkeypatch.setattr(cookbook_routes.asyncio, "create_subprocess_shell", AsyncMock(return_value=_Proc())) router = cookbook_routes.setup_cookbook_routes() endpoint = None for route in router.routes: if route.path == "/api/model/serve" and "POST" in route.methods: endpoint = route.endpoint break assert endpoint is not None http_req = Request({ "type": "http", "method": "POST", "path": "/api/model/serve", "headers": [], "app": SimpleNamespace(state=SimpleNamespace(auth_manager=SimpleNamespace( is_configured=True, is_admin=lambda user: user == "admin"))), }) http_req.state.current_user = "admin" http_req.state.local_model_authority = True req = ServeRequest( repo_id="test/repo", cmd="llama-server -m /dummy/model.gguf --port 8090", remote_host=None, ) resp = await endpoint(http_req, req) assert resp["ok"] is True session_id = resp["session_id"] runner_file = tmux_log_dir / f"{session_id}_run.sh" assert runner_file.exists() # Broken is earlier on PATH than healthy res = _run_shell( fake_home, [str(runner_file)], path=f"{git_bash_path(fake_home / 'bin')}:{git_bash_path(broken_bin.parent)}:{git_bash_path(healthy_bin.parent)}:/usr/bin:/bin", ) assert res.returncode == 0 assert not stale_shim.exists(), "Stale shim must have been retired" assert "RUN_NATIVE: -m /dummy/model.gguf --port 8090" in res.stdout assert "error: missing libllama.so" not in res.stdout assert "Native llama-server not found — building from source" not in res.stdout