os-backlog: reject flag-like positional board names (card #1818501985909868001)
board-ensure/activate/archive took NAME positionally with no validation, so `os-backlog board-ensure --board apprise-api` silently created a board literally named "--board". Add a shared validate_board_name! guard, checked right after each command shifts its name arg and before build_client, so a flag-like name fails loudly with no Planka API call. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MuWQ8oJbFU9uPKp7dAB1Pn
This commit is contained in:
parent
58f330fdf3
commit
8be597838d
|
|
@ -40,6 +40,18 @@ def fail_soft(message)
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Guards board-ensure/activate/archive's positional NAME argument against a
|
||||||
|
# mistaken flag-style invocation (e.g. `board-ensure --board apprise-api`,
|
||||||
|
# where `--board` is swallowed as the name and `apprise-api` is dropped
|
||||||
|
# silently). Called immediately after each command shifts its name arg,
|
||||||
|
# before build_client, so a bad name never reaches Planka.
|
||||||
|
def validate_board_name!(name, command)
|
||||||
|
return unless name.start_with?("--")
|
||||||
|
|
||||||
|
fail_soft("#{command}: board name #{name.inspect} must not start with \"--\" " \
|
||||||
|
"(looks like a flag was passed positionally — check your quoting/flags)")
|
||||||
|
end
|
||||||
|
|
||||||
def build_client
|
def build_client
|
||||||
require "planka_api"
|
require "planka_api"
|
||||||
client = Planka::Client.new
|
client = Planka::Client.new
|
||||||
|
|
@ -190,6 +202,7 @@ case command
|
||||||
when "board-ensure"
|
when "board-ensure"
|
||||||
name = rest.shift
|
name = rest.shift
|
||||||
fail_soft("board-ensure requires a board name") unless name
|
fail_soft("board-ensure requires a board name") unless name
|
||||||
|
validate_board_name!(name, "board-ensure")
|
||||||
project = parse_project_flag(rest)
|
project = parse_project_flag(rest)
|
||||||
client = build_client
|
client = build_client
|
||||||
result = Backlog::BoardEnsurer.new(client: client).ensure(name, project_name: project)
|
result = Backlog::BoardEnsurer.new(client: client).ensure(name, project_name: project)
|
||||||
|
|
@ -199,12 +212,14 @@ when "board-ensure"
|
||||||
when "activate"
|
when "activate"
|
||||||
name = rest.shift
|
name = rest.shift
|
||||||
fail_soft("activate requires a board name") unless name
|
fail_soft("activate requires a board name") unless name
|
||||||
|
validate_board_name!(name, "activate")
|
||||||
client = build_client
|
client = build_client
|
||||||
board = Backlog::BoardEnsurer.new(client: client).activate(name)
|
board = Backlog::BoardEnsurer.new(client: client).activate(name)
|
||||||
puts "activated: #{board.name}"
|
puts "activated: #{board.name}"
|
||||||
when "archive"
|
when "archive"
|
||||||
name = rest.shift
|
name = rest.shift
|
||||||
fail_soft("archive requires a board name") unless name
|
fail_soft("archive requires a board name") unless name
|
||||||
|
validate_board_name!(name, "archive")
|
||||||
fail_soft("archive requires explicit --yes (human go-ahead only)") unless rest.include?("--yes")
|
fail_soft("archive requires explicit --yes (human go-ahead only)") unless rest.include?("--yes")
|
||||||
|
|
||||||
client = build_client
|
client = build_client
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
require_relative "test_helper"
|
||||||
|
|
||||||
|
# Card #1818501985909868001: a mistaken flag-style invocation like
|
||||||
|
# `os-backlog board-ensure --board apprise-api` must fail loudly with a
|
||||||
|
# usage error instead of silently creating a Planka board literally named
|
||||||
|
# "--board". These are CLI-contract tests (shell out to bin/os-backlog)
|
||||||
|
# because the bug is about what the positional NAME arg parsing accepts
|
||||||
|
# BEFORE any Planka client/network call is made — no PLANKA_* env vars are
|
||||||
|
# set, so if validation didn't happen first, these would instead fail on
|
||||||
|
# network auth with a different message.
|
||||||
|
class BoardNameValidationCliTest < Minitest::Test
|
||||||
|
BIN = File.expand_path("../bin/os-backlog", __dir__)
|
||||||
|
|
||||||
|
# Strip any live Planka credentials from the child's env: these tests
|
||||||
|
# assert that flag-like names are rejected BEFORE build_client ever runs,
|
||||||
|
# so no test here may be allowed to reach the network even accidentally.
|
||||||
|
def run_cli(*args)
|
||||||
|
env = { "PLANKA_BASE_URL" => "", "PLANKA_USERNAME" => "", "PLANKA_PASSWORD" => "" }
|
||||||
|
out = IO.popen(env, [RbConfig.ruby, BIN, *args], err: [:child, :out], &:read)
|
||||||
|
[out, $?.exitstatus]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_board_ensure_rejects_flag_like_name
|
||||||
|
out, status = run_cli("board-ensure", "--board", "apprise-api")
|
||||||
|
|
||||||
|
refute_equal 0, status
|
||||||
|
assert_match(/board name.*must not start with/i, out)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_activate_rejects_flag_like_name
|
||||||
|
out, status = run_cli("activate", "--board")
|
||||||
|
|
||||||
|
refute_equal 0, status
|
||||||
|
assert_match(/board name.*must not start with/i, out)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_archive_rejects_flag_like_name
|
||||||
|
out, status = run_cli("archive", "--board", "--yes")
|
||||||
|
|
||||||
|
refute_equal 0, status
|
||||||
|
assert_match(/board name.*must not start with/i, out)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_board_ensure_accepts_normal_name_and_fails_later_on_network
|
||||||
|
out, status = run_cli("board-ensure", "apprise-api")
|
||||||
|
|
||||||
|
refute_equal 0, status
|
||||||
|
refute_match(/board name.*must not start with/i, out)
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in New Issue