The installer's environment variables kept the ThoughtSync-era TS_ prefix after the rename to Inkwell. They are now INKWELL_*, as is the INKWELL_SERVER_DEFAULT line the server fills in when it serves the script. The old names are not read any more; the operator approved the clean cut. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
124 lines
4.7 KiB
Python
124 lines
4.7 KiB
Python
import pytest
|
|
|
|
from inkwell import installer, settings
|
|
from inkwell.app import create_app
|
|
from inkwell.installer import PLACEHOLDER, SOURCE_SCRIPT, render, server_address
|
|
|
|
# DB-free, like test_client_dist: the route reads `public_url` from the live cache
|
|
# rather than from the database, which is what makes it testable here.
|
|
|
|
SCRIPT = f"#!/bin/sh\nset -eu\n{PLACEHOLDER}\nserver=\"${{INKWELL_SERVER:-$INKWELL_SERVER_DEFAULT}}\"\n"
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _defaults():
|
|
settings.reset_live()
|
|
yield
|
|
settings.reset_live()
|
|
|
|
|
|
@pytest.fixture
|
|
def app():
|
|
return create_app()
|
|
|
|
|
|
def test_the_published_script_carries_the_line_the_server_rewrites_exactly_once():
|
|
"""The forge-served script and the server-served one are the same file; this is
|
|
the one line that differs. Edit it and the server stops serving rather than
|
|
serving a script with no address in it — this test is what says so first."""
|
|
assert SOURCE_SCRIPT.read_text(encoding="utf-8").count(PLACEHOLDER) == 1
|
|
|
|
|
|
def test_render_writes_the_address_into_the_one_variable():
|
|
body = render(SCRIPT, "https://notes.example.com")
|
|
assert "INKWELL_SERVER_DEFAULT='https://notes.example.com'" in body
|
|
assert PLACEHOLDER not in body
|
|
# Nothing else moved.
|
|
assert body.replace("INKWELL_SERVER_DEFAULT='https://notes.example.com'", PLACEHOLDER) == SCRIPT
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"address",
|
|
[
|
|
"https://notes.example.com/sub/path",
|
|
"http://192.168.1.20:5000",
|
|
"https://notes.example.com:8443",
|
|
],
|
|
)
|
|
def test_an_ordinary_address_is_accepted(address):
|
|
assert render(SCRIPT, address) is not None
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"address",
|
|
[
|
|
# Each is a way out of the single quotes, or a second command.
|
|
"https://x.example.com'; rm -rf ~; '",
|
|
"https://x.example.com/$(id)",
|
|
"https://x.example.com/`id`",
|
|
"https://x.example.com\nrm -rf ~",
|
|
"https://x.example.com/a b",
|
|
"https://x.example.com/;id",
|
|
# Not an address at all.
|
|
"ftp://x.example.com",
|
|
"x.example.com",
|
|
"",
|
|
],
|
|
)
|
|
def test_an_address_that_could_mean_something_to_a_shell_is_refused(address):
|
|
assert render(SCRIPT, address) is None
|
|
|
|
|
|
@pytest.mark.parametrize("script", ["#!/bin/sh\n", SCRIPT + PLACEHOLDER + "\n"])
|
|
def test_a_script_without_exactly_one_placeholder_is_not_served(script):
|
|
assert render(script, "https://notes.example.com") is None
|
|
|
|
|
|
def test_the_public_address_setting_beats_the_request_host():
|
|
"""Behind a reverse proxy the Host this request arrived with may be an internal
|
|
one; Settings → Public address is what the operator says people use."""
|
|
assert server_address("https://notes.example.com", "http", "inkwell:5000") == "https://notes.example.com"
|
|
|
|
|
|
def test_without_the_setting_the_request_address_is_used():
|
|
assert server_address("", "https", "notes.example.com") == "https://notes.example.com"
|
|
|
|
|
|
def test_a_trailing_slash_is_dropped():
|
|
assert server_address("https://notes.example.com/", "http", "ignored") == "https://notes.example.com"
|
|
|
|
|
|
def test_a_host_header_that_is_not_an_address_yields_none():
|
|
assert server_address("", "http", "evil.example.com'$(id)") is None
|
|
|
|
|
|
async def test_the_route_serves_the_script_pointed_at_this_server(app):
|
|
resp = await app.test_client().get("/install.sh", headers={"Host": "notes.example.com"})
|
|
assert resp.status_code == 200
|
|
assert resp.mimetype == "text/plain"
|
|
body = await resp.get_data(as_text=True)
|
|
assert "INKWELL_SERVER_DEFAULT='http://notes.example.com'" in body
|
|
assert body.startswith("#!/bin/sh")
|
|
|
|
|
|
async def test_the_route_uses_the_public_address_when_one_is_set(app, monkeypatch):
|
|
monkeypatch.setitem(settings._live, "public_url", "https://notes.example.com")
|
|
resp = await app.test_client().get("/install.sh", headers={"Host": "inkwell:5000"})
|
|
assert "INKWELL_SERVER_DEFAULT='https://notes.example.com'" in await resp.get_data(as_text=True)
|
|
|
|
|
|
async def test_the_route_refuses_rather_than_serve_an_unsafe_address(app, monkeypatch):
|
|
# A Public address saved before it was validated this strictly: Settings only
|
|
# checks the scheme, so this is reachable.
|
|
monkeypatch.setitem(settings._live, "public_url", "https://notes.example.com/'$(id)'")
|
|
resp = await app.test_client().get("/install.sh", headers={"Host": "notes.example.com"})
|
|
assert resp.status_code == 500
|
|
assert "INKWELL_SERVER_DEFAULT" not in await resp.get_data(as_text=True)
|
|
|
|
|
|
async def test_a_server_with_no_copy_of_the_script_404s(app, monkeypatch, tmp_path):
|
|
monkeypatch.setattr(installer, "BAKED_SCRIPT", tmp_path / "absent")
|
|
monkeypatch.setattr(installer, "SOURCE_SCRIPT", tmp_path / "also-absent")
|
|
resp = await app.test_client().get("/install.sh")
|
|
assert resp.status_code == 404
|