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=\"${{TS_SERVER:-$TS_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 "TS_SERVER_DEFAULT='https://notes.example.com'" in body assert PLACEHOLDER not in body # Nothing else moved. assert body.replace("TS_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 "TS_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 "TS_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 "TS_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