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),

Reply via email to