import pathlib
import time

import heal.watch

READ_CONFIGURATION_FILE_1 = """
---
- lorem
- ipsum
- dolor
- sit
- amet
""".lstrip()

READ_CONFIGURATION_FILE_2 = """
---
- consectetur
- adipiscing
- elit
""".lstrip()

READ_CONFIGURATION_OK_OUTPUT = """
reading configuration
""".lstrip()


def test_read_configuration_ok(tmp_path, capsys):
    tmp_path.joinpath("file1.yml").write_text(READ_CONFIGURATION_FILE_1)
    tmp_path.joinpath("file2.yml").write_text(READ_CONFIGURATION_FILE_2)

    assert sorted(heal.watch.read_configuration(tmp_path)) == ["adipiscing", "amet", "consectetur", "dolor", "elit", "ipsum", "lorem", "sit"]
    assert capsys.readouterr().out == READ_CONFIGURATION_OK_OUTPUT


READ_CONFIGURATION_KO_OSERROR_OUTPUT = """
reading configuration
'file1.yml' ignored: [Errno 2] No such file or directory: '{0}/file1.yml'
""".lstrip()


def test_read_configuration_ko_oserror(tmp_path, monkeypatch, capsys):
    tmp_path.joinpath("file2.yml").write_text(READ_CONFIGURATION_FILE_2)
    monkeypatch.setattr(pathlib.Path, "iterdir", lambda _: [tmp_path.joinpath("file1.yml"), tmp_path.joinpath("file2.yml")])

    assert heal.watch.read_configuration(tmp_path) == ["consectetur", "adipiscing", "elit"]
    assert capsys.readouterr().out == READ_CONFIGURATION_KO_OSERROR_OUTPUT.format(tmp_path)


READ_CONFIGURATION_KO_VALUEERROR_OUTPUT = """
reading configuration
'file1.yml' ignored: 'utf-8' codec can't decode byte 0x99 in position 0: invalid start byte
""".lstrip()


def test_read_configuration_ko_valueerror(tmp_path, capsys):
    tmp_path.joinpath("file1.yml").write_bytes(bytes([0x99]))
    tmp_path.joinpath("file2.yml").write_text(READ_CONFIGURATION_FILE_2)

    assert heal.watch.read_configuration(tmp_path) == ["consectetur", "adipiscing", "elit"]
    assert capsys.readouterr().out == READ_CONFIGURATION_KO_VALUEERROR_OUTPUT


READ_CONFIGURATION_KO_NOT_A_LIST_OUTPUT = """
reading configuration
'file1.yml' ignored: not a proper yaml or json list
""".lstrip()


def test_read_configuration_ko_not_a_list(tmp_path, capsys):
    tmp_path.joinpath("file1.yml").write_text("lorem ipsum dolor sit amet")
    tmp_path.joinpath("file2.yml").write_text(READ_CONFIGURATION_FILE_2)

    assert heal.watch.read_configuration(tmp_path) == ["consectetur", "adipiscing", "elit"]
    assert capsys.readouterr().out == READ_CONFIGURATION_KO_NOT_A_LIST_OUTPUT


def test_read_configuration_ok_empty(tmp_path, capsys):
    assert heal.watch.read_configuration(tmp_path) == []
    assert capsys.readouterr().out == READ_CONFIGURATION_OK_OUTPUT


def test_read_configuration_ok_json(tmp_path, capsys):
    tmp_path.joinpath("file1.json").write_text('["lorem", "ipsum", "dolor", "sit", "amet"]')
    tmp_path.joinpath("file2.yml").write_text(READ_CONFIGURATION_FILE_2)

    assert sorted(heal.watch.read_configuration(tmp_path)) == ["adipiscing", "amet", "consectetur", "dolor", "elit", "ipsum", "lorem", "sit"]
    assert capsys.readouterr().out == READ_CONFIGURATION_OK_OUTPUT


VALIDATE_TESTS_OK_OUTPUT = """
validating tests
""".lstrip()


def test_validate_tests_ok(capsys):
    tests = heal.watch.validate_tests([{"test": "sed do eiusmod tempor", "fix": "incididunt ut labore et dolore magna aliqua", "order": "2"},
                                       {"test": "lorem ipsum dolor sit amet", "fix": "consectetur adipiscing elit", "order": "1", "mode": "alpha"}])

    assert tests == [{"test": "lorem ipsum dolor sit amet", "fix": "consectetur adipiscing elit", "order": 1, "mode": ["alpha"]},
                     {"test": "sed do eiusmod tempor", "fix": "incididunt ut labore et dolore magna aliqua", "order": 2}]
    assert capsys.readouterr().out == VALIDATE_TESTS_OK_OUTPUT


VALIDATE_TESTS_KO_OUTPUT = """
validating tests
ignored, not a dictionary: "check"
ignored, not a dictionary: []
ignored, value for "mode" can only be a string or a list of strings: {"test": "", "fix": "", "order": "1", "mode": {}}
ignored, value for "mode" can only be a string or a list of strings: {"test": "", "fix": "", "order": "1", "mode": ["", {}]}
ignored, values for "test" and "fix" can only be strings: {"test": "", "fix": {}, "order": "1"}
ignored, keys must match {"test", "fix", "order"} or {"test", "fix", "order", "mode"}: {"test": "", "fix": ""}
ignored, keys must match {"test", "fix", "order"} or {"test", "fix", "order", "mode"}: {"test": "", "fix": "", "order": "10", "then": ""}
ignored, keys must match {"test", "fix", "order"} or {"test", "fix", "order", "mode"}: {}
ignored, keys must match {"test", "fix", "order"} or {"test", "fix", "order", "mode"}: {"mode": "", "if": "", "order": "10"}
ignored, value for "order" can only be an integer: {"test": "", "fix": "", "order": {}}
ignored, value for "order" can only be an integer: {"test": "", "fix": "", "order": "max"}
""".lstrip()


def test_validate_tests_ko(capsys):
    assert heal.watch.validate_tests([
        "check",
        [],
        {"test": "", "fix": "", "order": "1", "mode": {}},
        {"test": "", "fix": "", "order": "1", "mode": ["", {}]},
        {"test": "", "fix": {}, "order": "1"},
        {"test": "", "fix": ""},
        {"test": "", "fix": "", "order": "10", "then": ""},
        {},
        {"mode": "", "if": "", "order": "10"},
        {"test": "", "fix": "", "order": {}},
        {"test": "", "fix": "", "order": "max"}
    ]) == []
    assert capsys.readouterr().out == VALIDATE_TESTS_KO_OUTPUT


def test_validate_tests_ok_empty(capsys):
    assert heal.watch.validate_tests([]) == []
    assert capsys.readouterr().out == VALIDATE_TESTS_OK_OUTPUT


def test_get_active_mode_ok(tmp_path):
    file = tmp_path.joinpath("text-file")
    file.write_text("dummy text\n")

    assert heal.watch.get_active_mode(file) == "dummy text"


def test_get_active_mode_ko_oserror(tmp_path):
    assert heal.watch.get_active_mode(tmp_path.joinpath("missing-file")) is "init"


def test_get_active_mode_ko_valueerror(tmp_path):
    file = tmp_path.joinpath("binary-file")
    file.write_bytes(bytes([0x99]))

    assert heal.watch.get_active_mode(file) is "init"


FILTER_ACTIVE_TESTS_OK_OUTPUT = """
filtering active tests
active: {"test": "", "fix": "", "order": 1}
active: {"test": "", "fix": "", "order": 3, "mode": ["alpha", "beta"]}
""".lstrip()


def test_filter_active_tests_ok(capsys):
    active_mode = "beta"
    tests = [{"test": "", "fix": "", "order": 1},
             {"test": "", "fix": "", "order": 2, "mode": ["alpha"]},
             {"test": "", "fix": "", "order": 3, "mode": ["alpha", "beta"]}]

    assert heal.watch.filter_active_tests(active_mode, tests) == [{"test": "", "fix": "", "order": 1},
                                                                  {"test": "", "fix": "", "order": 3, "mode": ["alpha", "beta"]}]
    assert capsys.readouterr().out == FILTER_ACTIVE_TESTS_OK_OUTPUT


FILTER_ACTIVE_TESTS_OK_EMPTY_OUTPUT = """
filtering active tests
""".lstrip()


def test_filter_active_tests_ok_empty(capsys):
    assert heal.watch.filter_active_tests("init", []) == []
    assert capsys.readouterr().out == FILTER_ACTIVE_TESTS_OK_EMPTY_OUTPUT


def test_tests_directory_has_changed(tmp_path, capsys):
    tests_directory = tmp_path.joinpath("tests")
    tests_directory.mkdir()
    watcher = heal.watch.Watcher(tests_directory, tmp_path.joinpath("mode"))

    # upon creation of the watcher, the directory will always show as changed
    assert watcher.tests_directory_has_changed()
    assert watcher.mtime == tests_directory.stat().st_mtime
    assert capsys.readouterr().out == "tests directory has changed\n"

    time.sleep(0.01)  # let some time pass, to ensure the current mtime has changed

    # if nothing happens to the directory, nothing has changed
    assert not watcher.tests_directory_has_changed()
    assert capsys.readouterr().out == ""

    time.sleep(0.01)  # let some time pass, to ensure the current mtime has changed

    # now we write a new file in the directory
    tests_directory.joinpath("dummy").write_text("dummy")
    assert watcher.tests_directory_has_changed()
    assert capsys.readouterr().out == "tests directory has changed\n"


def test_tests_have_changed(monkeypatch, capsys):
    # we will ultimately mock filter_modes_and_checks() with different values
    # so we can mock read_config() and don't need to care about the directory
    monkeypatch.setattr(heal.watch, "read_configuration", lambda _: None)
    watcher = heal.watch.Watcher(pathlib.Path(""), pathlib.Path(""))

    # let's add some checks
    monkeypatch.setattr(heal.watch, "validate_tests", lambda _: ["test_1", "test_2"])
    assert watcher.tests_have_changed()
    assert watcher.tests == ["test_1", "test_2"]
    assert capsys.readouterr().out == "tests have changed\n"

    # then let's remove some checks
    monkeypatch.setattr(heal.watch, "validate_tests", lambda _: ["test_1"])
    assert watcher.tests_have_changed()
    assert watcher.tests == ["test_1"]
    assert capsys.readouterr().out == "tests have changed\n"

    # finally, idempotence
    assert not watcher.tests_have_changed()
    assert watcher.tests == ["test_1"]
    assert capsys.readouterr().out == ""


def test_active_mode_has_changed(tmp_path, capsys):
    mode_file = tmp_path.joinpath("mode")
    watcher = heal.watch.Watcher(pathlib.Path(""), mode_file)

    # first, default mode
    assert not watcher.active_mode_has_changed()
    assert watcher.active_mode == "init"
    assert capsys.readouterr().out == ""

    # then a "nominal" mode
    mode_file.write_text("nominal\n")  # as in "echo nominal > /var/heal/mode"
    assert watcher.active_mode_has_changed()
    assert watcher.active_mode == "nominal"
    assert capsys.readouterr().out == "active mode has changed: nominal\n"

    # finally, idempotence
    assert not watcher.active_mode_has_changed()
    assert watcher.active_mode == "nominal"
    assert capsys.readouterr().out == ""


REFRESH_ACTIVE_TESTS_IF_NECESSARY_OUTPUT_1 = """
tests directory has changed
reading configuration
validating tests
""".lstrip()

REFRESH_ACTIVE_TESTS_IF_NECESSARY_TESTS_CHANGED = """
---
- test: just adding a check without mode
  fix: whatever
  order: 1
- test: adding a check for later
  fix: whatever
  order: 10
  mode: special
""".lstrip()

REFRESH_ACTIVE_TESTS_IF_NECESSARY_OUTPUT_2 = """
tests directory has changed
reading configuration
validating tests
tests have changed
filtering active tests
active: {"test": "just adding a check without mode", "fix": "whatever", "order": 1}
""".lstrip()

REFRESH_ACTIVE_TESTS_IF_NECESSARY_TESTS_AND_MODE_CHANGED = """
---
- test: adding a check with mode
  fix: whatever
  order: 2
  mode: basic
""".lstrip()

REFRESH_ACTIVE_TESTS_IF_NECESSARY_OUTPUT_3 = """
tests directory has changed
reading configuration
validating tests
tests have changed
active mode has changed: basic
filtering active tests
active: {"test": "just adding a check without mode", "fix": "whatever", "order": 1}
active: {"test": "adding a check with mode", "fix": "whatever", "order": 2, "mode": ["basic"]}
""".lstrip()

REFRESH_ACTIVE_TESTS_IF_NECESSARY_OUTPUT_4 = """
active mode has changed: special
filtering active tests
active: {"test": "just adding a check without mode", "fix": "whatever", "order": 1}
active: {"test": "adding a check for later", "fix": "whatever", "order": 10, "mode": ["special"]}
""".lstrip()


def test_refresh_active_tests_if_necessary_ok(tmp_path, capsys):
    tests_directory = tmp_path.joinpath("tests")
    tests_directory.mkdir()
    mode_file = tmp_path.joinpath("mode")
    watcher = heal.watch.Watcher(tests_directory, mode_file)

    # at first, directory is shown as changed, but nothing else has
    watcher.refresh_active_tests_if_necessary()
    assert watcher.active_tests == []
    assert capsys.readouterr().out == REFRESH_ACTIVE_TESTS_IF_NECESSARY_OUTPUT_1

    time.sleep(0.01)  # let some time pass, to ensure the current mtime has changed

    # case 1: tests have changed, active mode hasn't
    tests_directory.joinpath("tests_changed.yaml").write_text(REFRESH_ACTIVE_TESTS_IF_NECESSARY_TESTS_CHANGED)
    watcher.refresh_active_tests_if_necessary()
    assert watcher.active_tests == [{"test": "just adding a check without mode", "fix": "whatever", "order": 1}]
    assert capsys.readouterr().out == REFRESH_ACTIVE_TESTS_IF_NECESSARY_OUTPUT_2

    time.sleep(0.01)  # let some time pass, to ensure the current mtime has changed

    # case 2: both tests and active mode have changed
    tests_directory.joinpath("tests_and_modes_changed.yaml").write_text(REFRESH_ACTIVE_TESTS_IF_NECESSARY_TESTS_AND_MODE_CHANGED)
    mode_file.write_text("basic")
    watcher.refresh_active_tests_if_necessary()
    assert watcher.active_tests == [{"test": "just adding a check without mode", "fix": "whatever", "order": 1},
                                    {"test": "adding a check with mode", "fix": "whatever", "order": 2, "mode": ["basic"]}]
    assert capsys.readouterr().out == REFRESH_ACTIVE_TESTS_IF_NECESSARY_OUTPUT_3

    time.sleep(0.01)  # let some time pass, to ensure the current mtime has changed

    # case 3: active mode has changed
    mode_file.write_text("special")
    watcher.refresh_active_tests_if_necessary()
    assert watcher.active_tests == [{"test": "just adding a check without mode", "fix": "whatever", "order": 1},
                                    {"test": "adding a check for later", "fix": "whatever", "order": 10, "mode": ["special"]}]
    assert capsys.readouterr().out == REFRESH_ACTIVE_TESTS_IF_NECESSARY_OUTPUT_4

    time.sleep(0.01)  # let some time pass, to ensure the current mtime has changed

    # case 4: nothing changed
    watcher.refresh_active_tests_if_necessary()
    assert watcher.active_tests == [{"test": "just adding a check without mode", "fix": "whatever", "order": 1},
                                    {"test": "adding a check for later", "fix": "whatever", "order": 10, "mode": ["special"]}]
    assert capsys.readouterr().out == ""

import pathlib
import time

import heal.watch

READ_CONFIGURATION_FILE_1 = """
---
- lorem
- ipsum
- dolor
- sit
- amet
""".lstrip()

READ_CONFIGURATION_FILE_2 = """
---
- consectetur
- adipiscing
- elit
""".lstrip()

READ_CONFIGURATION_OK_OUTPUT = """
reading configuration
""".lstrip()


def test_read_configuration_ok(tmp_path, capsys):
    tmp_path.joinpath("file1.yml").write_text(READ_CONFIGURATION_FILE_1)
    tmp_path.joinpath("file2.yml").write_text(READ_CONFIGURATION_FILE_2)

    assert sorted(heal.watch.read_configuration(tmp_path)) == ["adipiscing", "amet", "consectetur", "dolor", "elit", "ipsum", "lorem", "sit"]
    assert capsys.readouterr().out == READ_CONFIGURATION_OK_OUTPUT


READ_CONFIGURATION_KO_OSERROR_OUTPUT = """
reading configuration
'file1.yml' ignored: [Errno 2] No such file or directory: '{0}/file1.yml'
""".lstrip()


def test_read_configuration_ko_oserror(tmp_path, monkeypatch, capsys):
    tmp_path.joinpath("file2.yml").write_text(READ_CONFIGURATION_FILE_2)
    monkeypatch.setattr(pathlib.Path, "iterdir", lambda _: [tmp_path.joinpath("file1.yml"), tmp_path.joinpath("file2.yml")])

    assert heal.watch.read_configuration(tmp_path) == ["consectetur", "adipiscing", "elit"]
    assert capsys.readouterr().out == READ_CONFIGURATION_KO_OSERROR_OUTPUT.format(tmp_path)


READ_CONFIGURATION_KO_VALUEERROR_OUTPUT = """
reading configuration
'file1.yml' ignored: 'utf-8' codec can't decode byte 0x99 in position 0: invalid start byte
""".lstrip()


def test_read_configuration_ko_valueerror(tmp_path, capsys):
    tmp_path.joinpath("file1.yml").write_bytes(bytes([0x99]))
    tmp_path.joinpath("file2.yml").write_text(READ_CONFIGURATION_FILE_2)

    assert heal.watch.read_configuration(tmp_path) == ["consectetur", "adipiscing", "elit"]
    assert capsys.readouterr().out == READ_CONFIGURATION_KO_VALUEERROR_OUTPUT


READ_CONFIGURATION_KO_NOT_A_LIST_OUTPUT = """
reading configuration
'file1.yml' ignored: not a proper yaml or json list
""".lstrip()


def test_read_configuration_ko_not_a_list(tmp_path, capsys):
    tmp_path.joinpath("file1.yml").write_text("lorem ipsum dolor sit amet")
    tmp_path.joinpath("file2.yml").write_text(READ_CONFIGURATION_FILE_2)

    assert heal.watch.read_configuration(tmp_path) == ["consectetur", "adipiscing", "elit"]
    assert capsys.readouterr().out == READ_CONFIGURATION_KO_NOT_A_LIST_OUTPUT


def test_read_configuration_ok_empty(tmp_path, capsys):
    assert heal.watch.read_configuration(tmp_path) == []
    assert capsys.readouterr().out == READ_CONFIGURATION_OK_OUTPUT


def test_read_configuration_ok_json(tmp_path, capsys):
    tmp_path.joinpath("file1.json").write_text('["lorem", "ipsum", "dolor", "sit", "amet"]')
    tmp_path.joinpath("file2.yml").write_text(READ_CONFIGURATION_FILE_2)

    assert sorted(heal.watch.read_configuration(tmp_path)) == ["adipiscing", "amet", "consectetur", "dolor", "elit", "ipsum", "lorem", "sit"]
    assert capsys.readouterr().out == READ_CONFIGURATION_OK_OUTPUT


VALIDATE_TESTS_OK_OUTPUT = """
validating tests
""".lstrip()


def test_validate_tests_ok(capsys):
    tests = heal.watch.validate_tests([{"test": "sed do eiusmod tempor", "fix": "incididunt ut labore et dolore magna aliqua", "order": "2"},
                                       {"test": "lorem ipsum dolor sit amet", "fix": "consectetur adipiscing elit", "order": "1", "mode": "alpha"}])

    assert tests == [{"test": "lorem ipsum dolor sit amet", "fix": "consectetur adipiscing elit", "order": 1, "mode": ["alpha"]},
                     {"test": "sed do eiusmod tempor", "fix": "incididunt ut labore et dolore magna aliqua", "order": 2}]
    assert capsys.readouterr().out == VALIDATE_TESTS_OK_OUTPUT


VALIDATE_TESTS_KO_OUTPUT = """
validating tests
ignored, not a dictionary: "check"
ignored, not a dictionary: []
ignored, value for "mode" can only be a string or a list of strings: {"test": "", "fix": "", "order": "1", "mode": {}}
ignored, value for "mode" can only be a string or a list of strings: {"test": "", "fix": "", "order": "1", "mode": ["", {}]}
ignored, values for "test" and "fix" can only be strings: {"test": "", "fix": {}, "order": "1"}
ignored, keys must match {"test", "fix", "order"} or {"test", "fix", "order", "mode"}: {"test": "", "fix": ""}
ignored, keys must match {"test", "fix", "order"} or {"test", "fix", "order", "mode"}: {"test": "", "fix": "", "order": "10", "then": ""}
ignored, keys must match {"test", "fix", "order"} or {"test", "fix", "order", "mode"}: {}
ignored, keys must match {"test", "fix", "order"} or {"test", "fix", "order", "mode"}: {"mode": "", "if": "", "order": "10"}
ignored, value for "order" can only be an integer: {"test": "", "fix": "", "order": {}}
ignored, value for "order" can only be an integer: {"test": "", "fix": "", "order": "max"}
""".lstrip()


def test_validate_tests_ko(capsys):
    assert heal.watch.validate_tests([
        "check",
        [],
        {"test": "", "fix": "", "order": "1", "mode": {}},
        {"test": "", "fix": "", "order": "1", "mode": ["", {}]},
        {"test": "", "fix": {}, "order": "1"},
        {"test": "", "fix": ""},
        {"test": "", "fix": "", "order": "10", "then": ""},
        {},
        {"mode": "", "if": "", "order": "10"},
        {"test": "", "fix": "", "order": {}},
        {"test": "", "fix": "", "order": "max"}
    ]) == []
    assert capsys.readouterr().out == VALIDATE_TESTS_KO_OUTPUT


def test_validate_tests_ok_empty(capsys):
    assert heal.watch.validate_tests([]) == []
    assert capsys.readouterr().out == VALIDATE_TESTS_OK_OUTPUT


def test_get_active_mode_ok(tmp_path):
    file = tmp_path.joinpath("text-file")
    file.write_text("dummy text\n")

    assert heal.watch.get_active_mode(file) == "dummy text"


def test_get_active_mode_ko_oserror(tmp_path):
    assert heal.watch.get_active_mode(tmp_path.joinpath("missing-file")) is "init"


def test_get_active_mode_ko_valueerror(tmp_path):
    file = tmp_path.joinpath("binary-file")
    file.write_bytes(bytes([0x99]))

    assert heal.watch.get_active_mode(file) is "init"


FILTER_ACTIVE_TESTS_OK_OUTPUT = """
filtering active tests
active: {"test": "", "fix": "", "order": 1}
active: {"test": "", "fix": "", "order": 3, "mode": ["alpha", "beta"]}
""".lstrip()


def test_filter_active_tests_ok(capsys):
    active_mode = "beta"
    tests = [{"test": "", "fix": "", "order": 1},
             {"test": "", "fix": "", "order": 2, "mode": ["alpha"]},
             {"test": "", "fix": "", "order": 3, "mode": ["alpha", "beta"]}]

    assert heal.watch.filter_active_tests(active_mode, tests) == [{"test": "", "fix": "", "order": 1},
                                                                  {"test": "", "fix": "", "order": 3, "mode": ["alpha", "beta"]}]
    assert capsys.readouterr().out == FILTER_ACTIVE_TESTS_OK_OUTPUT


FILTER_ACTIVE_TESTS_OK_EMPTY_OUTPUT = """
filtering active tests
""".lstrip()


def test_filter_active_tests_ok_empty(capsys):
    assert heal.watch.filter_active_tests("init", []) == []
    assert capsys.readouterr().out == FILTER_ACTIVE_TESTS_OK_EMPTY_OUTPUT


def test_tests_directory_has_changed(tmp_path, capsys):
    tests_directory = tmp_path.joinpath("tests")
    tests_directory.mkdir()
    watcher = heal.watch.Watcher(tests_directory, tmp_path.joinpath("mode"))

    # upon creation of the watcher, the directory will always show as changed
    assert watcher.tests_directory_has_changed()
    assert watcher.mtime == tests_directory.stat().st_mtime
    assert capsys.readouterr().out == "tests directory has changed\n"

    time.sleep(0.01)  # let some time pass, to ensure the current mtime has changed

    # if nothing happens to the directory, nothing has changed
    assert not watcher.tests_directory_has_changed()
    assert capsys.readouterr().out == ""

    time.sleep(0.01)  # let some time pass, to ensure the current mtime has changed

    # now we write a new file in the directory
    tests_directory.joinpath("dummy").write_text("dummy")
    assert watcher.tests_directory_has_changed()
    assert capsys.readouterr().out == "tests directory has changed\n"


def test_tests_have_changed(monkeypatch, capsys):
    # we will ultimately mock filter_modes_and_checks() with different values
    # so we can mock read_config() and don't need to care about the directory
    monkeypatch.setattr(heal.watch, "read_configuration", lambda _: None)
    watcher = heal.watch.Watcher(pathlib.Path(""), pathlib.Path(""))

    # let's add some checks
    monkeypatch.setattr(heal.watch, "validate_tests", lambda _: ["test_1", "test_2"])
    assert watcher.tests_have_changed()
    assert watcher.tests == ["test_1", "test_2"]
    assert capsys.readouterr().out == "tests have changed\n"

    # then let's remove some checks
    monkeypatch.setattr(heal.watch, "validate_tests", lambda _: ["test_1"])
    assert watcher.tests_have_changed()
    assert watcher.tests == ["test_1"]
    assert capsys.readouterr().out == "tests have changed\n"

    # finally, idempotence
    assert not watcher.tests_have_changed()
    assert watcher.tests == ["test_1"]
    assert capsys.readouterr().out == ""


def test_active_mode_has_changed(tmp_path, capsys):
    mode_file = tmp_path.joinpath("mode")
    watcher = heal.watch.Watcher(pathlib.Path(""), mode_file)

    # first, default mode
    assert not watcher.active_mode_has_changed()
    assert watcher.active_mode == "init"
    assert capsys.readouterr().out == ""

    # then a "nominal" mode
    mode_file.write_text("nominal\n")  # as in "echo nominal > /var/heal/mode"
    assert watcher.active_mode_has_changed()
    assert watcher.active_mode == "nominal"
    assert capsys.readouterr().out == "active mode has changed: nominal\n"

    # finally, idempotence
    assert not watcher.active_mode_has_changed()
    assert watcher.active_mode == "nominal"
    assert capsys.readouterr().out == ""


REFRESH_ACTIVE_TESTS_IF_NECESSARY_OUTPUT_1 = """
tests directory has changed
reading configuration
validating tests
""".lstrip()

REFRESH_ACTIVE_TESTS_IF_NECESSARY_TESTS_CHANGED = """
---
- test: just adding a check without mode
  fix: whatever
  order: 1
- test: adding a check for later
  fix: whatever
  order: 10
  mode: special
""".lstrip()

REFRESH_ACTIVE_TESTS_IF_NECESSARY_OUTPUT_2 = """
tests directory has changed
reading configuration
validating tests
tests have changed
filtering active tests
active: {"test": "just adding a check without mode", "fix": "whatever", "order": 1}
""".lstrip()

REFRESH_ACTIVE_TESTS_IF_NECESSARY_TESTS_AND_MODE_CHANGED = """
---
- test: adding a check with mode
  fix: whatever
  order: 2
  mode: basic
""".lstrip()

REFRESH_ACTIVE_TESTS_IF_NECESSARY_OUTPUT_3 = """
tests directory has changed
reading configuration
validating tests
tests have changed
active mode has changed: basic
filtering active tests
active: {"test": "just adding a check without mode", "fix": "whatever", "order": 1}
active: {"test": "adding a check with mode", "fix": "whatever", "order": 2, "mode": ["basic"]}
""".lstrip()

REFRESH_ACTIVE_TESTS_IF_NECESSARY_OUTPUT_4 = """
active mode has changed: special
filtering active tests
active: {"test": "just adding a check without mode", "fix": "whatever", "order": 1}
active: {"test": "adding a check for later", "fix": "whatever", "order": 10, "mode": ["special"]}
""".lstrip()


def test_refresh_active_tests_if_necessary_ok(tmp_path, capsys):
    tests_directory = tmp_path.joinpath("tests")
    tests_directory.mkdir()
    mode_file = tmp_path.joinpath("mode")
    watcher = heal.watch.Watcher(tests_directory, mode_file)

    # at first, directory is shown as changed, but nothing else has
    watcher.refresh_active_tests_if_necessary()
    assert watcher.active_tests == []
    assert capsys.readouterr().out == REFRESH_ACTIVE_TESTS_IF_NECESSARY_OUTPUT_1

    time.sleep(0.01)  # let some time pass, to ensure the current mtime has changed

    # case 1: tests have changed, active mode hasn't
    tests_directory.joinpath("tests_changed.yaml").write_text(REFRESH_ACTIVE_TESTS_IF_NECESSARY_TESTS_CHANGED)
    watcher.refresh_active_tests_if_necessary()
    assert watcher.active_tests == [{"test": "just adding a check without mode", "fix": "whatever", "order": 1}]
    assert capsys.readouterr().out == REFRESH_ACTIVE_TESTS_IF_NECESSARY_OUTPUT_2

    time.sleep(0.01)  # let some time pass, to ensure the current mtime has changed

    # case 2: both tests and active mode have changed
    tests_directory.joinpath("tests_and_modes_changed.yaml").write_text(REFRESH_ACTIVE_TESTS_IF_NECESSARY_TESTS_AND_MODE_CHANGED)
    mode_file.write_text("basic")
    watcher.refresh_active_tests_if_necessary()
    assert watcher.active_tests == [{"test": "just adding a check without mode", "fix": "whatever", "order": 1},
                                    {"test": "adding a check with mode", "fix": "whatever", "order": 2, "mode": ["basic"]}]
    assert capsys.readouterr().out == REFRESH_ACTIVE_TESTS_IF_NECESSARY_OUTPUT_3

    time.sleep(0.01)  # let some time pass, to ensure the current mtime has changed

    # case 3: active mode has changed
    mode_file.write_text("special")
    watcher.refresh_active_tests_if_necessary()
    assert watcher.active_tests == [{"test": "just adding a check without mode", "fix": "whatever", "order": 1},
                                    {"test": "adding a check for later", "fix": "whatever", "order": 10, "mode": ["special"]}]
    assert capsys.readouterr().out == REFRESH_ACTIVE_TESTS_IF_NECESSARY_OUTPUT_4

    time.sleep(0.01)  # let some time pass, to ensure the current mtime has changed

    # case 4: nothing changed
    watcher.refresh_active_tests_if_necessary()
    assert watcher.active_tests == [{"test": "just adding a check without mode", "fix": "whatever", "order": 1},
                                    {"test": "adding a check for later", "fix": "whatever", "order": 10, "mode": ["special"]}]
    assert capsys.readouterr().out == ""

--- - lorem - ipsum - dolor - sit - amet --- - consectetur - adipiscing - elit reading configuration reading configuration 'file1.yml' ignored: [Errno 2] No such file or directory: '{0}/file1.yml' reading configuration 'file1.yml' ignored: 'utf-8' codec can't decode byte 0x99 in position 0: invalid start byte reading configuration 'file1.yml' ignored: not a proper yaml or json list validating tests validating tests ignored, not a dictionary: "check" ignored, not a dictionary: [] ignored, value for "mode" can only be a string or a list of strings: {"test": "", "fix": "", "order": "1", "mode": {}} ignored, value for "mode" can only be a string or a list of strings: {"test": "", "fix": "", "order": "1", "mode": ["", {}]} ignored, values for "test" and "fix" can only be strings: {"test": "", "fix": {}, "order": "1"} ignored, keys must match {"test", "fix", "order"} or {"test", "fix", "order", "mode"}: {"test": "", "fix": ""} ignored, keys must match {"test", "fix", "order"} or {"test", "fix", "order", "mode"}: {"test": "", "fix": "", "order": "10", "then": ""} ignored, keys must match {"test", "fix", "order"} or {"test", "fix", "order", "mode"}: {} ignored, keys must match {"test", "fix", "order"} or {"test", "fix", "order", "mode"}: {"mode": "", "if": "", "order": "10"} ignored, value for "order" can only be an integer: {"test": "", "fix": "", "order": {}} ignored, value for "order" can only be an integer: {"test": "", "fix": "", "order": "max"} filtering active tests active: {"test": "", "fix": "", "order": 1} active: {"test": "", "fix": "", "order": 3, "mode": ["alpha", "beta"]} filtering active tests # upon creation of the watcher, the directory will always show as changed # let some time pass, to ensure the current mtime has changed # if nothing happens to the directory, nothing has changed # let some time pass, to ensure the current mtime has changed # now we write a new file in the directory # we will ultimately mock filter_modes_and_checks() with different values # so we can mock read_config() and don't need to care about the directory # let's add some checks # then let's remove some checks # finally, idempotence # first, default mode # then a "nominal" mode # as in "echo nominal > /var/heal/mode" # finally, idempotence tests directory has changed reading configuration validating tests --- - test: just adding a check without mode   fix: whatever   order: 1 - test: adding a check for later   fix: whatever   order: 10   mode: special tests directory has changed reading configuration validating tests tests have changed filtering active tests active: {"test": "just adding a check without mode", "fix": "whatever", "order": 1} --- - test: adding a check with mode   fix: whatever   order: 2   mode: basic tests directory has changed reading configuration validating tests tests have changed active mode has changed: basic filtering active tests active: {"test": "just adding a check without mode", "fix": "whatever", "order": 1} active: {"test": "adding a check with mode", "fix": "whatever", "order": 2, "mode": ["basic"]} active mode has changed: special filtering active tests active: {"test": "just adding a check without mode", "fix": "whatever", "order": 1} active: {"test": "adding a check for later", "fix": "whatever", "order": 10, "mode": ["special"]} # at first, directory is shown as changed, but nothing else has # let some time pass, to ensure the current mtime has changed # case 1: tests have changed, active mode hasn't # let some time pass, to ensure the current mtime has changed # case 2: both tests and active mode have changed # let some time pass, to ensure the current mtime has changed # case 3: active mode has changed # let some time pass, to ensure the current mtime has changed # case 4: nothing changed