110 lines
4.0 KiB
Python
110 lines
4.0 KiB
Python
import pytest
|
|
from typing import List, Optional
|
|
|
|
from app.core.errors import ApiError, map_command_error
|
|
from app.core.models import UserCreateRequest
|
|
import app.providers.cli_provider as cli_provider
|
|
from app.providers.cli_provider import CliSystemProvider
|
|
from app.providers.base import SystemProvider
|
|
from app.services.user_group_service import UserGroupService
|
|
|
|
|
|
class NoopProvider(SystemProvider):
|
|
def __init__(self) -> None:
|
|
self.created_home_dir: Optional[str] = None
|
|
self.created_linked_home_dir: Optional[str] = None
|
|
|
|
def create_user(self, username: str, password_hash: str, home_dir: Optional[str], linked_home_dir: Optional[str], shell: str, primary_group: Optional[str], groups: List[str]) -> None:
|
|
self.created_home_dir = home_dir
|
|
self.created_linked_home_dir = linked_home_dir
|
|
return None
|
|
def delete_user(self, username: str) -> None:
|
|
return None
|
|
def change_user_password(self, username: str, password_hash: str) -> None:
|
|
return None
|
|
def list_users(self):
|
|
return []
|
|
def get_user(self, username: str):
|
|
raise ApiError(404, "not_found", "not found")
|
|
def create_group(self, groupname: str) -> None:
|
|
return None
|
|
def delete_group(self, groupname: str) -> None:
|
|
return None
|
|
def list_groups(self):
|
|
return []
|
|
def get_group(self, groupname: str):
|
|
raise ApiError(404, "not_found", "not found")
|
|
def add_user_groups(self, username: str, groups: List[str], replace: bool) -> None:
|
|
return None
|
|
def remove_user_groups(self, username: str, groups: List[str]) -> None:
|
|
return None
|
|
def get_user_groups(self, username: str):
|
|
return []
|
|
|
|
|
|
def test_home_dir_out_of_base_rejected() -> None:
|
|
service = UserGroupService(provider=NoopProvider(), home_base_dir="/home")
|
|
payload = UserCreateRequest(username="alice", password_hash="$6$rounds=5000$abcdefghij", home_dir="/tmp/alice", groups=[])
|
|
with pytest.raises(ApiError) as raised:
|
|
service.create_user(payload)
|
|
assert raised.value.status_code == 400
|
|
|
|
|
|
def test_link_home_dir_uses_home_base_symlink_path_and_external_storage() -> None:
|
|
provider = NoopProvider()
|
|
service = UserGroupService(provider=provider, home_base_dir="/home", link_home_dir="/data/home")
|
|
payload = UserCreateRequest(username="alice", password_hash="$6$rounds=5000$abcdefghij", groups=[])
|
|
|
|
service.create_user(payload)
|
|
|
|
assert provider.created_home_dir == "/home/alice"
|
|
assert provider.created_linked_home_dir == "/data/home/alice"
|
|
|
|
|
|
def test_command_error_mapping_conflict() -> None:
|
|
error = map_command_error("user already exists", 9)
|
|
assert error.status_code == 409
|
|
|
|
|
|
class RecordingExecutor:
|
|
def __init__(self, home_dir: str) -> None:
|
|
self.home_dir = home_dir
|
|
self.commands: List[List[str]] = []
|
|
|
|
def run(self, args: List[str], use_sudo: bool = True) -> str:
|
|
self.commands.append(args)
|
|
if args == ["getent", "passwd", "alice"]:
|
|
return f"alice:x:1000:1000::%s:/bin/bash" % self.home_dir
|
|
|
|
return ""
|
|
|
|
|
|
class FakePath:
|
|
def __init__(self, path: str) -> None:
|
|
self.path = path
|
|
|
|
def is_symlink(self) -> bool:
|
|
return self.path == "/home/alice"
|
|
|
|
|
|
def test_delete_user_unlinks_home_when_it_is_symlink(monkeypatch) -> None:
|
|
monkeypatch.setattr(cli_provider, "Path", FakePath)
|
|
executor = RecordingExecutor("/home/alice")
|
|
provider = CliSystemProvider(executor=executor)
|
|
|
|
provider.delete_user("alice")
|
|
|
|
assert ["userdel", "alice"] in executor.commands
|
|
assert ["unlink", "/home/alice"] in executor.commands
|
|
|
|
|
|
def test_delete_user_does_not_unlink_home_when_it_is_directory(monkeypatch) -> None:
|
|
monkeypatch.setattr(cli_provider, "Path", FakePath)
|
|
executor = RecordingExecutor("/home/old-alice")
|
|
provider = CliSystemProvider(executor=executor)
|
|
|
|
provider.delete_user("alice")
|
|
|
|
assert ["userdel", "alice"] in executor.commands
|
|
assert ["unlink", "/home/old-alice"] not in executor.commands
|