Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions tests/test_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
"""

import os
import datetime
from freezegun import freeze_time
from click.testing import CliRunner
from .conftest import path_conversion_tests
from .conftest import abspath_conversion_tests

from ascmhl import utils
from ascmhl.generator import MHLGenerationCreationSession
from ascmhl.hashlist import MHLHashEntry
from ascmhl.history import MHLHistory
import ascmhl.commands

Expand Down Expand Up @@ -376,3 +379,63 @@ def test_create_mulitple_hashformats_double_hashformat(fs, simple_mhl_history):
print(result.output)

assert result.exit_code == 0


@freeze_time("2020-01-15 13:00:00")
def test_multiple_format_hash_entries_are_instances(fs):
"""Bug: generator.py:75 — hash_entries = [MHLHashEntry] includes the class
itself as a list element. Every hash entry must be an instance, not the class."""
fs.create_file("/root/Stuff.txt", contents="stuff\n")

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

history = MHLHistory.load_from_path(abspath_conversion_tests("/root"))
session = MHLGenerationCreationSession(history)

file_path = abspath_conversion_tests("/root/Stuff.txt")
file_size = os.path.getsize(file_path)
modification_date = datetime.datetime.fromtimestamp(os.path.getmtime(file_path))

hash_lookup = {"xxh64": "aabbccdd11223344", "md5": "d41d8cd98f00b204e9800998ecf8427e"}
session.append_multiple_format_file_hashes(file_path, file_size, hash_lookup, modification_date)

new_hash_list = session.new_hash_lists[history]
media_hash = new_hash_list.find_media_hash_for_path("Stuff.txt")
assert media_hash is not None

for entry in media_hash.hash_entries:
assert isinstance(entry, MHLHashEntry), (
f"Expected an instance of MHLHashEntry, got {entry!r}. "
f"The MHLHashEntry class itself was inserted into hash_entries."
)


@freeze_time("2020-01-15 13:00:00")
def test_multiple_format_hash_entries_correct_count(fs):
"""Bug: generator.py:75 — hash_entries = [MHLHashEntry] causes 3 entries
instead of 2 when appending 2 hash formats."""
fs.create_file("/root/File.txt", contents="data\n")

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

history = MHLHistory.load_from_path(abspath_conversion_tests("/root"))
session = MHLGenerationCreationSession(history)

file_path = abspath_conversion_tests("/root/File.txt")
file_size = os.path.getsize(file_path)
modification_date = datetime.datetime.fromtimestamp(os.path.getmtime(file_path))

hash_lookup = {"xxh64": "aabbccdd11223344", "md5": "d41d8cd98f00b204e9800998ecf8427e"}
session.append_multiple_format_file_hashes(file_path, file_size, hash_lookup, modification_date)

new_hash_list = session.new_hash_lists[history]
media_hash = new_hash_list.find_media_hash_for_path("File.txt")
assert media_hash is not None
assert len(media_hash.hash_entries) == 2, (
f"Expected 2 hash entries (one per format), got {len(media_hash.hash_entries)}. "
f"Extra entry is likely the MHLHashEntry class from `hash_entries = [MHLHashEntry]`."
)
24 changes: 24 additions & 0 deletions tests/test_hasher.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import pytest
from ascmhl.hasher import *
from ascmhl.hasher import AggregateHasher


def test_cannot_instantiate_abstract_classes():
Expand Down Expand Up @@ -149,3 +150,26 @@ def test_hash_list_concat_equals_chunking():
concat = hasher.bytes_from_string_digest("".join(hash_list))
h2 = hasher.hash_data(concat)
assert h1 == h2 # assert that sequentially feeding hashes is same as concat then feeding hashes


def test_aggregate_hasher_can_be_instantiated():
"""Bug: hasher.py:250 — hasher_lookup = Dict[str, str] assigns the type
instead of creating an empty dict, crashing on item assignment."""
try:
hasher = AggregateHasher(["xxh64", "md5"])
except TypeError as e:
pytest.fail(
f"AggregateHasher.__init__ crashed: {e}. "
f"hasher_lookup = Dict[str, str] assigns the type, not an empty dict."
)


def test_aggregate_hasher_has_correct_lookups():
"""Bug: hasher.py:250 — after instantiation, hasher_lookup should be a
real dict keyed by format name."""
hasher = AggregateHasher(["xxh64"])
assert isinstance(hasher.hasher_lookup, dict), (
f"hasher_lookup should be a dict, got {type(hasher.hasher_lookup)}. "
f"It was assigned Dict[str, str] (a type) instead of an empty dict."
)
assert "xxh64" in hasher.hasher_lookup
102 changes: 102 additions & 0 deletions tests/test_nested.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@
"""

import os
import pytest
from .conftest import abspath_conversion_tests
from .conftest import path_conversion_tests

from click.testing import CliRunner
from freezegun import freeze_time

import ascmhl
from ascmhl.hashlist import MHLHashList, MHLHashListReference, MHLProcessInfo
from ascmhl.history import MHLHistory


@freeze_time("2020-01-16 09:15:00")
Expand Down Expand Up @@ -197,3 +200,102 @@ def test_create_nested_mhl_chain_missing(fs):
result = runner.invoke(ascmhl.commands.diff, [abspath_conversion_tests("/root")])
assert result.exception
assert result.exit_code == 32


def test_nested_history_no_crash_when_child_has_no_ignore_patterns():
"""Bug: history.py:105 — latest_ignore_pattern_from_nested_histories iterates
over latest_ignore_patterns() which can return None, causing TypeError."""
parent = MHLHistory()
parent.asc_mhl_path = "/root/ascmhl"

child = MHLHistory()
child.asc_mhl_path = "/root/sub/ascmhl"
child.parent_history = parent
# child has NO hash_lists, so latest_ignore_patterns() returns None

parent.child_histories.append(child)
parent.child_history_mappings["sub"] = child

try:
parent.latest_ignore_pattern_from_nested_histories()
except TypeError as e:
pytest.fail(
f"latest_ignore_pattern_from_nested_histories crashed with TypeError: {e}. "
f"latest_ignore_patterns() returned None for a child with no hash lists."
)


def test_nested_history_no_crash_when_child_has_none_ignore_spec():
"""Bug: history.py:105 — same crash when child has hash lists but
the latest has ignore_spec set to None."""
parent = MHLHistory()
parent.asc_mhl_path = "/root/ascmhl"

child = MHLHistory()
child.asc_mhl_path = "/root/sub/ascmhl"
child.parent_history = parent

hash_list = MHLHashList()
hash_list.process_info = MHLProcessInfo()
hash_list.process_info.ignore_spec = None
child.hash_lists.append(hash_list)

parent.child_histories.append(child)
parent.child_history_mappings["sub"] = child

try:
parent.latest_ignore_pattern_from_nested_histories()
except TypeError as e:
pytest.fail(
f"latest_ignore_pattern_from_nested_histories crashed with TypeError: {e}. "
f"latest_ignore_patterns() returned None for a child with None ignore_spec."
)


def test_resolve_references_continues_after_missing_child():
"""Bug: history.py:370 — _resolve_hash_list_references uses `return`
instead of `continue` when a reference path is not found, aborting
resolution of all remaining references."""
history = MHLHistory()
history.asc_mhl_path = "/root/ascmhl"

# Create a child history that can be found
child = MHLHistory()
child.asc_mhl_path = "/root/child_b/ascmhl"
child.parent_history = history

child_hash_list = MHLHashList()
child_hash_list.file_path = "/root/child_b/ascmhl/0001_child_b.mhl"
child_hash_list.generation_number = 1
child.hash_lists.append(child_hash_list)

history.child_histories.append(child)
history.child_history_mappings["child_b"] = child

# Hash list with two references: first missing, second resolvable
hash_list = MHLHashList()
hash_list.file_path = "/root/ascmhl/0001_root.mhl"

ref_missing = MHLHashListReference()
ref_missing.path = "child_a/ascmhl/0001_child_a.mhl"
ref_missing.reference_hash = "fake_hash_a"

ref_found = MHLHashListReference()
ref_found.path = "child_b/ascmhl/0001_child_b.mhl"
child_hash_list.generate_reference_hash = lambda: "valid_hash_b"
ref_found.reference_hash = "valid_hash_b"

hash_list.hash_list_references.append(ref_missing)
hash_list.hash_list_references.append(ref_found)
history.hash_lists.append(hash_list)

history._resolve_hash_list_references()

assert len(hash_list.referenced_hash_lists) > 0, (
"No references were resolved. The method returned early when it "
"encountered the first missing reference instead of continuing."
)
assert child_hash_list in hash_list.referenced_hash_lists, (
"The known child_b reference was not resolved. The method "
"aborted at the missing child_a reference (return vs continue)."
)