#
# Tests for the 'osbuild.util.selinux' module.
#

import errno
import io
import os
from unittest import mock

from osbuild.util import selinux


def test_selinux_config():
    f = io.StringIO()
    cfg = selinux.parse_config(f)
    assert cfg is not None
    policy = selinux.config_get_policy(cfg)
    assert policy is None

    example_good = """
    # This file controls the state of SELinux on the system.
    # SELINUX= can take one of these three values:
    #     enforcing - SELinux security policy is enforced.
    #     permissive - SELinux prints warnings instead of enforcing.
    #     disabled - No SELinux policy is loaded.
    SELINUX=enforcing
    # SELINUXTYPE= can take one of these three values:
    #     targeted - Targeted processes are protected,
    #     minimum - Modification of targeted policy.
    #     mls - Multi Level Security protection.
    SELINUXTYPE=targeted
    """

    f = io.StringIO(example_good)
    cfg = selinux.parse_config(f)
    assert 'SELINUX' in cfg
    assert 'SELINUXTYPE' in cfg
    assert cfg['SELINUX'] == 'enforcing'
    assert cfg['SELINUXTYPE'] == 'targeted'

    policy = selinux.config_get_policy(cfg)
    assert policy == 'targeted'


def test_setfilecon():
    with mock.patch("os.setxattr") as setxattr:

        selinux.setfilecon("/path", "context")
        setxattr.assert_called_once_with("/path", selinux.XATTR_NAME_SELINUX,
                                         b"context", follow_symlinks=True)

    with mock.patch("os.getxattr") as getxattr:
        with mock.patch("os.setxattr") as setxattr:

            def raise_error(*_args, **_kwargs):
                raise OSError(errno.ENOTSUP, "Not supported")

            getxattr.return_value = b"context"
            setxattr.side_effect = raise_error

            selinux.setfilecon("path", "context")


@mock.patch("subprocess.run")
def test_selinux_setfiles(mocked_run, tmp_path):
    selinux.setfiles("/etc/selinux/thing", os.fspath(tmp_path), "/", "/boot")

    assert len(mocked_run.call_args_list) == 2
    assert mocked_run.call_args_list == [
        mock.call(
            ["setfiles", "-F", "-r", os.fspath(tmp_path),
             "/etc/selinux/thing", os.fspath(tmp_path) + "/"], check=True),
        mock.call(
            ["setfiles", "-F", "-r", os.fspath(tmp_path),
             "/etc/selinux/thing", os.fspath(tmp_path) + "/boot"], check=True),
    ]


@mock.patch("subprocess.run")
def test_selinux_setfiles_exclude(mocked_run, tmp_path):
    selinux.setfiles("/etc/selinux/thing", os.fspath(tmp_path), "/", exclude_paths=["/sysroot", "/other/dir"])

    assert len(mocked_run.call_args_list) == 1
    assert mocked_run.call_args_list == [
        mock.call(
            ["setfiles", "-F", "-r", os.fspath(tmp_path),
             "-e", "/sysroot", "-e", "/other/dir",
             "/etc/selinux/thing", os.fspath(tmp_path) + "/"], check=True),
    ]
