fix(create): validate explicit IDs against allowed_prefixes using starts-with matching (#1137)

When creating issues with explicit IDs like `bd create --id hq-cv-test`,
the prefix validation was failing even when `hq-cv` was in `allowed_prefixes`.

Root cause: `ExtractIssuePrefix("hq-cv-test")` returns `"hq"` (not `"hq-cv"`)
because "test" looks like an English word, causing the algorithm to fall back
to the first hyphen. The validation then checked if `"hq"` was in the allowed
list containing `"hq-cv"` - which failed.

The fix adds `ValidateIDPrefixAllowed()` which validates the full ID using
"starts with" matching (the same approach the importer uses successfully).
This correctly handles multi-hyphen prefixes like `hq-cv-` regardless of
what the suffix looks like.

Fixes #1135

Co-authored-by: Steven Syrek <steven.syrek@deepl.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steven Syrek
2026-01-19 19:08:56 +01:00
committed by GitHub
parent 8e40018701
commit 73d4d5ecb2
3 changed files with 100 additions and 9 deletions

View File

@@ -334,6 +334,56 @@ func TestValidatePrefixWithAllowed(t *testing.T) {
}
}
// TestValidateIDPrefixAllowed tests the new function that validates IDs using
// "starts with" matching to handle multi-hyphen prefixes correctly (GH#1135).
func TestValidateIDPrefixAllowed(t *testing.T) {
tests := []struct {
name string
id string
dbPrefix string
allowedPrefixes string
force bool
wantError bool
}{
// Basic cases
{"matching prefix", "bd-abc123", "bd", "", false, false},
{"empty db prefix", "bd-abc123", "", "", false, false},
{"mismatched with force", "foo-abc123", "bd", "", true, false},
{"mismatched without force", "foo-abc123", "bd", "", false, true},
// Multi-hyphen prefix cases (GH#1135 - the main bug)
{"hq-cv prefix with word suffix", "hq-cv-test", "djdefi-ops", "hq,hq-cv", false, false},
{"hq-cv prefix with hash suffix", "hq-cv-abc123", "djdefi-ops", "hq,hq-cv", false, false},
{"djdefi-ops with word suffix", "djdefi-ops-test", "djdefi-ops", "", false, false},
// Allowed prefixes list
{"allowed prefix gt", "gt-abc123", "hq", "gt,hmc", false, false},
{"allowed prefix hmc", "hmc-abc123", "hq", "gt,hmc", false, false},
{"primary prefix still works", "hq-abc123", "hq", "gt,hmc", false, false},
{"prefix not in allowed list", "foo-abc123", "hq", "gt,hmc", false, true},
// Edge cases
{"allowed with spaces", "gt-abc123", "hq", "gt, hmc, foo", false, false},
{"allowed with trailing dash", "gt-abc123", "hq", "gt-, hmc-", false, false},
{"empty allowed list", "gt-abc123", "hq", "", false, true},
{"single allowed prefix", "gt-abc123", "hq", "gt", false, false},
// Multi-hyphen allowed prefixes
{"multi-hyphen in allowed list", "my-cool-prefix-abc123", "hq", "my-cool-prefix,other", false, false},
{"partial match should fail", "hq-cv-extra-test", "hq", "hq-cv-extra", false, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateIDPrefixAllowed(tt.id, tt.dbPrefix, tt.allowedPrefixes, tt.force)
if (err != nil) != tt.wantError {
t.Errorf("ValidateIDPrefixAllowed(%q, %q, %q) error = %v, wantError %v",
tt.id, tt.dbPrefix, tt.allowedPrefixes, err, tt.wantError)
}
})
}
}
func TestValidateAgentID(t *testing.T) {
tests := []struct {
name string