mirror of
https://github.com/d9pouces/id_configurator.git
synced 2026-11-09 01:47:21 +01:00
bf56cdfcd2
Signed-off-by: d9pouces <github@19pouces.net>
230 lines
7.6 KiB
Python
230 lines
7.6 KiB
Python
import pytest
|
|
|
|
from id_configurator.config import Configuration
|
|
from id_configurator.context import Context
|
|
|
|
CONFIG = Configuration.model_validate(
|
|
{"uuid": "12345678-1234-5678-1234-567812345678", "organisation": "InterDiode"}
|
|
)
|
|
|
|
|
|
def make_context(
|
|
tmp_path, with_src: bool = True, users=None, auto_users: bool = False
|
|
) -> Context:
|
|
dst = tmp_path / "dst"
|
|
dst.mkdir()
|
|
src = None
|
|
if with_src:
|
|
src = tmp_path / "src"
|
|
src.mkdir()
|
|
return Context(config=CONFIG, dst=dst, src=src, users=users, auto_users=auto_users)
|
|
|
|
|
|
def test_dst_path_within_destination(tmp_path) -> None:
|
|
context = make_context(tmp_path)
|
|
resolved = context.dst_path("etc/foo.conf")
|
|
assert resolved == context.dst / "etc" / "foo.conf"
|
|
|
|
|
|
def test_dst_path_rejects_escape(tmp_path) -> None:
|
|
context = make_context(tmp_path)
|
|
with pytest.raises(ValueError, match="refusing to write outside"):
|
|
context.dst_path("../escape.conf")
|
|
|
|
|
|
def test_src_path_without_src(tmp_path) -> None:
|
|
context = make_context(tmp_path, with_src=False)
|
|
assert context.src_path("etc/foo.conf") is None
|
|
|
|
|
|
def test_src_path_with_src(tmp_path) -> None:
|
|
context = make_context(tmp_path)
|
|
resolved = context.src_path("etc/foo.conf")
|
|
assert resolved == context.src / "etc" / "foo.conf"
|
|
|
|
|
|
def test_write_file_creates_parents_and_content(tmp_path) -> None:
|
|
context = make_context(tmp_path)
|
|
path = context.write_file("etc/nested/foo.conf", b"hello")
|
|
assert path.read_bytes() == b"hello"
|
|
assert path.stat().st_mode & 0o777 == 0o644
|
|
|
|
|
|
def test_read_src_file_without_src(tmp_path) -> None:
|
|
context = make_context(tmp_path, with_src=False)
|
|
assert context.read_src_file("etc/foo.conf") is None
|
|
|
|
|
|
def test_read_src_file_missing(tmp_path) -> None:
|
|
context = make_context(tmp_path)
|
|
assert context.read_src_file("etc/missing.conf") is None
|
|
|
|
|
|
def test_read_src_file_present(tmp_path) -> None:
|
|
context = make_context(tmp_path)
|
|
(context.src / "foo.conf").write_bytes(b"existing")
|
|
assert context.read_src_file("foo.conf") == b"existing"
|
|
|
|
|
|
def test_iter_src_dir_without_src(tmp_path) -> None:
|
|
context = make_context(tmp_path, with_src=False)
|
|
assert context.iter_src_dir("some/dir") == []
|
|
|
|
|
|
def test_iter_src_dir_missing_dir(tmp_path) -> None:
|
|
context = make_context(tmp_path)
|
|
assert context.iter_src_dir("missing-dir") == []
|
|
|
|
|
|
def test_iter_src_dir_lists_only_files(tmp_path) -> None:
|
|
context = make_context(tmp_path)
|
|
target = context.src / "certs"
|
|
target.mkdir()
|
|
(target / "a.crt").write_bytes(b"a")
|
|
(target / "b.crt").write_bytes(b"b")
|
|
(target / "subdir").mkdir()
|
|
result = context.iter_src_dir("certs")
|
|
assert [entry.name for entry in result] == ["a.crt", "b.crt"]
|
|
|
|
|
|
def test_glob_src_without_src(tmp_path) -> None:
|
|
context = make_context(tmp_path, with_src=False)
|
|
assert context.glob_src("usr/lib/python3.*") == []
|
|
|
|
|
|
def test_glob_src_matches_pattern(tmp_path) -> None:
|
|
context = make_context(tmp_path)
|
|
(context.src / "usr/lib/python3.11").mkdir(parents=True)
|
|
(context.src / "usr/lib/python3.12").mkdir(parents=True)
|
|
(context.src / "usr/lib/python-legacy").mkdir(parents=True)
|
|
result = context.glob_src("usr/lib/python3.*")
|
|
assert [entry.name for entry in result] == ["python3.11", "python3.12"]
|
|
|
|
|
|
def test_users_defaults_to_empty_list(tmp_path) -> None:
|
|
context = make_context(tmp_path, with_src=False)
|
|
assert context.users == []
|
|
|
|
|
|
def test_users_keeps_explicit_names_in_order_without_duplicates(tmp_path) -> None:
|
|
context = make_context(tmp_path, with_src=False, users=["alice", "bob", "alice"])
|
|
assert context.users == ["alice", "bob"]
|
|
|
|
|
|
def test_auto_users_expands_from_passwd_excluding_disabled_shells(tmp_path) -> None:
|
|
dst = tmp_path / "dst"
|
|
dst.mkdir()
|
|
src = tmp_path / "src"
|
|
(src / "etc").mkdir(parents=True)
|
|
(src / "etc/passwd").write_text(
|
|
"root:x:0:0:root:/root:/bin/bash\n"
|
|
"alice:x:1000:1000:Alice:/home/alice:/bin/bash\n"
|
|
"daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n",
|
|
encoding="utf-8",
|
|
)
|
|
context = Context(config=CONFIG, dst=dst, src=src, auto_users=True)
|
|
# root is always included despite its uid 0; daemon (nologin shell) is
|
|
# excluded as a system account.
|
|
assert context.users == ["root", "alice"]
|
|
|
|
|
|
def test_auto_users_excludes_root_with_disabled_shell(tmp_path) -> None:
|
|
dst = tmp_path / "dst"
|
|
dst.mkdir()
|
|
src = tmp_path / "src"
|
|
(src / "etc").mkdir(parents=True)
|
|
(src / "etc/passwd").write_text(
|
|
"root:x:0:0:root:/root:/usr/sbin/nologin\n"
|
|
"alice:x:1000:1000:Alice:/home/alice:/bin/bash\n",
|
|
encoding="utf-8",
|
|
)
|
|
context = Context(config=CONFIG, dst=dst, src=src, auto_users=True)
|
|
assert context.users == ["alice"]
|
|
|
|
|
|
def test_auto_users_excludes_uid_below_default_uid_min(tmp_path) -> None:
|
|
dst = tmp_path / "dst"
|
|
dst.mkdir()
|
|
src = tmp_path / "src"
|
|
(src / "etc").mkdir(parents=True)
|
|
(src / "etc/passwd").write_text(
|
|
"svc:x:999:999:service account:/home/svc:/bin/bash\n"
|
|
"alice:x:1000:1000:Alice:/home/alice:/bin/bash\n",
|
|
encoding="utf-8",
|
|
)
|
|
context = Context(config=CONFIG, dst=dst, src=src, auto_users=True)
|
|
assert context.users == ["alice"]
|
|
|
|
|
|
def test_auto_users_honors_uid_min_from_login_defs(tmp_path) -> None:
|
|
dst = tmp_path / "dst"
|
|
dst.mkdir()
|
|
src = tmp_path / "src"
|
|
(src / "etc").mkdir(parents=True)
|
|
(src / "etc/passwd").write_text(
|
|
"svc:x:999:999:service account:/home/svc:/bin/bash\n"
|
|
"alice:x:1000:1000:Alice:/home/alice:/bin/bash\n",
|
|
encoding="utf-8",
|
|
)
|
|
(src / "etc/login.defs").write_text("UID_MIN\t500\n", encoding="utf-8")
|
|
context = Context(config=CONFIG, dst=dst, src=src, auto_users=True)
|
|
assert context.users == ["svc", "alice"]
|
|
|
|
|
|
def test_auto_users_combines_with_explicit_names_without_duplicates(tmp_path) -> None:
|
|
dst = tmp_path / "dst"
|
|
dst.mkdir()
|
|
src = tmp_path / "src"
|
|
src.mkdir()
|
|
(src / "etc").mkdir()
|
|
(src / "etc/passwd").write_text(
|
|
"root:x:0:0:root:/root:/bin/bash\nalice:x:1000:1000:Alice:/home/alice:/bin/bash\n",
|
|
encoding="utf-8",
|
|
)
|
|
context = Context(
|
|
config=CONFIG, dst=dst, src=src, users=["bob", "alice"], auto_users=True
|
|
)
|
|
# explicit names come first, in order; auto-detected names are appended,
|
|
# skipping "alice" since it's already present.
|
|
assert context.users == ["bob", "alice", "root"]
|
|
|
|
|
|
def test_auto_users_false_ignores_passwd(tmp_path) -> None:
|
|
dst = tmp_path / "dst"
|
|
dst.mkdir()
|
|
src = tmp_path / "src"
|
|
(src / "etc").mkdir(parents=True)
|
|
(src / "etc/passwd").write_text(
|
|
"alice:x:1000:1000:Alice:/home/alice:/bin/bash\n", encoding="utf-8"
|
|
)
|
|
context = Context(config=CONFIG, dst=dst, src=src)
|
|
assert context.users == []
|
|
|
|
|
|
def test_auto_users_without_src_is_empty(tmp_path) -> None:
|
|
context = make_context(tmp_path, with_src=False, auto_users=True)
|
|
assert context.users == []
|
|
|
|
|
|
def test_home_dir_for_root(tmp_path) -> None:
|
|
context = make_context(tmp_path, with_src=False)
|
|
assert context.home_dir("root") == "root"
|
|
|
|
|
|
def test_home_dir_falls_back_to_convention(tmp_path) -> None:
|
|
context = make_context(tmp_path, with_src=False)
|
|
assert context.home_dir("alice") == "home/alice"
|
|
|
|
|
|
def test_home_dir_uses_passwd_entry_when_available(tmp_path) -> None:
|
|
dst = tmp_path / "dst"
|
|
dst.mkdir()
|
|
src = tmp_path / "src"
|
|
src.mkdir()
|
|
(src / "etc").mkdir()
|
|
(src / "etc/passwd").write_text(
|
|
"alice:x:1000:1000:Alice:/srv/alice:/bin/bash\n", encoding="utf-8"
|
|
)
|
|
context = Context(config=CONFIG, dst=dst, src=src)
|
|
assert context.home_dir("alice") == "srv/alice"
|