From 5de069eb883c9beaa6bf4460508aea0bfa0fec0e Mon Sep 17 00:00:00 2001 From: Nguyen Thanh Dat Date: Sun, 4 Oct 2026 21:49:32 +0700 Subject: [PATCH] Drop the duplicate DB save that crashed --submit --db (#3122) The save at the end of Submitter.dialog read self.args.db, an attribute argparse never creates: maigret.py declares --db with dest="db_file", and the line right above it in the same block already reads self.args.db_file. So `maigret --submit --db custom.json` ran the whole interactive dialog and then raised AttributeError on the final step. dialog() never returned True, so main() never reached save_db_safely() either, and the newly submitted site was lost after every question had been answered. The default path was unaffected, because the block is guarded by `db_file != settings.sites_db_path`. Removing the block rather than renaming the attribute: main() already persists the database after a successful submit, through save_db_safely(db, db_file, logger). That call takes its path from resolve_db_path(), which returns an absolute or module-relative path for a custom --db, so the removed line could have written to a different file than the rest of the run uses. save_db_safely also tolerates a read-only installation, which db.save_to_file() does not. The existing dialog tests pass a MagicMock, which answers to any attribute name, so the typo was invisible to them. The new test builds its namespace with the real parser. Co-authored-by: datrixlab <325650023+datrixlab@users.noreply.github.com> --- maigret/submit.py | 7 ------- tests/test_submit.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/maigret/submit.py b/maigret/submit.py index 440c63cb..73e81bcc 100644 --- a/maigret/submit.py +++ b/maigret/submit.py @@ -660,11 +660,4 @@ class Submitter: final_site = old_site if old_site else stripped_site self.db.update_site(final_site) - # save the db in file - if self.args.db_file != self.settings.sites_db_path: - print( - f"{Fore.GREEN}[+] Maigret DB is saved to {self.args.db}.{Style.RESET_ALL}" - ) - self.db.save_to_file(self.args.db) - return True diff --git a/tests/test_submit.py b/tests/test_submit.py index 7d894d31..54522a8f 100644 --- a/tests/test_submit.py +++ b/tests/test_submit.py @@ -208,6 +208,41 @@ async def test_dialog_selects_status_code_check(settings): assert db.sites[0].check_type == "status_code" +@pytest.mark.asyncio +async def test_dialog_completes_with_parser_built_args(settings, tmp_path): + # The dialog tests above hand Submitter a MagicMock, which answers to any + # attribute name -- a reference to an attribute argparse never builds stays + # invisible there. At runtime Submitter gets the namespace below, where --db + # lands on `db_file`. + from maigret.maigret import setup_arguments_parser + + args = setup_arguments_parser(settings).parse_args( + [ + "--db", + str(tmp_path / "custom_db.json"), + "--submit", + "https://example.com/claimed", + ] + ) + assert args.db_file == str(tmp_path / "custom_db.json") + assert not hasattr(args, "db") + + db = MaigretDatabase() + submitter = Submitter(db, settings, logging.getLogger("test_logger"), args) + submitter.detect_known_engine = AsyncMock(return_value=([], "")) + submitter.extract_username_dialog = MagicMock(return_value="claimed") + submitter.check_features_manually = AsyncMock( + return_value=(None, None, "Found", "unclaimed", 200, 404) + ) + submitter.site_self_check = AsyncMock(return_value={"disabled": False}) + + with patch('builtins.input', side_effect=['y', '', '']): + result = await submitter.dialog("https://example.com/claimed", None) + + assert result is True + assert len(db.sites) == 1 + + @pytest.mark.asyncio async def test_dialog_keeps_message_check_for_redirect(settings): db = MaigretDatabase()