This is an automated email from the ASF dual-hosted git repository. nickva pushed a commit to branch fix-couch_scanner_plugin_ddoc_features in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit 54aad633bd264a6b7254519ceea80b65a5553255 Author: Nick Vatamaniuc <[email protected]> AuthorDate: Fri Jul 24 12:19:35 2026 -0400 Handle empty ddocs in ddoc_features scanner plugin We had too strong of an assert that design docs should not be empty (and to be useful they shouldn't be) but if user did have some empty ddoc it would crash the scanner plugin, so fix it to anticipate that case. --- .../src/couch_scanner_plugin_ddoc_features.erl | 8 +++++--- src/couch_scanner/test/eunit/couch_scanner_test.erl | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/couch_scanner/src/couch_scanner_plugin_ddoc_features.erl b/src/couch_scanner/src/couch_scanner_plugin_ddoc_features.erl index adfeb0985..e3fb258d0 100644 --- a/src/couch_scanner/src/couch_scanner_plugin_ddoc_features.erl +++ b/src/couch_scanner/src/couch_scanner_plugin_ddoc_features.erl @@ -125,12 +125,14 @@ db(#st{} = St, DbName) -> ddoc(#st{} = St, _DbName, #doc{id = <<"_design/_", _/binary>>}) -> % These are auto-inserted ddocs _design/_auth, etc. {ok, St}; -ddoc(#st{} = St, DbName, #doc{} = DDoc) -> - #doc{body = {Props = [_ | _]}} = DDoc, +ddoc(#st{} = St, DbName, #doc{body = {Props = [_ | _]}} = DDoc) -> case couch_util:get_value(<<"language">>, Props, <<"javascript">>) of <<"javascript">> -> {ok, check_ddoc(St, DbName, DDoc)}; _ -> {ok, St} - end. + end; +ddoc(#st{} = St, _DbName, #doc{}) -> + % Skip ddocs with empty or malformed bodies + {ok, St}. % Private diff --git a/src/couch_scanner/test/eunit/couch_scanner_test.erl b/src/couch_scanner/test/eunit/couch_scanner_test.erl index 9cce06f38..d16183a1b 100644 --- a/src/couch_scanner/test/eunit/couch_scanner_test.erl +++ b/src/couch_scanner/test/eunit/couch_scanner_test.erl @@ -29,6 +29,7 @@ couch_scanner_test_() -> ?TDEF_FE(t_run_through_all_callbacks_basic, 10), ?TDEF_FE(t_find_reporting_works, 10), ?TDEF_FE(t_ddoc_features_works, 20), + ?TDEF_FE(t_ddoc_features_empty_ddoc_body, 20), ?TDEF_FE(t_conflict_finder_works, 30), ?TDEF_FE(t_config_skips, 10), ?TDEF_FE(t_resume_after_error, 10), @@ -250,6 +251,20 @@ t_ddoc_features_works({_, {_, DbName2, _}}) -> resume_couch_scanner(Plugin), ?assertEqual(2, meck:num_calls(couch_scanner_util, log, LogArgs)). +t_ddoc_features_empty_ddoc_body({_, {_, DbName2, _}}) -> + % Handle empty/malformed ddocs by skipping them + ok = add_doc(DbName2, <<"_design/empty">>, #{}), + Plugin = atom_to_list(?FEATURES_PLUGIN), + meck:reset(couch_scanner_server), + meck:reset(couch_scanner_util), + config:set("couch_scanner_plugins", Plugin, "true", false), + wait_exit(10000), + ExitMsg = meck:capture(last, couch_scanner_server, handle_info, [{'EXIT', '_', '_'}, '_'], 1), + ?assertMatch({'EXIT', _, normal}, ExitMsg), + % Scan completes and still reports features from the other db + LogArgs = [warning, ?FEATURES_PLUGIN, '_', '_', '_'], + ?assertEqual(1, meck:num_calls(couch_scanner_util, log, LogArgs)). + t_conflict_finder_works({_, {_, _, DbName3}}) -> % Run the "conflict_finder" plugin Plugin = atom_to_list(?CONFLICTS_PLUGIN),
