Example #1
0
def test_record_fail_altered_file(fs, simple_mhl_history):
    # alter a file
    with open("/root/Stuff.txt", "a") as file:
        file.write("!!")

    runner = CliRunner()
    result = runner.invoke(ascmhl.commands.create,
                           ["/root", "-sf", "/root/Stuff.txt"])
    assert result.exit_code == 12
    assert "Stuff.txt" in result.output

    # when passing a different file to record no error ws thrown since the altered file is ignored
    runner = CliRunner()
    result = runner.invoke(ascmhl.commands.create,
                           ["/root", "-sf", "/root/A/A1.txt"])
    assert result.exit_code == 0

    # make sure we have created one failing and one succeeded generation
    history = MHLHistory.load_from_path("/root")
    assert history.hash_lists[1].media_hashes[0].path == "Stuff.txt"
    assert history.hash_lists[1].media_hashes[0].hash_entries[
        0].action == "failed"
    assert history.hash_lists[2].media_hashes[0].path == "A/A1.txt"
    assert history.hash_lists[2].media_hashes[0].hash_entries[
        0].action == "verified"
Example #2
0
def test_create_fail_altered_file(fs, simple_mhl_history):
    # alter a file
    with open("/root/Stuff.txt", "a") as file:
        file.write("!!")

    result = CliRunner().invoke(ascmhl.commands.create, ["/root"])
    assert result.exit_code == 12
    assert "Stuff.txt" in result.output

    # since the file is still altered every other seal will fail as well since we compare to the original hash
    result = CliRunner().invoke(ascmhl.commands.create, ["/root"])
    assert result.exit_code == 12
    assert "Stuff.txt" in result.output

    # when we now choose a new hash format we still fail but will add the new hash in the new format
    result = CliRunner().invoke(ascmhl.commands.create, ["/root", "-h", "md5"])
    assert result.exit_code == 12
    assert "Stuff.txt" in result.output

    root_history = MHLHistory.load_from_path("/root")
    stuff_txt_latest_media_hash = root_history.hash_lists[-1].find_media_hash_for_path("Stuff.txt")
    # the media hash for the Stuff.txt in the latest generation contains the failed xxh64 hash of the altered file
    assert stuff_txt_latest_media_hash.hash_entries[0].hash_format == "xxh64"
    assert stuff_txt_latest_media_hash.hash_entries[0].hash_string == "2346e97eb08788cc"
    assert stuff_txt_latest_media_hash.hash_entries[0].action == "failed"
    # and it contains NO new md5 hash value of the altered file
    assert len(stuff_txt_latest_media_hash.hash_entries) == 1

    # since we didn't add a new md5 hash for the failing file before creating a new generation will still fail for the altered file
    result = CliRunner().invoke(ascmhl.commands.create, ["/root", "-h", "md5"])
    assert result.exit_code == 12
    assert "Stuff.txt" in result.output
Example #3
0
def test_child_history_verify(fs, nested_mhl_histories):
    """ """

    runner = CliRunner()
    result = runner.invoke(ascmhl.commands.create, ["/root"],
                           catch_exceptions=False)
    assert result.exit_code == 0

    assert os.path.isfile("/root/ascmhl/0002_root_2020-01-16_091500.mhl")
    assert os.path.isfile("/root/A/AA/ascmhl/0002_AA_2020-01-16_091500.mhl")
    assert os.path.isfile("/root/B/ascmhl/0002_B_2020-01-16_091500.mhl")
    assert os.path.isfile("/root/B/BB/ascmhl/0002_BB_2020-01-16_091500.mhl")

    root_history = MHLHistory.load_from_path("/root")
    assert len(root_history.hash_lists) == 2

    assert root_history.hash_lists[1].media_hashes[1].path == "A/AB/AB1.txt"
    assert root_history.hash_lists[1].media_hashes[1].hash_entries[
        0].action == "original"
    assert root_history.hash_lists[1].media_hashes[5].path == "Stuff.txt"
    assert root_history.hash_lists[1].media_hashes[5].hash_entries[
        0].action == "verified"

    aa_history = root_history.child_histories[0]
    b_history = root_history.child_histories[1]
    bb_history = root_history.child_histories[1].child_histories[0]
    root_hash_list = root_history.hash_lists[-1]
    aa_hash_list = aa_history.hash_lists[-1]
    b_hash_list = b_history.hash_lists[-1]
    bb_hash_list = bb_history.hash_lists[-1]

    assert aa_history.latest_generation_number() == 2
    assert b_hash_list.media_hashes[0].path == "BA/BA1.txt"
    assert b_hash_list.media_hashes[3].path == "B1.txt"
    assert b_hash_list.media_hashes[0].hash_entries[0].action == "original"
    assert b_hash_list.media_hashes[3].hash_entries[0].action == "verified"

    # check that the mhl references are correct
    assert root_history.hash_lists[1].referenced_hash_lists[0] == aa_hash_list
    assert root_history.hash_lists[1].referenced_hash_lists[1] == b_hash_list
    assert b_hash_list.referenced_hash_lists[0] == bb_hash_list
    assert len(aa_hash_list.referenced_hash_lists) == 0

    # the media hashes of the directories that contain a history themselves should be both in the child history
    # as root media hash and in the parent history to represent the directory that contains the child history
    aa_dir_hash = root_hash_list.find_media_hash_for_path(
        "A/AA").hash_entries[0].hash_string
    assert aa_dir_hash
    assert aa_hash_list.process_info.root_media_hash.hash_entries[
        0].hash_string == aa_dir_hash
    # the dir hash of BB is in the history of B not in the root history
    assert root_hash_list.find_media_hash_for_path("B/BB") is None
    bb_dir_hash = b_hash_list.find_media_hash_for_path(
        "BB").hash_entries[0].hash_string
    assert bb_hash_list.process_info.root_media_hash.hash_entries[
        0].hash_string == bb_dir_hash
    # but the dir hash of B is also in the root history
    assert root_hash_list.find_media_hash_for_path("B")
Example #4
0
def test_create_fail_missing_file(fs, nested_mhl_histories):
    """
    test that creating a new generation fails if there is a file missing on the file system that is referenced by one of the histories
    """

    root_history = MHLHistory.load_from_path("/root")
    paths = root_history.set_of_file_paths()

    assert paths == {"/root/B/B1.txt", "/root/B/BB/BB1.txt", "/root/Stuff.txt", "/root/A/AA/AA1.txt"}
    os.remove("/root/A/AA/AA1.txt")
    runner = CliRunner()
    result = runner.invoke(ascmhl.commands.create, ["/root"])
    assert result.exit_code == 15
    assert "1 missing file(s):\n  A/AA/AA1.txt" in result.output

    # the actual seal has been written to disk anyways we expect the history to contain
    # the new not yet referenced files (/root/B/BA/BA1.txt and /root/A/AB/AB1.txt) as well now
    root_history = MHLHistory.load_from_path("/root")
    paths = root_history.set_of_file_paths()

    # since we scan all generations for file paths we now get old files, missing files and new files here
    # as well as all entries for the directories
    assert paths == {
        "/root/B/B1.txt",
        "/root/B/BA/BA1.txt",
        "/root/B",
        "/root/A/AA",
        "/root/A/AB/AB1.txt",
        "/root/B/BA",
        "/root/A/AA/AA1.txt",
        "/root/A/AB",
        "/root/Stuff.txt",
        "/root/B/BB",
        "/root/A",
        "/root/B/BB/BB1.txt",
    }

    # since the file /root/A/AA/AA1.txt is still missing all further seal attempts will still fail
    runner = CliRunner()
    result = runner.invoke(ascmhl.commands.create, ["/root"])
    assert result.exit_code == 15
    assert "1 missing file(s):\n  A/AA/AA1.txt" in result.output
Example #5
0
def test_record_succeed_single_directory(fs):
    fs.create_file("/root/Stuff.txt", contents="stuff\n")
    fs.create_file("/root/A/A1.txt", contents="A1\n")
    fs.create_file("/root/A/A2.txt", contents="A2\n")

    runner = CliRunner()
    result = runner.invoke(ascmhl.commands.create, ["/root", "-sf", "/root/A"])
    assert result.exit_code == 0
    assert os.path.exists("/root/ascmhl/0001_root_2020-01-16_091500.mhl")
    assert os.path.exists("/root/ascmhl/ascmhl_chain.xml")

    # make sure that only the specified file was added
    history = MHLHistory.load_from_path("/root")
    assert len(history.hash_lists[0].media_hashes) == 2
    assert history.hash_lists[0].media_hashes[0].path == "A/A1.txt"
    assert history.hash_lists[0].media_hashes[1].path == "A/A2.txt"
Example #6
0
def test_child_history_partial_verification_bb_folder(fs,
                                                      nested_mhl_histories):
    """ """

    # create an additional file the record command will find because we pass it a folder
    fs.create_file("/root/B/BB/BB2.txt", contents="BB2\n")
    runner = CliRunner()
    result = runner.invoke(ascmhl.commands.create,
                           ["/root", "-sf", "/root/B/BB"])
    assert result.exit_code == 0

    assert os.path.isfile("/root/ascmhl/0002_root_2020-01-16_091500.mhl")
    assert os.path.isfile("/root/B/ascmhl/0002_B_2020-01-16_091500.mhl")
    assert os.path.isfile("/root/B/BB/ascmhl/0002_BB_2020-01-16_091500.mhl")

    root_history = MHLHistory.load_from_path("/root")
    assert len(root_history.hash_lists) == 2

    aa_history = root_history.child_histories[0]
    b_history = root_history.child_histories[1]
    bb_history = root_history.child_histories[1].child_histories[0]

    # the root and the B hash lists only contains a mhl reference to the hash list
    # down the folder hierarchy, no media hashes
    assert len(root_history.hash_lists[1].media_hashes) == 0
    assert root_history.hash_lists[1].referenced_hash_lists[
        0] == b_history.hash_lists[1]
    assert len(b_history.hash_lists[1].media_hashes) == 0
    assert b_history.hash_lists[1].referenced_hash_lists[
        0] == bb_history.hash_lists[1]

    # the BB hash list contains the media hash of the verified files in BB
    assert bb_history.hash_lists[1].media_hashes[0].path == "BB1.txt"
    assert bb_history.hash_lists[1].media_hashes[0].hash_entries[
        0].action == "verified"
    assert bb_history.hash_lists[1].media_hashes[1].path == "BB2.txt"
    assert bb_history.hash_lists[1].media_hashes[1].hash_entries[
        0].action == "original"

    # the other histories don't have a new generation
    assert not os.path.isfile(
        "/root/A/AA/ascmhl/0002_AA_2020-01-16_091500.mhl")
    assert aa_history.latest_generation_number() == 1
Example #7
0
def test_child_history_parsing(fs, nested_mhl_histories):
    """ """

    root_history = MHLHistory.load_from_path("/root")
    assert len(root_history.child_histories) == 2

    aa_history = root_history.child_histories[0]
    b_history = root_history.child_histories[1]
    bb_history = root_history.child_histories[1].child_histories[0]

    assert aa_history.asc_mhl_path == "/root/A/AA/ascmhl"
    assert aa_history.parent_history == root_history
    assert b_history.asc_mhl_path == "/root/B/ascmhl"
    assert b_history.parent_history == root_history

    assert len(b_history.child_histories) == 1
    assert bb_history.asc_mhl_path == "/root/B/BB/ascmhl"
    assert bb_history.parent_history == b_history

    # check sub children mappings that map all transitive children and their relative path
    assert root_history.child_history_mappings["A/AA"] == aa_history
    assert root_history.child_history_mappings["B"] == b_history
    assert root_history.child_history_mappings["B/BB"] == bb_history
    assert b_history.child_history_mappings["BB"] == bb_history

    # check if the correct (child) histories are returned for a given path
    assert root_history.find_history_for_path("Stuff.txt")[0] == root_history
    assert root_history.find_history_for_path("A/AA/AA1.txt")[0] == aa_history
    assert root_history.find_history_for_path(
        "A/AB/AB1.txt")[0] == root_history
    assert root_history.find_history_for_path("B/B1.txt")[0] == b_history
    assert root_history.find_history_for_path("B/BA/BA1.txt")[0] == b_history
    assert root_history.find_history_for_path("B/BB/BB1.txt")[0] == bb_history

    # the history object should only return the media hashes and hash entries it contains directly
    # if we need th entries from child histories we have to ask them directly
    assert root_history.find_original_hash_entry_for_path(
        "Stuff.txt") is not None
    assert root_history.find_original_hash_entry_for_path(
        "A/AA/AA1.txt") is None
    assert aa_history.find_original_hash_entry_for_path("AA1.txt") is not None
Example #8
0
def test_child_history_partial_verification_ba_1_file(fs,
                                                      nested_mhl_histories):
    """ """

    # create an additional file the record command will not add since we only pass it B1 as single file
    fs.create_file("/root/B/B2.txt", contents="B2\n")
    runner = CliRunner()
    result = runner.invoke(ascmhl.commands.create,
                           ["/root", "-sf", "/root/B/B1.txt"],
                           catch_exceptions=False)
    assert result.exit_code == 0

    # two new generations have been written
    assert os.path.isfile("/root/ascmhl/0002_root_2020-01-16_091500.mhl")
    assert os.path.isfile("/root/B/ascmhl/0002_B_2020-01-16_091500.mhl")

    root_history = MHLHistory.load_from_path("/root")
    assert len(root_history.hash_lists) == 2

    aa_history = root_history.child_histories[0]
    b_history = root_history.child_histories[1]
    bb_history = root_history.child_histories[1].child_histories[0]

    # the root hash list only contains a mhl reference to the hash list of the B history, no media hashes
    assert len(root_history.hash_lists[1].media_hashes) == 0
    assert root_history.hash_lists[1].referenced_hash_lists[
        0] == b_history.hash_lists[1]
    # the B hash list contains the media hash of the verified file
    assert b_history.hash_lists[1].media_hashes[0].path == "B1.txt"
    assert b_history.hash_lists[1].media_hashes[0].hash_entries[
        0].action == "verified"
    # the created B2 file is not referenced in the B history, only B1
    assert len(b_history.hash_lists[1].media_hashes) == 1

    # the other histories don't have a new generation
    assert not os.path.isfile(
        "/root/A/AA/ascmhl/0002_AA_2020-01-16_091500.mhl")
    assert not os.path.isfile(
        "/root/B/BB/ascmhl/0002_BB_2020-01-16_091500.mhl")
    assert aa_history.latest_generation_number() == 1
    assert bb_history.latest_generation_number() == 1
Example #9
0
def test_create_nested_new_format(fs, nested_mhl_histories):
    """
    test that ensures that hasehs in a new format are also verified in child histories
    used to verify fix of bug: https://github.com/ascmitc/mhl/issues/48
    """

    runner = CliRunner()
    result = runner.invoke(ascmhl.commands.create, ["/root", "-h", "md5"])
    assert result.exit_code == 0

    # load one of the the nested histories and check the first media hash of the last generation
    nested_history = MHLHistory.load_from_path("/root/A/AA")
    media_hash = nested_history.hash_lists[-1].media_hashes[0]

    # assure that the first hash entry is the verification of the original hash
    assert media_hash.hash_entries[0].action == "verified"
    assert media_hash.hash_entries[0].hash_format == "xxh64"

    # assure that the second hash entry is the new md5 hash
    assert media_hash.hash_entries[1].action == "verified"  # formerly 'new'
    assert media_hash.hash_entries[1].hash_format == "md5"
Example #10
0
def test_create_no_directory_hashes(fs):
    fs.create_file("/root/Stuff.txt", contents="stuff\n")
    fs.create_file("/root/A/A1.txt", contents="A1\n")
    os.mkdir("/root/emptyFolder")

    runner = CliRunner()
    result = runner.invoke(ascmhl.commands.create, ["/root", "-v", "-n"])
    assert result.exit_code == 0

    # a directory entry without hash was created for the folder A
    hash_list = MHLHistory.load_from_path("/root").hash_lists[0]
    assert hash_list.find_media_hash_for_path("A").is_directory
    assert len(hash_list.find_media_hash_for_path("A").hash_entries) == 0
    # and no directory hash of the root folder is set in the header
    assert len(hash_list.process_info.root_media_hash.hash_entries) == 0
    # the empty folder is still referenced even if not creating directory hashes
    assert hash_list.find_media_hash_for_path("emptyFolder").is_directory

    # removing an empty folder will cause creating a new generation to fail
    os.removedirs("/root/emptyFolder")
    runner = CliRunner()
    result = runner.invoke(ascmhl.commands.create, ["/root", "-v", "-n"])
    assert result.exit_code == 15
    assert "1 missing file(s):\n  emptyFolder" in result.output
Example #11
0
def test_create_directory_hashes(fs):
    fs.create_file("/root/Stuff.txt", contents="stuff\n")
    fs.create_file("/root/A/A1.txt", contents="A1\n")

    result = CliRunner().invoke(ascmhl.commands.create, ["/root", "-h", "xxh64", "-v"])
    assert result.exit_code == 0

    # a directory hash for the folder A was created
    hash_list = MHLHistory.load_from_path("/root").hash_lists[0]
    assert hash_list.find_media_hash_for_path("A").is_directory
    assert hash_list.find_media_hash_for_path("A").hash_entries[0].hash_string == "95e230e90be29dd6"
    # and the directory hash of the root folder is set in the header
    assert hash_list.process_info.root_media_hash.hash_entries[0].hash_string == "36e824bc313f3b77"

    # test that the directory-hash command creates the same directory hashes
    # FIXME: command doesn't exist any more, replace with tests of verify directory hashes command?
    #    result = CliRunner().invoke(ascmhl.commands.directory_hash, ["/root", "-v"])
    #    assert result.exit_code == 0
    #    assert "directory hash for: /root/A xxh64: ee2c3b94b6eecb8d" in result.output
    #    assert "root hash: xxh64: 15ef0ade91fff267" in result.output

    # add some more files and folders
    fs.create_file("/root/B/B1.txt", contents="B1\n")
    fs.create_file("/root/A/A2.txt", contents="A2\n")
    fs.create_file("/root/A/AA/AA1.txt", contents="AA1\n")
    os.mkdir("/root/emptyFolderA")
    os.mkdir("/root/emptyFolderB")
    os.mkdir("/root/emptyFolderC")
    os.mkdir("/root/emptyFolderC/emptyFolderCA")
    os.mkdir("/root/emptyFolderC/emptyFolderCB")

    runner = CliRunner()
    result = runner.invoke(ascmhl.commands.create, ["/root", "-v", "-h", "xxh64"])
    assert result.exit_code == 0

    hash_list = MHLHistory.load_from_path("/root").hash_lists[-1]
    # due to the additional content the directory hash of folder A and the root folder changed
    assert hash_list.find_media_hash_for_path("A").hash_entries[0].hash_string == "a8d0ad812ab102bd"
    assert hash_list.process_info.root_media_hash.hash_entries[0].hash_string == "d6b881fed0b325bd"
    # empty folder all have the same directory hash
    assert hash_list.find_media_hash_for_path("emptyFolderA").hash_entries[0].hash_string == "ef46db3751d8e999"
    assert hash_list.find_media_hash_for_path("emptyFolderB").hash_entries[0].hash_string == "ef46db3751d8e999"
    # but since we also contain the file names in the dir hashes an empty folder that contains other empty folders
    # has a different directory structure hash
    assert (
        hash_list.find_media_hash_for_path("emptyFolderC").hash_entries[0].structure_hash_string == "a5e6b8f95dfe2762"
    )
    # the content hash stays the same
    assert hash_list.find_media_hash_for_path("emptyFolderC").hash_entries[0].hash_string == "ef46db3751d8e999"

    # test that the directory-hash command creates the same directory hashes
    # FIXME: command doesn't exist any more, replace with tests of verify directory hashes command?
    #    result = CliRunner().invoke(ascmhl.commands.directory_hash, ["/root"])
    #    assert result.exit_code == 0
    #    assert "  calculated root hash: xxh64: 5f4af3b3fd736415" in result.output

    # altering the content of one file
    with open("/root/A/A2.txt", "a") as file:
        file.write("!!")

    runner = CliRunner()
    result = runner.invoke(ascmhl.commands.create, ["/root", "-v", "-h", "xxh64"])
    assert "ERROR: hash mismatch for        A/A2.txt" in result.output
    hash_list = MHLHistory.load_from_path("/root").hash_lists[-1]
    # an altered file leads to a different root directory hash
    assert hash_list.process_info.root_media_hash.hash_entries[0].hash_string == "cae6659fc7b34c2f"
    # structure hash stays the same
    assert hash_list.process_info.root_media_hash.hash_entries[0].structure_hash_string == "2c99e94e8fa7d90c"

    # test that the directory-hash command creates the same root hash
    # FIXME: command doesn't exist any more, replace with tests of verify directory hashes command?
    #    result = CliRunner().invoke(ascmhl.commands.directory_hash, ["/root"])
    #    assert result.exit_code == 0
    #    assert "root hash: xxh64: adf18c910489663c" in result.output

    assert hash_list.find_media_hash_for_path("B").hash_entries[0].hash_string == "51fb8fb099e92821"
    assert hash_list.find_media_hash_for_path("B").hash_entries[0].structure_hash_string == "945ecf443295ffbd"
    assert hash_list.process_info.root_media_hash.hash_entries[0].hash_string == "cae6659fc7b34c2f"
    assert hash_list.process_info.root_media_hash.hash_entries[0].structure_hash_string == "2c99e94e8fa7d90c"

    # rename one file
    os.rename("/root/B/B1.txt", "/root/B/B2.txt")

    runner = CliRunner()
    result = runner.invoke(ascmhl.commands.create, ["/root", "-v", "-h", "xxh64"])
    assert "ERROR: hash mismatch for        A/A2.txt" in result.output
    # in addition to the failing verification we also have a missing file B1/B1.txt
    assert "missing file(s):\n  B/B1.txt" in result.output
    hash_list = MHLHistory.load_from_path("/root").hash_lists[-1]
    # the file name is part of the structure directory hash of the containing directory so it's hash changes
    assert hash_list.find_media_hash_for_path("B").hash_entries[0].structure_hash_string == "fa4e99472911e118"
    # .. and the content hash stays the same
    assert hash_list.find_media_hash_for_path("B").hash_entries[0].hash_string == "51fb8fb099e92821"

    # a renamed file also leads to a different root structure directory hash
    assert hash_list.process_info.root_media_hash.hash_entries[0].structure_hash_string == "b758c9b165fb6c2a"
    # and an unchanged content hash
    assert hash_list.process_info.root_media_hash.hash_entries[0].hash_string == "cae6659fc7b34c2f"
Example #12
0
def test_create_directory_hashes(fs):
    fs.create_file("/root/Stuff.txt", contents="stuff\n")
    fs.create_file("/root/A/A1.txt", contents="A1\n")

    result = CliRunner().invoke(ascmhl.commands.create, ["/root", "-h", "xxh64", "-v"])
    assert result.exit_code == 0

    # a directory hash for the folder A was created
    hash_list = MHLHistory.load_from_path("/root").hash_lists[0]
    assert hash_list.find_media_hash_for_path("A").is_directory
    assert hash_list.find_media_hash_for_path("A").hash_entries[0].hash_string == "d3904ee76bba3d2a"
    # and the directory hash of the root folder is set in the header
    assert hash_list.process_info.root_media_hash.hash_entries[0].hash_string == "ca56d22f064fdf1b"

    # test that the directory-hash command creates the same directory hashes
    # FIXME: command doesn't exist any more, replace with tests of verify directory hashes command?
    #    result = CliRunner().invoke(ascmhl.commands.directory_hash, ["/root", "-v"])
    #    assert result.exit_code == 0
    #    assert "directory hash for: /root/A xxh64: ee2c3b94b6eecb8d" in result.output
    #    assert "root hash: xxh64: 15ef0ade91fff267" in result.output

    # add some more files and folders
    fs.create_file("/root/B/B1.txt", contents="B1\n")
    fs.create_file("/root/A/A2.txt", contents="A2\n")
    fs.create_file("/root/A/AA/AA1.txt", contents="AA1\n")
    os.mkdir("/root/emptyFolderA")
    os.mkdir("/root/emptyFolderB")

    os.mkdir("/root/emptyFolderC")
    os.mkdir("/root/emptyFolderC/emptyFolderCA")
    os.mkdir("/root/emptyFolderC/emptyFolderCB")

    runner = CliRunner()
    result = runner.invoke(ascmhl.commands.create, ["/root", "-v", "-h", "xxh64"])
    assert result.exit_code == 0

    hash_list = MHLHistory.load_from_path("/root").hash_lists[-1]
    # due to the additional content the directory hash of folder A and the root folder changed
    assert hash_list.find_media_hash_for_path("A").hash_entries[0].hash_string == "cc195301a14023a9"
    assert hash_list.process_info.root_media_hash.hash_entries[0].hash_string == "4ccac5e6856ecf04"
    # empty folder all have the same directory hash
    assert hash_list.find_media_hash_for_path("emptyFolderA").hash_entries[0].hash_string == "ef46db3751d8e999"
    assert hash_list.find_media_hash_for_path("emptyFolderB").hash_entries[0].hash_string == "ef46db3751d8e999"
    # but since we also contain the file names in the dir hashes an empty folder that contains other empty folders
    # has a different directory structure hash
    assert (
        hash_list.find_media_hash_for_path("emptyFolderC").hash_entries[0].structure_hash_string == "949018e6a4932905"
    )
    # FIXME: emtpyFolderC isn't really empty is it - it has empty dirs inside of it.
    # how do we want to handle empty dirs in empty dirs.
    # the content hash stays the same
    assert hash_list.find_media_hash_for_path("emptyFolderC").hash_entries[0].hash_string == "cf4b060700272aa6"

    # test that the directory-hash command creates the same directory hashes
    # FIXME: command doesn't exist any more, replace with tests of verify directory hashes command?
    #    result = CliRunner().invoke(ascmhl.commands.directory_hash, ["/root"])
    #    assert result.exit_code == 0
    #    assert "  calculated root hash: xxh64: 5f4af3b3fd736415" in result.output

    # altering the content of one file
    with open("/root/A/A2.txt", "a") as file:
        file.write("!!")

    runner = CliRunner()
    result = runner.invoke(ascmhl.commands.create, ["/root", "-v", "-h", "xxh64", "-h", "md5"])
    assert "ERROR: hash mismatch for        A/A2.txt" in result.output
    hash_list = MHLHistory.load_from_path("/root").hash_lists[-1]
    # an altered file leads to a different root directory hash
    assert hash_list.process_info.root_media_hash.hash_entries[1].hash_string == "28ed09733f793dfc"
    # structure hash stays the same
    assert hash_list.process_info.root_media_hash.hash_entries[1].structure_hash_string == "89e4debdb80cc068"

    # test that the directory-hash command creates the same root hash
    # FIXME: command doesn't exist any more, replace with tests of verify directory hashes command?
    #    result = CliRunner().invoke(ascmhl.commands.directory_hash, ["/root"])
    #    assert result.exit_code == 0
    #    assert "root hash: xxh64: adf18c910489663c" in result.output

    assert hash_list.find_media_hash_for_path("B").hash_entries[0].hash_string == "d6df246725efff6ceaee31f663a32cf8"
    assert hash_list.find_media_hash_for_path("B").hash_entries[1].hash_string == "aab0eba57cd1aca9"

    assert (
        hash_list.find_media_hash_for_path("B").hash_entries[0].structure_hash_string
        == "a21e164c1df944733e5e3d4e4ed64f90"
    )
    assert hash_list.find_media_hash_for_path("B").hash_entries[1].structure_hash_string == "fac2a2ceb0fa0c0b"

    assert hash_list.process_info.root_media_hash.hash_entries[1].hash_string == "28ed09733f793dfc"
    assert hash_list.process_info.root_media_hash.hash_entries[1].structure_hash_string == "89e4debdb80cc068"

    # rename one file
    os.rename("/root/B/B1.txt", "/root/B/B2.txt")

    runner = CliRunner()
    result = runner.invoke(ascmhl.commands.create, ["/root", "-v", "-h", "xxh64", "-h", "c4"])
    assert "ERROR: hash mismatch for        A/A2.txt" in result.output
    # in addition to the failing verification we also have a missing file B1/B1.txt
    assert "missing file(s):\n  B/B1.txt" in result.output
    hash_list = MHLHistory.load_from_path("/root").hash_lists[-1]
    # the file name is part of the structure directory hash of the containing directory so it's hash changes
    assert (
        hash_list.find_media_hash_for_path("B").hash_entries[0].structure_hash_string
        == "c42qegPDBxh16Vqi4qFGh1EQv39nEbVmZ9R1LGkaVr1dEBRcD69pH3r5vdGDSwceQLEZc872kQho5Cforb95s2wjH8"
    )
    assert hash_list.find_media_hash_for_path("B").hash_entries[1].structure_hash_string == "7ae620e883160eb3"
    # .. and the content hash stays the same
    assert (
        hash_list.find_media_hash_for_path("B").hash_entries[0].hash_string
        == "c43X1ve8nmicwGit4fnhs428pTCV6ZjXQsorxPLNx3396oRuQFaq79iLR2ZsPoWN8yckFzZdkqZ21igH8K7rWAoDMa"
    )
    assert hash_list.find_media_hash_for_path("B").hash_entries[1].hash_string == "aab0eba57cd1aca9"

    # a renamed file also leads to a different root structure directory hash
    assert hash_list.process_info.root_media_hash.hash_entries[1].structure_hash_string == "0bba67923d19d36b"
    # and an unchanged content hash
    assert hash_list.process_info.root_media_hash.hash_entries[1].hash_string == "28ed09733f793dfc"