Compare commits

...
3 Commits
15 changed files with 173 additions and 32 deletions

No files matched your search

+8
View File
@@ -23,6 +23,14 @@ uv run ruff check . # lint
row and a unit test.
- Native binaries in `artifacts/` are rebuilt with `python native/build.py` (downloads the NDK).
## Adding a tested game
Tested a game on the Steam Frame? The easiest way is in FramePort: open the game → **…** → **Share working config…**.
It opens a prefilled GitHub issue with the exact recipe; after review a workflow turns it into
`catalog/games/<package>.yaml`. Without the app: [working config form](https://github.com/spoopyghosty0/frameport/issues/new?template=working-config.yml). Something doesn't run: the game's
**Report a problem…** (diagnostics zip, personal data removed) or the [problem report form](https://github.com/spoopyghosty0/frameport/issues/new?template=bug-report.yml).
After catalog changes, regenerate the games list: `python scripts/compat_list.py`.
## Translating FramePort
The app's texts (screens, help, patch and setting descriptions) can be translated; the command line, logs and
diagnostics stay English. No programming needed:
+8 -3
View File
@@ -36,11 +36,12 @@ Install games that target the Meta Quest, Android, or general PCVR onto your **V
2. **Connect the Frame** (once):
1. In FramePort click **Steam Frame → Show setup command**. Keep FramePort open; the Frame and your computer must
be on the same Wi-Fi.
2. On the Frame press the **Steam button → Power → Switch to Desktop**.
3. Open the app menu (bottom-left corner), search for **Konsole** and open it.
2. On the Frame open the **SteamVR dashboard → Launch a program → Desktop**: the Linux desktop opens on a virtual
screen.
3. Open the app menu (bottom-left corner of that desktop) → **System → Konsole** (or search for Konsole).
4. Type the command FramePort shows exactly as shown (on-screen keyboard or any USB/Bluetooth keyboard) and press
**Enter**. This will run the following [bash setup script](bootstrap/bootstrap.sh).
5. After a few seconds the desktop closes by itself and the Frame returns to its normal view; that's expected. If
5. After a few seconds the desktop closes by itself (Steam restarts once); that's expected. If
Steam asks to install **Lepton**, confirm it. FramePort shows the Frame as connected within a minute. No
password needed.
3. **Add games → Scan a folder** with your game backups (APK + OBB, or PC VR game folders).
@@ -68,6 +69,10 @@ will attempt to guess key patches. If you find a new config that works for an ap
**[List of tested games](docs/GAMES.md)**
**Tested something? Share it.** In FramePort open the game → **…** → **Share working config…** (it fills in the
recipe for you) or **Report a problem…** (attaches a diagnostics zip with personal data removed). Without the app:
[share a working config](https://github.com/spoopyghosty0/frameport/issues/new?template=working-config.yml) · [report a problem](https://github.com/spoopyghosty0/frameport/issues/new?template=bug-report.yml). Shared configs become built-in recipes for everyone.
## Built on
[OVRPort](https://github.com/Android-XR-Bridge/OVRPort) (Quest → OpenXR, originally
+3 -2
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env bash
# FramePort one-time Steam Frame setup. Run in the Frame's Desktop Mode terminal (Konsole):
# FramePort one-time Steam Frame setup. Run in the Frame's desktop terminal (SteamVR dashboard -> Launch a program
# -> Desktop, then System -> Konsole):
# curl -fsS <pc-ip>:<port>/<code> | bash
# (the FramePort app shows the exact line and serves this script while its Setup page is open).
#
@@ -128,6 +129,6 @@ if [[ ! -x "$DEVKIT_HELPER" || ! -f "$STEAM_CONFIG" ]] || \
echo "Couldn't turn it on automatically. Turn it on in Settings > System > Developer, then run this command again."
exit 1
fi
echo "Steam restarts to turn it on. That closes Desktop Mode in a few seconds and the Frame returns to its"
echo "Steam restarts to turn it on. That closes the desktop in a few seconds and the Frame returns to its"
echo "normal view; setup finishes on its own. If Steam asks to install Lepton, confirm it."
echo "Then return to FramePort on your PC: the Frame appears as connected within a minute."
+1
View File
@@ -1,6 +1,7 @@
# Tested games
Games tested on the Steam Frame with FramePort's recipes. Games not listed here may work too: FramePort suggests patches for them, and a working config can be shared from the app (**Share working config…**).
Tested a game? [Share a working config](https://github.com/spoopyghosty0/frameport/issues/new?template=working-config.yml) or [report a problem](https://github.com/spoopyghosty0/frameport/issues/new?template=bug-report.yml) (in the app: the game's **…** menu does both and fills in the details).
Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
| Game | Platform | Status | Notes |
+3 -3
View File
@@ -31,12 +31,12 @@ The Frame and the computer must be on the same network.
1. In FramePort open **Steam Frame** and click **Show setup command**.
2. First time only, on the Frame:
1. Press the **Steam button → Power → Switch to Desktop**.
2. Open the app menu (bottom-left corner), search for **Konsole** and open it.
1. Open the **SteamVR dashboard → Launch a program → Desktop**: the Linux desktop opens on a virtual screen.
2. Open the app menu (bottom-left corner of that desktop) → **System → Konsole** (or search for Konsole).
3. Type the command FramePort shows exactly as shown (on-screen keyboard or any USB/Bluetooth keyboard) and press
**Enter**. It looks like `curl -fsS 192.168.1.20:8765/1a2b3c4d | bash`: your computer's address, then a
one-time code.
4. After a few seconds the desktop closes by itself and the Frame returns to its normal view; that's expected. If
4. After a few seconds the desktop closes by itself (Steam restarts once); that's expected. If
Steam asks to install **Lepton** (Valve's Android runtime), confirm it.
FramePort connects by itself within a minute. No password is needed. The command lets FramePort in and turns on
+4
View File
@@ -13,6 +13,8 @@ from frameport.recommend import catalog # noqa: E402
STATUS = {"works": "✅ Works", "issues": "⚠️ Works with issues", "unsupported": "❌ Doesn't run"}
ORDER = {"works": 0, "issues": 1, "unsupported": 2}
ISSUES = "https://github.com/spoopyghosty0/frameport/issues/new?template="
SHARE, REPORT = ISSUES + "working-config.yml", ISSUES + "bug-report.yml"
def short(note: str) -> str:
@@ -32,6 +34,8 @@ def render() -> str:
return ("# Tested games\n\n"
"Games tested on the Steam Frame with FramePort's recipes. Games not listed here may work too: FramePort "
"suggests patches for them, and a working config can be shared from the app (**Share working config…**).\n"
f"Tested a game? [Share a working config]({SHARE}) or [report a problem]({REPORT}) (in the app: the game's "
"**…** menu does both and fills in the details).\n"
"Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.\n\n"
+ "\n".join(rows) + "\n")
+1 -1
View File
@@ -1,2 +1,2 @@
# The FramePort version: the single source (pyproject reads it via hatch; CI checks a release tag matches it).
__version__ = "0.6.1"
__version__ = "0.6.2"
+2 -1
View File
@@ -405,7 +405,8 @@ def frame_pair(timeout: float = typer.Option(600, help="seconds to wait for the
from .frame.pairing import PairingServer
srv = PairingServer().start()
typer.echo("On the Frame, open Desktop Mode → Konsole and run:\n\n " + srv.one_liner + "\n")
typer.echo("On the Frame, open the SteamVR dashboard → Launch a program → Desktop, then System → Konsole, and "
"run:\n\n " + srv.one_liner + "\n")
end = time.time() + timeout
while time.time() < end and not srv.paired:
time.sleep(1)
+6 -2
View File
@@ -46,8 +46,12 @@ def explain(exc: BaseException) -> str:
low = msg.lower()
if paramiko and isinstance(exc, paramiko.ChannelException):
return tr("The connection to the Frame is busy. Try again in a moment.")
if "authentication failed" in low:
return msg # already says what to do (run the bootstrap / enter the password)
if "authentication failed" in low or "no authentication methods available" in low:
if "password was refused" in low:
return tr("The Frame refused that password. Check it, or run the first-time setup instead: Steam Frame → "
"Show setup command.")
return tr("This Frame isn't set up for FramePort yet. Run the first-time setup: Steam Frame → Show setup "
"command.")
if is_connection_error(exc):
return tr("Can't reach your Frame. Make sure it's on, awake and on the same network, then try again.")
if isinstance(exc, OSError) and exc.errno == errno.ENOSPC:
+26 -2
View File
@@ -24,6 +24,25 @@ from ..core.paths import agent_file, ssh_dir, user_data_dir, write_atomic
REMOTE_AGENT_DIR = ".local/share/frameport/agent"
class FrameNotPaired(ConnectionError):
"""SSH works but none of FramePort's ways in was accepted: the Frame hasn't run the first-time setup (or the
password was wrong). The message keeps "authentication failed" for older callers."""
def __init__(self, tried_password: bool, details: list[str]):
self.tried_password = tried_password
self.details = details
reason = ("the password was refused" if tried_password
else "this Frame doesn't know FramePort yet")
super().__init__(f"SSH authentication failed: {reason}. Run the first-time setup on the Frame (Steam Frame → "
"Show setup command)." + (f" ({'; '.join(details)})" if details else ""))
def _no_auth_methods(exc: BaseException) -> bool:
"""paramiko ends a key/agent attempt that had nothing to offer (no SSH keys on this PC, no agent) with a plain
SSHException, not an AuthenticationException: it's a failed login all the same, not a network problem."""
return isinstance(exc, paramiko.SSHException) and "no authentication methods available" in str(exc).lower()
def app_key() -> paramiko.Ed25519Key:
path = ssh_dir() / "id_ed25519"
if not path.exists():
@@ -283,12 +302,17 @@ class Frame:
except paramiko.AuthenticationException as exc:
client.close()
errors.append(f"{attempt}: {exc}")
except paramiko.SSHException as exc:
client.close()
if not _no_auth_methods(exc):
raise
# (Reddit, 2026-10-03: this ended the loop before the password was tried)
errors.append(f"{attempt}: {exc}")
except BaseException:
client.close()
raise
else:
raise ConnectionError("SSH authentication failed (" + "; ".join(errors) + "). Run the FramePort "
"bootstrap on the Frame or enter the steamos password.")
raise FrameNotPaired(bool(self.password), errors)
client.save_host_keys(str(known))
transport = client.get_transport()
transport.set_keepalive(15)
+9 -6
View File
@@ -97,6 +97,7 @@
"Allows very large pictures (8K video, theatres) instead of quitting.": "",
"Already patched: don't change the game's files": "",
"Already patched: install as is (skip patching)": "",
"Already set up: on your network": "",
"Also in your library: {value} version": "",
"Also remove FramePort's games and files from the Frame": "",
"An ordinary Android app or game (no VR). It's installed unchanged and runs in Lepton, Valve's Android container.": "",
@@ -403,7 +404,7 @@
"It has a known blocker on the Steam Frame.": "",
"It has issues: share…": "",
"it stopped at '{value}'": "",
"It trusts this app, turns on Developer Mode and installs Lepton if needed. Turning on Developer Mode closes Desktop Mode; the setup finishes on its own and FramePort connects by itself. You only do this once.": "",
"It trusts this app, turns on Developer Mode and installs Lepton if needed. Turning on Developer Mode closes the desktop; the setup finishes on its own and FramePort connects by itself. You only do this once.": "",
"It was removed from the library.": "",
"It works: share…": "",
"Java runtime": "",
@@ -487,13 +488,13 @@
"Needs Windows (or WSL on Windows)": "",
"Neutralises Meta XR Audio's Quest-only calls (Unity/Wwise).": "",
"New folder": "",
"New Frame? Run one command on it and FramePort does the rest.": "",
"New Frame? Run one command on it and FramePort does the rest. Did this once already? Your Frame appears under step 2.": "",
"New name": "",
"Next": "",
"No activity": "",
"No artwork could be downloaded from that {source} result; the current artwork stays. Try another one.": "",
"No folders to rescan yet: add games with Scan a folder": "",
"No Frames found. New Frame? Use first-time setup below. Otherwise make sure Developer Mode is on (Settings → System → Developer) and the Frame is on the same network.": "",
"No Frames found. New Frame? Use first-time setup (step 1). Otherwise make sure Developer Mode is on (Settings → System → Developer) and the Frame is on the same network.": "",
"No games found in that folder": "",
"No limit": "",
"No matching files": "",
@@ -528,11 +529,10 @@
"Older workaround for upside-down quads: rotates them 180° (quads are single-sided; prefer flip_emul).": "",
"On Frame": "",
"On the Frame, place Revive's runtime (FramePort's Revive, or the repack's bundled LibRevive) where the game's Oculus SDK looks for LibOVRRT so it can load a VR runtime (pure runtime substitution — what Revive's LoadLibrary hook does on Windows; that hook doesn't work under Proton on the Frame). It does NOT bypass the Oculus runtime signature check: a game that verifies the runtime's signature will still refuse it. Ignored on this PC (Revive's hook handles it there).": "",
"On the Frame: Steam button → Power → Switch to Desktop, open Konsole and run:": "",
"On the Frame: SteamVR dashboard → Launch a program → Desktop, then app menu → System → Konsole, and run:": "",
"On this PC": "",
"On this PC, launch the game through Revive's OpenVR backend, which talks straight to SteamVR — the original, most reliable Revive path. On by default for PC installs. Turn it off to use Revive's newer OpenXR backend instead. Ignored on the Frame (which always uses OpenXR).": "",
"On this PC, runs Revive through SteamVR (works with more headsets).": "",
"On your network": "",
"on: {names}": "",
"One PC VR game folder": "",
"Only FramePort's entry is removed. Your game files and anything installed stay.": "",
@@ -546,7 +546,7 @@
"Opens this player's storage on the Frame (Files tab): upload videos into the folder it lists": "",
"OpenXR runtime": "",
"Or paste text to type it in one go": "",
"Or turn on Developer Mode on the Frame (Settings → System → Developer): it then shows up above. Open Settings → Developer → Pair new host on the Frame, click Connect here and approve FramePort. That way needs no connection into this computer.": "",
"Or turn on Developer Mode on the Frame (Settings → System → Developer): it then shows up under step 2. Open Settings → Developer → Pair new host on the Frame, click Connect there and approve FramePort. That way needs no connection into this computer.": "",
"Override the guardian depth (0 = use guardian, min 1.5 m).": "",
"Override the guardian width (0 = use guardian, min 1.5 m).": "",
"OVRPort converts Quest games from Meta's own VR APIs to standard OpenXR. These are its optional patches.": "",
@@ -813,6 +813,7 @@
"The Frame is charging: continuing the installs.": "",
"the Frame isn't connected": "",
"The Frame isn't connected": "",
"The Frame refused that password. Check it, or run the first-time setup instead: Steam Frame → Show setup command.": "",
"The Frame went offline: {exc}": "",
"The Frame's battery is at {pct} % and not charging. Plug it in (a strong charger, not a PC port): long installs can drain it, and FramePort pauses them at {pause} %.": "",
"The Frame's battery is at {pct} %: installs paused so it doesn't switch off mid-upload. Plug it in; the queue continues by itself.": "",
@@ -837,6 +838,7 @@
"This computer's keyboard works as a keyboard on your Frame while this window is open. In the headset, select a text field (in a game or app, in Steam or on the desktop), then type here.": "",
"This deletes the {what} on the Frame. It can't be undone.": "",
"This folder is empty. Upload files with the buttons above.": "",
"This Frame isn't set up for FramePort yet. Run the first-time setup: Steam Frame → Show setup command.": "",
"This game can't run on the Steam Frame.": "",
"This game is marked \"Can't run\" on the Steam Frame:": "",
"This game's folder has several programs and FramePort isn't sure which one starts the game. Open the game (or right-click → Change executable) to pick it.": "",
@@ -969,6 +971,7 @@
"Your custom recipe": "",
"your Frame": "",
"Your Frame is back: continuing": "",
"Your Frame isn't set up for FramePort yet. Run the first-time setup once (step 1).": "",
"Your headset, what it's ready for and what's installed": "",
"Your own game files aren't touched.": "",
"Your own tags (filled) can be anything you like. Outlined tags are added automatically from the engine, VR API and store genres. Filter the library by either.": "",
+12 -2
View File
@@ -393,8 +393,13 @@ class FramePortApp:
else (lambda e: self.show_activity(True)))
elif job.state == "failed":
msg = tr("{title} failed: {error}").format(title=job.title, error=job.error)
self.toast(msg, error=True, action=tr("Details"),
on_action=lambda e: self.show_activity(True))
pkg = job.package
if job.kind in ("install", "test") and pkg: # one click to the diagnostics + prefilled issue
self.toast(msg, error=True, action=tr("Report a problem"),
on_action=lambda e: self.report_problem_dialog(pkg))
else:
self.toast(msg, error=True, action=tr("Details"),
on_action=lambda e: self.show_activity(True))
self.refresh_view()
if job.kind == "scan" and self.exe_queue:
self.page.run_thread(self.next_exe_choice)
@@ -1466,9 +1471,14 @@ class FramePortApp:
pass
if not quiet:
self.toast(tr("Connected to {label}").format(label=target.label))
self._not_paired = False
except Exception as exc: # noqa: BLE001
from ..frame.connection import FrameNotPaired
self.frame_state = "offline"
self.frame_info = None
# reachable, but it doesn't let FramePort in: the Frame page points at the first-time setup
self._not_paired = isinstance(exc, FrameNotPaired)
if not quiet:
self.toast(tr("Couldn't connect: {exc}").format(exc=explain(exc)), error=True)
if self.route[0] in ("frame", "library", "game", "welcome"):
+17 -9
View File
@@ -173,7 +173,7 @@ class FrameView:
devkit=f.source == "devkit")),
], spacing=T.S3), padding=ft.Padding(T.S3, T.px(8), T.S2, T.px(8)), bgcolor=T.SURFACE_2,
border_radius=T.RADIUS_SM))
found.controls = items or [C.body(tr("No Frames found. New Frame? Use first-time setup below. Otherwise "
found.controls = items or [C.body(tr("No Frames found. New Frame? Use first-time setup (step 1). Otherwise "
"make sure Developer Mode is on (Settings → System → Developer) and "
"the Frame is on the same network."))]
found.controls.append(C.ghost(tr("Search again"), ft.Icons.REFRESH_ROUNDED, lambda e: app.run_bg(discover)))
@@ -215,7 +215,8 @@ class FrameView:
discovery), which used to close it."""
line = app.pairing.one_liner
pair_box.controls = [
C.body(tr("On the Frame: Steam button → Power → Switch to Desktop, open Konsole and run:"), T.TEXT),
C.body(tr("On the Frame: SteamVR dashboard → Launch a program → Desktop, then app menu → System → "
"Konsole, and run:"), T.TEXT),
ft.Container(ft.Row([ft.Text(line, font_family="monospace", selectable=True, size=T.px(12),
color=T.TEXT, expand=True),
C.icon_btn(ft.Icons.CONTENT_COPY_ROUNDED, tr("Copy"), lambda e: app.copy(line))]),
@@ -225,7 +226,7 @@ class FrameView:
C.meta(tr("Waiting for your Frame… (code {code})").format(code=app.pairing.code))],
spacing=T.S2),
C.meta(tr("It trusts this app, turns on Developer Mode and installs Lepton if needed. Turning on "
"Developer Mode closes Desktop Mode; the setup finishes on its own and FramePort connects "
"Developer Mode closes the desktop; the setup finishes on its own and FramePort connects "
"by itself. You only do this once.")),
]
hint = getattr(app.pairing, "hint", "")
@@ -235,8 +236,8 @@ class FrameView:
weight=ft.FontWeight.W_600),
C.body(hint, T.TEXT),
C.meta(tr("Or turn on Developer Mode on the Frame (Settings → System → Developer): it then shows "
"up above. Open Settings → Developer → Pair new host on the Frame, click Connect here "
"and approve FramePort. That way needs no connection into this computer.")),
"up under step 2. Open Settings → Developer → Pair new host on the Frame, click Connect "
"there and approve FramePort. That way needs no connection into this computer.")),
], spacing=T.S2), "warn"))
if update:
pair_box.update()
@@ -252,7 +253,12 @@ class FrameView:
width=T.px(240), tooltip=C.tip(HELP["password"]), **style)
saved = saved_targets()
offline = None
if saved and app.frame_state == "offline":
if getattr(app, "_not_paired", False): # it answered, but doesn't know FramePort yet
offline = C.callout(ft.Row([
C.body(tr("Your Frame isn't set up for FramePort yet. Run the first-time setup once (step 1)."), T.TEXT,
expand=True),
C.primary(tr("Show setup command"), ft.Icons.TERMINAL_ROUNDED, pair)]), "warn")
elif saved and app.frame_state == "offline":
unreachable = tr("{label} ({host}) isn't reachable. Make sure it's switched on and on the same "
"network.").format(label=saved[0].label, host=saved[0].host)
offline = C.callout(ft.Row([C.body(unreachable, T.TEXT, expand=True),
@@ -280,10 +286,12 @@ class FrameView:
return ft.Column([
app.top_bar(tr("Connect your Steam Frame"), tr("FramePort installs games on the Frame over your network")),
*([offline] if offline else []),
step(1, tr("On your network"), tr("Frames in Developer Mode show up here."), found, help="developer_mode"),
step(2, tr("First-time setup"), tr("New Frame? Run one command on it and FramePort does the rest."),
C.secondary(tr("Show setup command"), ft.Icons.TERMINAL_ROUNDED, pair), pair_box,
step(1, tr("First-time setup"), tr("New Frame? Run one command on it and FramePort does the rest. Did this "
"once already? Your Frame appears under step 2."),
C.primary(tr("Show setup command"), ft.Icons.TERMINAL_ROUNDED, pair), pair_box,
help="first_time_setup"),
step(2, tr("Already set up: on your network"), tr("Frames in Developer Mode show up here."), found,
help="developer_mode"),
step(3, tr("Enter the address"), tr("If you know the Frame's address."),
ft.Row([addr, pw, C.primary(tr("Connect"),
on_click=lambda e: app.connect_manual(addr.value, pw.value))],
+71
View File
@@ -0,0 +1,71 @@
"""Logging in to the Frame (Reddit 2026-10-03: "Couldn't connect: no authentication methods available" even with the
right password). paramiko ends the key/agent attempt with a plain SSHException when this PC has no SSH keys; that must
not stop the password attempt, and the final error must say what to do."""
import paramiko
import pytest
from frameport.errors import explain
from frameport.frame import connection as C
class _Client:
"""Stands in for paramiko.SSHClient: `script` maps attempt kind -> exception (or None = success)."""
script: dict = {}
tried: list = []
def load_host_keys(self, path):
pass
def set_missing_host_key_policy(self, policy):
pass
def close(self):
pass
def save_host_keys(self, path):
pass
def connect(self, pkey=None, password=None, allow_agent=False, look_for_keys=False, **kw):
kind = "password" if password else "agent" if allow_agent else "app_key"
_Client.tried.append(kind)
exc = _Client.script.get(kind)
if exc is not None:
raise exc
@pytest.fixture
def fake_ssh(monkeypatch):
monkeypatch.setattr(C.paramiko, "SSHClient", _Client)
monkeypatch.setattr(C, "app_keys", lambda: ["key"])
_Client.tried = []
return _Client
NO_METHODS = paramiko.SSHException("No authentication methods available")
def test_no_ssh_keys_on_the_pc_still_tries_the_password(fake_ssh, monkeypatch):
fake_ssh.script = {"app_key": paramiko.AuthenticationException("Authentication failed."), "agent": NO_METHODS,
"password": paramiko.AuthenticationException("Authentication failed.")}
frame = C.Frame(C.FrameTarget("10.0.0.2"), password="secret")
with pytest.raises(C.FrameNotPaired) as err:
frame.connect()
assert fake_ssh.tried == ["app_key", "agent", "password"]
assert err.value.tried_password and "password was refused" in str(err.value)
assert "refused that password" in explain(err.value)
def test_unpaired_frame_without_password_explains_the_setup(fake_ssh):
fake_ssh.script = {"app_key": paramiko.AuthenticationException("Authentication failed."), "agent": NO_METHODS}
with pytest.raises(C.FrameNotPaired) as err:
C.Frame(C.FrameTarget("10.0.0.2")).connect()
assert "authentication failed" in str(err.value).lower() # app.connect's devkit fallback looks for this
assert "first-time setup" in explain(err.value) and "isn't set up" in explain(err.value)
assert explain(NO_METHODS).startswith("This Frame isn't set up")
def test_network_errors_still_end_the_attempts(fake_ssh):
fake_ssh.script = {"app_key": paramiko.SSHException("Error reading SSH protocol banner")}
with pytest.raises(paramiko.SSHException, match="banner"):
C.Frame(C.FrameTarget("10.0.0.2"), password="x").connect()
assert fake_ssh.tried == ["app_key"]
+2 -1
View File
@@ -37,6 +37,7 @@ def test_known_failures(exc, start):
def test_other_errors_keep_their_message():
auth = ConnectionError("SSH authentication failed (bad key). Run the FramePort bootstrap on the Frame.")
assert explain(auth) == str(auth) and not is_connection_error(auth) # already says what to do
# login refused: points at the first-time setup, and isn't a "Frame went away" (the queue mustn't wait for it)
assert explain(auth).startswith("This Frame isn't set up") and not is_connection_error(auth)
assert explain(AgentFailed("Lepton is not installed (Developer Mode → …)")).startswith("Lepton is not installed")
assert explain(RuntimeError()) == "RuntimeError"