This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb.git


The following commit(s) were added to refs/heads/main by this push:
     new 91cc9619c Handle empty ddocs in ddoc_features scanner plugin
91cc9619c is described below

commit 91cc9619c359ae5514ab2cae53f0555b544ddc59
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