2026-03-11 22:50:02 -04:00
|
|
|
#!/usr/bin/env python3
|
2026-03-11 23:15:07 -04:00
|
|
|
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
2026-03-11 22:50:02 -04:00
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
|
|
|
import csv
|
|
|
|
|
import io
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
import tempfile
|
|
|
|
|
import unittest
|
|
|
|
|
import zipfile
|
|
|
|
|
from unittest.mock import patch
|
|
|
|
|
|
|
|
|
|
from script.database.migrate.process_backup_file import process_zip
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestProcessZip(unittest.TestCase):
|
|
|
|
|
"""Test ZIP file processing to remove lines containing a keyword."""
|
|
|
|
|
|
|
|
|
|
def _create_zip(self, path, filename, content):
|
|
|
|
|
with zipfile.ZipFile(path, "w") as zf:
|
|
|
|
|
zf.writestr(filename, content)
|
|
|
|
|
|
|
|
|
|
def test_removes_matching_lines(self):
|
|
|
|
|
tmpdir = tempfile.mkdtemp()
|
|
|
|
|
input_zip = os.path.join(tmpdir, "input.zip")
|
|
|
|
|
output_zip = os.path.join(tmpdir, "output.zip")
|
|
|
|
|
self._create_zip(
|
|
|
|
|
input_zip,
|
|
|
|
|
"dump.sql",
|
|
|
|
|
"keep this line\ndelete secret line\nkeep also\n",
|
|
|
|
|
)
|
|
|
|
|
process_zip(input_zip, output_zip, "secret", "dump.sql")
|
|
|
|
|
with zipfile.ZipFile(output_zip) as zf:
|
|
|
|
|
content = zf.read("dump.sql").decode()
|
|
|
|
|
self.assertIn("keep this line", content)
|
|
|
|
|
self.assertIn("keep also", content)
|
|
|
|
|
self.assertNotIn("secret", content)
|
|
|
|
|
|
|
|
|
|
def test_preserves_all_when_no_match(self):
|
|
|
|
|
tmpdir = tempfile.mkdtemp()
|
|
|
|
|
input_zip = os.path.join(tmpdir, "input.zip")
|
|
|
|
|
output_zip = os.path.join(tmpdir, "output.zip")
|
|
|
|
|
original = "line1\nline2\nline3\n"
|
|
|
|
|
self._create_zip(input_zip, "data.sql", original)
|
|
|
|
|
process_zip(input_zip, output_zip, "nomatch", "data.sql")
|
|
|
|
|
with zipfile.ZipFile(output_zip) as zf:
|
|
|
|
|
content = zf.read("data.sql").decode()
|
|
|
|
|
self.assertIn("line1", content)
|
|
|
|
|
self.assertIn("line2", content)
|
|
|
|
|
self.assertIn("line3", content)
|
|
|
|
|
|
|
|
|
|
def test_removes_all_matching(self):
|
|
|
|
|
tmpdir = tempfile.mkdtemp()
|
|
|
|
|
input_zip = os.path.join(tmpdir, "input.zip")
|
|
|
|
|
output_zip = os.path.join(tmpdir, "output.zip")
|
|
|
|
|
self._create_zip(
|
|
|
|
|
input_zip,
|
|
|
|
|
"dump.sql",
|
|
|
|
|
"bad line\nbad again\nbad too\n",
|
|
|
|
|
)
|
|
|
|
|
process_zip(input_zip, output_zip, "bad", "dump.sql")
|
|
|
|
|
with zipfile.ZipFile(output_zip) as zf:
|
|
|
|
|
content = zf.read("dump.sql").decode()
|
|
|
|
|
self.assertEqual(content.strip(), "")
|
|
|
|
|
|
|
|
|
|
def test_other_files_untouched(self):
|
|
|
|
|
tmpdir = tempfile.mkdtemp()
|
|
|
|
|
input_zip = os.path.join(tmpdir, "input.zip")
|
|
|
|
|
output_zip = os.path.join(tmpdir, "output.zip")
|
|
|
|
|
with zipfile.ZipFile(input_zip, "w") as zf:
|
|
|
|
|
zf.writestr("target.sql", "keep\nremove secret\n")
|
|
|
|
|
zf.writestr("other.txt", "secret stays here\n")
|
|
|
|
|
process_zip(input_zip, output_zip, "secret", "target.sql")
|
|
|
|
|
with zipfile.ZipFile(output_zip) as zf:
|
|
|
|
|
target = zf.read("target.sql").decode()
|
|
|
|
|
other = zf.read("other.txt").decode()
|
|
|
|
|
self.assertNotIn("secret", target)
|
|
|
|
|
self.assertIn("secret", other)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestCompareDatabaseApplicationLogic(unittest.TestCase):
|
|
|
|
|
"""Test CSV set comparison logic used by compare_database_application."""
|
|
|
|
|
|
|
|
|
|
def _write_csv(self, path, rows, fieldnames):
|
|
|
|
|
with open(path, "w", newline="") as f:
|
|
|
|
|
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
|
|
|
|
writer.writeheader()
|
|
|
|
|
writer.writerows(rows)
|
|
|
|
|
|
|
|
|
|
def test_identical_csvs(self):
|
|
|
|
|
tmpdir = tempfile.mkdtemp()
|
|
|
|
|
csv1 = os.path.join(tmpdir, "a.csv")
|
|
|
|
|
csv2 = os.path.join(tmpdir, "b.csv")
|
|
|
|
|
rows = [{"name": "mod1"}, {"name": "mod2"}]
|
|
|
|
|
self._write_csv(csv1, rows, ["name"])
|
|
|
|
|
self._write_csv(csv2, rows, ["name"])
|
|
|
|
|
with open(csv1) as f1, open(csv2) as f2:
|
|
|
|
|
r1 = csv.DictReader(f1)
|
|
|
|
|
r2 = csv.DictReader(f2)
|
|
|
|
|
s1 = {a["name"] for a in r1}
|
|
|
|
|
s2 = {a["name"] for a in r2}
|
|
|
|
|
self.assertEqual(s1, s2)
|
|
|
|
|
self.assertEqual(len(s1.difference(s2)), 0)
|
|
|
|
|
|
|
|
|
|
def test_different_csvs(self):
|
|
|
|
|
tmpdir = tempfile.mkdtemp()
|
|
|
|
|
csv1 = os.path.join(tmpdir, "a.csv")
|
|
|
|
|
csv2 = os.path.join(tmpdir, "b.csv")
|
|
|
|
|
self._write_csv(csv1, [{"name": "mod1"}, {"name": "mod2"}], ["name"])
|
|
|
|
|
self._write_csv(csv2, [{"name": "mod2"}, {"name": "mod3"}], ["name"])
|
|
|
|
|
with open(csv1) as f1, open(csv2) as f2:
|
|
|
|
|
r1 = csv.DictReader(f1)
|
|
|
|
|
r2 = csv.DictReader(f2)
|
|
|
|
|
s1 = {a["name"] for a in r1}
|
|
|
|
|
s2 = {a["name"] for a in r2}
|
|
|
|
|
self.assertEqual(s1.intersection(s2), {"mod2"})
|
|
|
|
|
self.assertEqual(s1.difference(s2), {"mod1"})
|
|
|
|
|
self.assertEqual(s2.difference(s1), {"mod3"})
|
|
|
|
|
|
|
|
|
|
def test_empty_csvs(self):
|
|
|
|
|
tmpdir = tempfile.mkdtemp()
|
|
|
|
|
csv1 = os.path.join(tmpdir, "a.csv")
|
|
|
|
|
csv2 = os.path.join(tmpdir, "b.csv")
|
|
|
|
|
self._write_csv(csv1, [], ["name"])
|
|
|
|
|
self._write_csv(csv2, [], ["name"])
|
|
|
|
|
with open(csv1) as f1, open(csv2) as f2:
|
|
|
|
|
r1 = csv.DictReader(f1)
|
|
|
|
|
r2 = csv.DictReader(f2)
|
|
|
|
|
s1 = {a["name"] for a in r1}
|
|
|
|
|
s2 = {a["name"] for a in r2}
|
|
|
|
|
self.assertEqual(len(s1.union(s2)), 0)
|
|
|
|
|
|
|
|
|
|
def test_one_empty_csv(self):
|
|
|
|
|
tmpdir = tempfile.mkdtemp()
|
|
|
|
|
csv1 = os.path.join(tmpdir, "a.csv")
|
|
|
|
|
csv2 = os.path.join(tmpdir, "b.csv")
|
|
|
|
|
self._write_csv(csv1, [{"name": "mod1"}], ["name"])
|
|
|
|
|
self._write_csv(csv2, [], ["name"])
|
|
|
|
|
with open(csv1) as f1, open(csv2) as f2:
|
|
|
|
|
r1 = csv.DictReader(f1)
|
|
|
|
|
r2 = csv.DictReader(f2)
|
|
|
|
|
s1 = {a["name"] for a in r1}
|
|
|
|
|
s2 = {a["name"] for a in r2}
|
|
|
|
|
self.assertEqual(s1.difference(s2), {"mod1"})
|
|
|
|
|
self.assertEqual(len(s2.difference(s1)), 0)
|