summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-06-17 22:20:43 +0300
committerPaul Buetow <paul@buetow.org>2026-06-17 22:20:43 +0300
commit2dc8a01b83734a0b01eb51d20c880883625e3c87 (patch)
tree6d00c42b62501d9fa158c4f994f27a29077a77b8 /tests
parent5fb1f17e3b16c8ef4b71648419251d76ae1227b2 (diff)
gn0: dispatch CLI actions via registration table
Adding a CLI action previously required editing two case statements (run_action and run_configured_action). Introduce ACTION_SPECS, a single '|'-delimited registry (flag|handler|requires_config|validation_fn| validation_arg) matching the CLI_OPTION_SPEC / STATS_CATEGORIES encoding, and replace both dispatchers with table lookups via action_spec_field. - run_action: looks up requires_config; routes non-config actions (--version/--init) through run_unconfigured_action (shared config/override/force precheck) and the rest through run_configured_action. - run_configured_action: keeps the force-generate guard and config load/log, then runs the entry's validation_fn (with optional arg, used by --dry-run) and handler via run_configured_action_body. - Extracted the --clean inline rm body into a clean_dist handler and added an action_print_version handler so every action is just a registry entry plus named functions. - Unknown/empty actions have no entry, so dispatch falls through to the same usage + exit 1 behavior as the old case "*)" arm. Adding an action is now one ACTION_SPECS entry plus its handler/ validation functions; neither dispatcher changes (Open/Closed). Added test_action_dispatch_is_registry_driven proving every parser action flag has a registry entry, all handlers/validators resolve, and unknown actions are rejected. All existing action/dispatcher tests pass unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'tests')
-rwxr-xr-xtests/cli.sh77
1 files changed, 77 insertions, 0 deletions
diff --git a/tests/cli.sh b/tests/cli.sh
index 479a998..9c3f7d7 100755
--- a/tests/cli.sh
+++ b/tests/cli.sh
@@ -4170,6 +4170,80 @@ BASH
fi
}
+# Open/Closed proof (task gn0): CLI action dispatch is driven by a single
+# registration table, ACTION_SPECS, instead of two hardcoded case statements.
+# We assert (1) every action flag the parser accepts (CLI_OPTION_SPEC entries
+# with kind=action) has exactly one ACTION_SPECS entry; (2) every registry entry
+# resolves its handler and validation_fn (when set) to real functions; and
+# (3) an unknown/empty action has no entry, so run_action falls through to the
+# usage/exit-1 path (action_spec_field returns non-zero for it).
+test_action_dispatch_is_registry_driven() {
+ local output
+
+ output=$(
+ bash -euo pipefail -s "$TEST_REPO_ROOT" "$TEST_SHURIKEN" <<'BASH'
+repo_root="$1"; shift
+shuriken="$1"; shift
+
+# Source every inlined lib function from the generated binary (dropping the
+# trailing dispatcher line) so ACTION_SPECS, action_spec_field, and every
+# registered handler/validation function are defined -- the same trick the
+# render-redirect tests use.
+# shellcheck source=/dev/null
+source <(sed '$d' "$shuriken")
+
+# Re-declare CLI_OPTION_SPEC's action flags exactly as the source lists them by
+# parsing src/shuriken.sh, so the test tracks the real parser, not a copy.
+declare -a action_flags=()
+while IFS= read -r flag; do
+ action_flags+=("$flag")
+done < <(grep -oE "\[--[a-z-]+\]='kind=action'" "$repo_root/src/shuriken.sh" \
+ | sed -E "s/^\[(--[a-z-]+)\].*/\1/")
+
+# (1) Every parser-accepted action flag has a registry entry.
+missing_entry=0
+for flag in "${action_flags[@]}"; do
+ if ! action_spec_field "$flag" 0 > /dev/null; then
+ missing_entry=1
+ fi
+done
+printf 'all_flags_registered=%s\n' \
+ "$([ "$missing_entry" -eq 0 ] && echo yes || echo no)"
+
+# (2) Every registry handler + (non-empty) validation_fn resolves to a function.
+bad_handler=0
+for spec in "${ACTION_SPECS[@]}"; do
+ IFS='|' read -r _ handler _ validation_fn _ <<< "$spec"
+ if ! declare -F "$handler" > /dev/null; then
+ bad_handler=1
+ fi
+ if [ -n "$validation_fn" ] && ! declare -F "$validation_fn" > /dev/null; then
+ bad_handler=1
+ fi
+done
+printf 'all_handlers_resolve=%s\n' \
+ "$([ "$bad_handler" -eq 0 ] && echo yes || echo no)"
+
+# (3) An unknown action and the empty action have no entry -> non-zero lookup,
+# which is exactly what makes run_action take the usage/exit-1 path.
+if action_spec_field "--bogus-action" 0 > /dev/null \
+ || action_spec_field "" 0 > /dev/null; then
+ printf 'unknown_rejected=no\n'
+else
+ printf 'unknown_rejected=yes\n'
+fi
+BASH
+ )
+
+ if [ "$output" != \
+ $'all_flags_registered=yes\nall_handlers_resolve=yes\nunknown_rejected=yes' ]
+ then
+ printf 'FAIL: action dispatch not registry-driven\n' >&2
+ printf 'actual:\n%s\n' "$output" >&2
+ exit 1
+ fi
+}
+
# The preview_num next/prev handlers compute neighbour page numbers with $(( )).
# A non-numeric or empty preview_num context value must NOT crash the script with
# a bash arithmetic syntax error (which, under set -e, would abort the whole run):
@@ -6272,6 +6346,9 @@ main() {
'template render var dispatch is extensible (OCP)' \
test_template_render_var_dispatch_is_extensible
test::run_case \
+ 'action dispatch is registry-driven (OCP)' \
+ test_action_dispatch_is_registry_driven
+ test::run_case \
'preview_num render handlers guard non-numeric input' \
test_template_render_var_preview_num_guards_non_numeric
test::run_case \