"""Synthetic tests only. No spreadsheet or formula evaluation.""" import csv import subprocess import sys import tempfile import unittest from pathlib import Path from script import flag_csv ROOT = Path(__file__).resolve().parent class FlagCSVTests(unittest.TestCase): def setUp(self): self.temp = tempfile.TemporaryDirectory(dir=ROOT) self.addCleanup(self.temp.cleanup) self.source = Path(self.temp.name) / "source.csv" self.output = Path(self.temp.name) / "output.csv" def put(self, text): self.source.write_bytes(text.encode("utf-8")) def read(self, path): with path.open(encoding="utf-8", newline="") as handle: return list(csv.reader(handle, strict=True)) def test_repeated_rows_retained_in_order(self): self.put("a,b\nx,y\nx,y\nx,y\nz,w\n") self.assertEqual(flag_csv(self.source, self.output), (4, 1, 2, 3)) result = self.read(self.output) self.assertEqual([r[:-1] for r in result[1:]], self.read(self.source)[1:]) self.assertEqual([r[-1] for r in result[1:]], ["1", "1", "1", "0"]) def test_whitespace_case_and_numeric_strings_distinct(self): self.put("a\nx\nx \nX\n01\n1\n") self.assertEqual(flag_csv(self.source, self.output), (5, 0, 0, 0)) self.assertEqual([r[0] for r in self.read(self.output)[1:]], ["x", "x ", "X", "01", "1"]) def test_quoted_commas_and_newlines(self): self.put("a,b\n\"x,y\",\"two\nlines\"\n\"x,y\",\"two\nlines\"\n") self.assertEqual(flag_csv(self.source, self.output), (2, 1, 1, 2)) self.assertEqual(self.read(self.output)[1:], [["x,y", "two\nlines", "1"]] * 2) def test_formula_leading_values_literal(self): self.put("a\n=1+1\n+1\n-1\n@SUM(A1)\n") flag_csv(self.source, self.output) self.assertEqual([r[0] for r in self.read(self.output)[1:]], ["=1+1", "+1", "-1", "@SUM(A1)"]) def test_wrong_row_lengths_rejected_without_output(self): for text in ["a,b\nx\n", "a,b\nx,y,z\n", "a,b\n\n"]: with self.subTest(text=text): self.put(text) with self.assertRaisesRegex(ValueError, "expected 2 fields"): flag_csv(self.source, self.output) self.assertFalse(self.output.exists()) def test_duplicate_headers_rejected(self): self.put("a,a\nx,y\n") with self.assertRaisesRegex(ValueError, "duplicate header"): flag_csv(self.source, self.output) self.assertFalse(self.output.exists()) def test_flag_header_collision_rejected(self): self.put("a,is_duplicate\nx,0\n") with self.assertRaisesRegex(ValueError, "header collision"): flag_csv(self.source, self.output) self.assertFalse(self.output.exists()) def test_missing_and_empty_headers_rejected(self): for text in ["", "\n", "a,\nx,y\n"]: with self.subTest(text=text): self.put(text) with self.assertRaisesRegex(ValueError, "nonempty header"): flag_csv(self.source, self.output) self.assertFalse(self.output.exists()) def test_invalid_utf8_cli_error(self): self.source.write_bytes(b"a\n\xff\n") result = subprocess.run([sys.executable, str(ROOT / "script.py"), str(self.source), str(self.output)], capture_output=True, text=True) self.assertEqual(result.returncode, 2) self.assertIn("error:", result.stderr) self.assertIn("utf-8", result.stderr) self.assertEqual(result.stdout, "") self.assertFalse(self.output.exists()) def test_unclosed_quote_rejected(self): self.put("a\n\"unterminated\n") with self.assertRaises(csv.Error): flag_csv(self.source, self.output) self.assertFalse(self.output.exists()) def test_existing_output_not_overwritten(self): self.put("a\nx\n") self.output.write_bytes(b"keep me") with self.assertRaises(FileExistsError): flag_csv(self.source, self.output) self.assertEqual(self.output.read_bytes(), b"keep me") def test_same_path_rejected(self): self.put("a\nx\n") before = self.source.read_bytes() with self.assertRaisesRegex(ValueError, "different paths"): flag_csv(self.source, self.source) self.assertEqual(self.source.read_bytes(), before) def test_header_only_file(self): self.put("a,b\n") self.assertEqual(flag_csv(self.source, self.output), (0, 0, 0, 0)) self.assertEqual(self.read(self.output), [["a", "b", "is_duplicate"]]) def test_unicode_and_embedded_crlf_cr_preserved(self): rows = [["caf\u00e9", "\u6771\u4eac\r\nline"], ["cafe\u0301", "\u6771\u4eac\r\nline"], ["\U0001f642", "left\rright"], ["caf\u00e9", "\u6771\u4eac\r\nline"]] with self.source.open("w", encoding="utf-8", newline="") as handle: writer = csv.writer(handle) writer.writerow(["label", "note"]) writer.writerows(rows) self.assertEqual(flag_csv(self.source, self.output), (4, 1, 1, 2)) result = self.read(self.output) self.assertEqual([r[:-1] for r in result[1:]], rows) self.assertEqual([r[-1] for r in result[1:]], ["1", "0", "0", "1"]) def test_cli_fixture_exact_expected_output(self): result = subprocess.run([sys.executable, str(ROOT / "script.py"), str(ROOT / "fixture.csv"), str(self.output)], capture_output=True, text=True) self.assertEqual(result.returncode, 0, result.stderr) self.assertEqual(result.stdout, "rows=7 duplicate_groups=2 repeated_rows=2 flagged_rows=4\n") self.assertEqual(result.stderr, "") self.assertEqual(self.output.read_bytes(), (ROOT / "expected.csv").read_bytes()) if __name__ == "__main__": unittest.main()