Files
app/backend/tests/test_cli.py
T
stroblmeandClaude Fable 5 99f6530698 One installation per project, and no login to reach it
Two things a local install should not have asked for.

`fluksio serve` now signs you in. Logging in to your own machine was a
formality — the password was printed by the same process that would have
checked it, and the database it authenticates against sits in the directory
the token goes into — so `serve` mints the token itself and says where it put
it. `fluksio login` is left for an engine somewhere else.

And an installation is `.fluksio` beside the code, found the way `.git` is,
rather than one `~/.fluksio` for the machine. A repository with its own venv
was already getting its own engine; it now gets its own flows, run history and
token too, instead of three repositories sharing one database and fighting
over one port. `--global` asks for the shared one, `--data-dir` still names
any directory, and when both exist the banner says which you are looking at
and how to reach the other.

The directory ignores itself from within — a `.gitignore` of `*`, the way uv
writes one into `.venv` — because it holds a credential and a database, and
neither belongs in anybody's history. The token is written mode 600. A login
an older version wrote to ~/.config/fluksio is still read, so nothing that
worked stops working.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ue1tkFWB1bcGy3aWhCKpU
2026-08-24 16:13:35 +02:00

119 lines
3.9 KiB
Python

"""The command line, and the one ordering it depends on."""
import subprocess
import sys
from pathlib import Path
from fluksio.cli import load_or_create_secret_key
def test_importing_the_cli_does_not_build_the_settings() -> None:
"""`_configure_environment` has to run before anything reads a setting.
The settings are built on the first import of `fluksio.core.config`, and
every engine module reaches it within an import or two. If importing the
CLI pulled it in, `DATA_DIR` would be fixed at whatever directory the
command was run from — which is how the database ends up in the cwd.
"""
leaked = subprocess.run(
[
sys.executable,
"-c",
"import fluksio.cli, sys; print('fluksio.core.config' in sys.modules)",
],
capture_output=True,
text=True,
check=True,
)
assert leaked.stdout.strip() == "False", leaked.stdout
def test_the_secret_key_is_kept_rather_than_regenerated(tmp_path: Path) -> None:
"""A new key each start would sign out every session and orphan secrets.enc."""
path = tmp_path / "secret_key"
first = load_or_create_secret_key(path)
assert load_or_create_secret_key(path) == first
assert path.stat().st_mode & 0o777 == 0o600
def test_the_store_works_without_git(tmp_path: Path, monkeypatch) -> None:
"""A pip install on a locked-down host may have no git.
Flows are files, and that is what the store is for; the history is the part
that needs git. Losing it must not be a refusal to start.
"""
import subprocess as sp
from fluksio.flow.store import FlowStore
real_run = sp.run
def no_git(cmd, *args, **kwargs):
if cmd and cmd[0] == "git":
raise FileNotFoundError(2, "No such file or directory", "git")
return real_run(cmd, *args, **kwargs)
monkeypatch.setattr(sp, "run", no_git)
monkeypatch.setattr(FlowStore, "_warned_no_git", False)
store = FlowStore(tmp_path / "flows")
assert store.head() == ""
store.write_requirements("numpy\n")
assert store.read_requirements() == "numpy\n"
def test_run_arguments_are_typed_by_the_flow_they_are_for() -> None:
"""`--lr 0.05` is a float because the flow says `lr` is one."""
from fluksio.sdk import SyncError
from fluksio.sdk.cli import _params
definition = {
"inputs": [
{"spec": {"name": "lr", "dtype": "float"}},
{"spec": {"name": "epochs", "dtype": "int"}},
{"spec": {"name": "resume", "dtype": "bool"}},
]
}
assert _params(definition, ["--lr", "0.05", "--epochs", "3", "--resume"]) == {
"lr": 0.05,
"epochs": 3,
"resume": True,
}
assert _params(definition, ["--lr=1e-4"]) == {"lr": 0.0001}
import pytest
with pytest.raises(SyncError, match="not an input of this flow"):
_params(definition, ["--nonesuch", "1"])
def test_serve_uses_the_installation_the_directory_belongs_to(
tmp_path: Path, monkeypatch
) -> None:
"""A repository with its own venv gets its own engine, not the machine's."""
from fluksio import cli
monkeypatch.chdir(tmp_path)
monkeypatch.setattr(cli, "DEFAULT_HOME", tmp_path / "home" / ".fluksio")
# Nothing above it yet: one is made here rather than in the home directory.
made = cli._data_dir(None)
assert made == (tmp_path / ".fluksio").resolve()
assert (made / ".gitignore").exists()
# And is then found again from anywhere below it.
deep = tmp_path / "src" / "deep"
deep.mkdir(parents=True)
monkeypatch.chdir(deep)
assert cli._data_dir(None) == made
# `--global` asks for the shared one even so.
assert (
cli._data_dir(None, shared=True) == (tmp_path / "home" / ".fluksio").resolve()
)
# And `--data-dir` still names any directory outright.
named = cli._data_dir(str(tmp_path / "named"))
assert named == (tmp_path / "named").resolve()