Add --import-qr to run the QR->CSV step before importing

Wrap the QR decode/append step behind --import-qr on the importer so a
single command runs the whole pipeline: decode QR images, append only
new credentials to the CSV (deduped on otpauth://, existing entries
silently skipped), then validate and import. QR folder defaults to the
CSV's folder; override with --qr-images. Refactor qr_to_sheet into
reusable collect_candidates / apply_new_candidates / run_qr_import.
This commit is contained in:
2026-07-08 15:31:03 -07:00
parent 0fad68b9f6
commit a8a405600c
4 changed files with 109 additions and 37 deletions

View File

@@ -4,7 +4,8 @@ import tempfile
import unittest
from otpauth import parse_otpauth, canonical_key
from qr_to_sheet import existing_keys, dedup_new, append_csv, read_csv_rows
from qr_to_sheet import (existing_keys, dedup_new, append_csv, read_csv_rows,
apply_new_candidates)
class TestParseOtpauth(unittest.TestCase):
@@ -83,5 +84,33 @@ class TestCsvIO(unittest.TestCase):
self.assertEqual(read_csv_rows(os.path.join(tempfile.mkdtemp(), "nope.csv")), [])
class TestApplyNewCandidates(unittest.TestCase):
def _csv_with_A(self):
p = os.path.join(tempfile.mkdtemp(), "otp-keys.csv")
append_csv(p, [{"issuer": "X", "account": "a", "otpauth_uri": A, "source_file": "a.png"}])
return p
def test_only_new_otpauth_is_appended(self):
p = self._csv_with_A()
summary = apply_new_candidates([(A, "a2.png"), (B, "b.png"), (B, "b2.png")], p)
self.assertEqual(summary["added"], 1)
self.assertEqual(summary["duplicates"], 2)
rows = read_csv_rows(p)
self.assertEqual(len(rows), 2) # existing A + new B only
self.assertEqual(rows[1]["otpauth_uri"], B)
def test_all_existing_appends_nothing(self):
p = self._csv_with_A()
summary = apply_new_candidates([(A, "a2.png")], p)
self.assertEqual((summary["added"], summary["duplicates"]), (0, 1))
self.assertEqual(len(read_csv_rows(p)), 1) # unchanged
def test_dry_run_reports_but_does_not_write(self):
p = self._csv_with_A()
summary = apply_new_candidates([(B, "b.png")], p, dry_run=True)
self.assertEqual(summary["added"], 1)
self.assertEqual(len(read_csv_rows(p)), 1) # not written
if __name__ == "__main__":
unittest.main()