from pathlib import Path import random import unittest from govoplan_campaign.backend.services.filenames import FilenameAllocator from govoplan_campaign.backend.services.zip_service import _normalized_members def legacy_names(names): used = set() result = [] for name in names: path = Path(name) candidate = name counter = 2 while candidate.casefold() in used: candidate = f"{path.stem} ({counter}){path.suffix}" counter += 1 used.add(candidate.casefold()) result.append(candidate) return result class FilenameAllocatorTests(unittest.TestCase): def test_exact_equivalence_with_colliding_suffixes_case_unicode_and_multiple_dots( self, ): choices = [ "report.pdf", "REPORT.PDF", "report (2).pdf", "report (3).pdf", "report (2) (2).pdf", ".hidden", "a.tar.gz", "A.TAR.GZ", "Straße.txt", "STRASSE.txt", "readme", ] rng = random.Random(42) for _ in range(30): names = [rng.choice(choices) for _ in range(200)] allocator = FilenameAllocator() self.assertEqual( legacy_names(names), [allocator.allocate(name) for name in names] ) def test_zip_and_message_names_preserve_every_input_and_sequence(self): names = ["a.pdf", "A.pdf", "a (2).pdf", "a.pdf"] paths = [Path(f"/synthetic/{index}/source") for index in range(len(names))] members = _normalized_members(list(zip(paths, names))) self.assertEqual(paths, [path for path, _ in members]) self.assertEqual(legacy_names(names), [name for _, name in members]) self.assertEqual("a.pdf", FilenameAllocator().allocate("a.pdf")) def test_many_collisions_have_linear_membership_work(self): class CountingSet(set): probes = 0 def __contains__(self, item): self.probes += 1 return super().__contains__(item) allocator = FilenameAllocator() allocator.used = CountingSet() for _ in range(10000): last = allocator.allocate("report.pdf") self.assertEqual("report (10000).pdf", last) self.assertEqual(10000, len(allocator.used)) self.assertEqual(19999, allocator.used.probes) if __name__ == "__main__": unittest.main()