Skip to content

test(bitbucket): add host.go state-mapping tests#312

Merged
kunchenguid merged 1 commit into
kunchenguid:mainfrom
e-jung:test/bitbucket-host-tests
Jul 1, 2026
Merged

test(bitbucket): add host.go state-mapping tests#312
kunchenguid merged 1 commit into
kunchenguid:mainfrom
e-jung:test/bitbucket-host-tests

Conversation

@e-jung

@e-jung e-jung commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Intent

The developer's goal was to add test coverage to internal/bitbucket/host.go (~300 lines) which had zero tests, targeting six pure state-mapping helpers (normalizePRState, statusName, statusBucket, normalizePipelineUUID, pipelineUUIDFromStatusURL, failedPipelineUUIDs) that drive Bitbucket CI status interpretation and gate resolution. Requirements were to write table-driven tests with t.Run subtests focused on edge cases (malformed inputs, empty strings, unexpected values), follow the existing test conventions in internal/bitbucket/, and not modify host.go. Explicit constraints included branching off origin/main rather than local main, pushing to the e-jung/no-mistakes fork, opening a cross-repo PR to kunchenguid/no-mistakes with the specific title "test(bitbucket): add host.go state-mapping tests (300 lines, 0 covered)" and AI disclosure set to Human-reviewed, running go test ./internal/bitbucket/... -v, and not merging the PR. The developer also required staying within the worktree, reporting status via a status file, and leaving the merge decision to the maintainer.

What Changed

  • Added internal/bitbucket/host_test.go (390 lines) with table-driven tests covering the six pure state-mapping helpers in host.go: normalizePRState, statusName, statusBucket, normalizePipelineUUID, pipelineUUIDFromStatusURL, and failedPipelineUUIDs.
  • Tests target edge cases (malformed inputs, empty strings, unexpected values) across 89 subtests using t.Run, raising these helpers from 0% to covered.
  • No changes to host.go or production behavior; test-only addition following existing internal/bitbucket test conventions.

Risk Assessment

✅ Low: Test-only addition (no production code changed); all assertions trace correctly against the six pure helpers, conventions match existing tests, and go vet is clean.

Testing

Baseline go test -race ./... was already green. The added change is purely the new internal/bitbucket/host_test.go (390 lines), confirmed as the only diff vs base and with host.go untouched. go test ./internal/bitbucket/... -v passes all six new functions (TestNormalizePRState, TestStatusName, TestStatusBucket, TestNormalizePipelineUUID, TestPipelineUUIDFromStatusURL, TestFailedPipelineUUIDs) and every one of their 89 edge-case subtests (malformed/invalid-percent-escape URLs, empty/whitespace inputs, strings.Trim brace-cutset behavior, fragment-before-path URL parsing, nil/empty/dedup map results), and the race detector is clean. Coverage shows each of the six targeted helpers at exactly 100% (LatestStatuses transitively at 85.7%, not the target). Beyond pass/fail, three source mutations (forcing statusBucket's fail bucket to pass, dropping normalizePipelineUUID's ToLower, and reversing pipelineUUIDFromStatusURL's fragment/path order) were each caught by their corresponding test, proving the coverage is meaningful rather than vacuous; all mutations were reverted and host.go was restored to its committed state before finishing. No issues found; the user intent (add zero-previously-covered test coverage to host.go's six helpers, without modifying host.go) is satisfied.

Evidence: Verbose bitbucket test transcript (6 new fns, 89 subtests)

All 6 new test functions PASS with all subtests: TestNormalizePRState (18), TestStatusName (9), TestStatusBucket (18), TestNormalizePipelineUUID (16), TestPipelineUUIDFromStatusURL (13), TestFailedPipelineUUIDs (15). Final: PASS / ok github.com/kunchenguid/no-mistakes/internal/bitbucket

=== RUN   TestNewClientFromEnvReadsAmbientEnvironment
--- PASS: TestNewClientFromEnvReadsAmbientEnvironment (0.00s)
=== RUN   TestNewClientFromEnvPrefersExplicitEnvironment
--- PASS: TestNewClientFromEnvPrefersExplicitEnvironment (0.00s)
=== RUN   TestParseRepoRefRejectsLookalikeHosts
=== RUN   TestParseRepoRefRejectsLookalikeHosts/rejects_lookalike_host
=== RUN   TestParseRepoRefRejectsLookalikeHosts/accepts_exact_host
=== RUN   TestParseRepoRefRejectsLookalikeHosts/accepts_subdomain_host
--- PASS: TestParseRepoRefRejectsLookalikeHosts (0.00s)
=== RUN   TestListPRStatusesFollowsPagination
--- PASS: TestListPRStatusesFollowsPagination (0.00s)
=== RUN   TestListPRStatusesRejectsCrossOriginPagination
--- PASS: TestListPRStatusesRejectsCrossOriginPagination (0.00s)
=== RUN   TestFindOpenPRBySourceAndDestinationBranchFiltersSourceRepo
--- PASS: TestFindOpenPRBySourceAndDestinationBranchFiltersSourceRepo (0.00s)
=== RUN   TestListPipelinesByCommitFollowsPagination
--- PASS: TestListPipelinesByCommitFollowsPagination (0.00s)
=== RUN   TestListPipelineStepsFollowsPagination
--- PASS: TestListPipelineStepsFollowsPagination (0.00s)
=== RUN   TestGetStepLogCapsResponseToTail
--- PASS: TestGetStepLogCapsResponseToTail (0.00s)
=== RUN   TestNormalizePRState
=== RUN   TestNormalizePRState/open_canonical
=== RUN   TestNormalizePRState/open_lowercase
=== RUN   TestNormalizePRState/open_mixed_case
=== RUN   TestNormalizePRState/open_with_surrounding_whitespace
=== RUN   TestNormalizePRState/merged_canonical
=== RUN   TestNormalizePRState/merged_lowercase
=== RUN   TestNormalizePRState/merged_with_whitespace
=== RUN   TestNormalizePRState/declined_canonical
=== RUN   TestNormalizePRState/declined_lowercase
=== RUN   TestNormalizePRState/closed_canonical
=== RUN   TestNormalizePRState/closed_lowercase
=== RUN   TestNormalizePRState/superseded_canonical
=== RUN   TestNormalizePRState/superseded_lowercase
=== RUN   TestNormalizePRState/unknown_state_passes_through_raw
=== RUN   TestNormalizePRState/unknown_state_preserves_original_casing
=== RUN   TestNormalizePRState/unknown_state_preserves_original_whitespace
=== RUN   TestNormalizePRState/empty_string_stays_empty
=== RUN   TestNormalizePRState/whitespace-only_with_no_recognized_token_returns_raw
--- PASS: TestNormalizePRState (0.00s)
=== RUN   TestStatusName
=== RUN   TestStatusName/name_present
=== RUN   TestStatusName/name_takes_precedence_over_key
=== RUN   TestStatusName/name_empty_falls_back_to_key
=== RUN   TestStatusName/name_whitespace-only_falls_back_to_key
=== RUN   TestStatusName/name_trimmed_when_returned
=== RUN   TestStatusName/key_trimmed_when_used_as_fallback
=== RUN   TestStatusName/both_empty_returns_empty
=== RUN   TestStatusName/both_whitespace-only_returns_empty
=== RUN   TestStatusName/neither_name_nor_key_set_but_state_populated_returns_empty
--- PASS: TestStatusName (0.00s)
=== RUN   TestStatusBucket
=== RUN   TestStatusBucket/successful_canonical
=== RUN   TestStatusBucket/success_alias
=== RUN   TestStatusBucket/successful_lowercase
=== RUN   TestStatusBucket/successful_mixed_case
=== RUN   TestStatusBucket/successful_with_whitespace
=== RUN   TestStatusBucket/failed_canonical
=== RUN   TestStatusBucket/failure_alias
=== RUN   TestStatusBucket/error_alias
=== RUN   TestStatusBucket/failed_lowercase
=== RUN   TestStatusBucket/stopped_maps_to_cancel
=== RUN   TestStatusBucket/stopped_lowercase
=== RUN   TestStatusBucket/inprogress_no_underscore
=== RUN   TestStatusBucket/in_progress_with_underscore
=== RUN   TestStatusBucket/pending
=== RUN   TestStatusBucket/inprogress_lowercase
=== RUN   TestStatusBucket/unknown_returns_empty
=== RUN   TestStatusBucket/empty_returns_empty
=== RUN   TestStatusBucket/whitespace-only_returns_empty
--- PASS: TestStatusBucket (0.00s)
=== RUN   TestNormalizePipelineUUID
=== RUN   TestNormalizePipelineUUID/already_lowercase
=== RUN   TestNormalizePipelineUUID/uppercase_lowered
=== RUN   TestNormalizePipelineUUID/mixed_case_lowered
=== RUN   TestNormalizePipelineUUID/with_braces_stripped
=== RUN   TestNormalizePipelineUUID/with_braces_uppercase
=== RUN   TestNormalizePipelineUUID/with_surrounding_spaces_trimmed
=== RUN   TestNormalizePipelineUUID/spaces_and_braces_combined
=== RUN   TestNormalizePipelineUUID/nested_braces_collapse
=== RUN   TestNormalizePipelineUUID/only_leading_brace_stripped
=== RUN   TestNormalizePipelineUUID/only_trailing_brace_stripped
=== RUN   TestNormalizePipelineUUID/triple_trailing_braces
=== RUN   TestNormalizePipelineUUID/interior_braces_preserved
=== RUN   TestNormalizePipelineUUID/empty_returns_empty
=== RUN   TestNormalizePipelineUUID/whitespace-only_returns_empty
=== RUN   TestNormalizePipelineUUID/empty_braces_return_empty
=== RUN   TestNormalizePipelineUUID/braces_with_whitespace_return_empty
--- PASS: TestNormalizePipelineUUID (0.00s)
=== RUN   TestPipelineUUIDFromStatusURL
=== RUN   TestPipelineUUIDFromStatusURL/valid_results_URL_with_braces
=== RUN   TestPipelineUUIDFromStatusURL/UUID_without_braces
=== RUN   TestPipelineUUIDFromStatusURL/uppercase_UUID_normalized_to_lowercase
=== RUN   TestPipelineUUIDFromStatusURL/URL_with_query_string_strips_query
=== RUN   TestPipelineUUIDFromStatusURL/URL_with_trailing_path_segment_stops_at_first_slash
=== RUN   TestPipelineUUIDFromStatusURL/fragment_consulted_before_path
=== RUN   TestPipelineUUIDFromStatusURL/last_results_segment_wins_when_duplicated
=== RUN   TestPipelineUUIDFromStatusURL/URL_without_results_segment_returns_empty
=== RUN   TestPipelineUUIDFromStatusURL/URL_whose_path_lacks_results_but_fragment_has_it_extracts_fragment_UUID
=== RUN   TestPipelineUUIDFromStatusURL/empty_string_returns_empty
=== RUN   TestPipelineUUIDFromStatusURL/whitespace-only_returns_empty
=== RUN   TestPipelineUUIDFromStatusURL/plain_string_with_no_URL_structure_returns_empty
=== RUN   TestPipelineUUIDFromStatusURL/malformed_URL_with_invalid_percent_escape_returns_empty
--- PASS: TestPipelineUUIDFromStatusURL (0.00s)
=== RUN   TestFailedPipelineUUIDs
=== RUN   TestFailedPipelineUUIDs/no_failing_names_returns_nil
=== RUN   TestFailedPipelineUUIDs/empty_failing_names_slice_returns_nil
=== RUN   TestFailedPipelineUUIDs/failing_names_all_whitespace_returns_nil
=== RUN   TestFailedPipelineUUIDs/no_statuses_returns_nil
=== RUN   TestFailedPipelineUUIDs/no_status_name_matches_a_failing_name_returns_nil
=== RUN   TestFailedPipelineUUIDs/matching_status_with_no_results_URL_returns_nil
=== RUN   TestFailedPipelineUUIDs/matching_status_with_empty_URL_returns_nil
=== RUN   TestFailedPipelineUUIDs/single_match_extracts_UUID
=== RUN   TestFailedPipelineUUIDs/multiple_distinct_failing_names_map_to_distinct_UUIDs
=== RUN   TestFailedPipelineUUIDs/duplicate_UUIDs_collapse_into_a_single_target
=== RUN   TestFailedPipelineUUIDs/only_the_requested_failing_names_contribute_UUIDs
=== RUN   TestFailedPipelineUUIDs/failing_name_with_surrounding_whitespace_matches_trimmed_status_name
=== RUN   TestFailedPipelineUUIDs/status_matched_by_key_when_name_is_empty
=== RUN   TestFailedPipelineUUIDs/uppercase_UUIDs_in_URLs_are_normalized
=== RUN   TestFailedPipelineUUIDs/LatestStatuses_dedup_happens_before_UUID_collection
--- PASS: TestFailedPipelineUUIDs (0.00s)
PASS
ok  	github.com/kunchenguid/no-mistakes/internal/bitbucket	(cached)
Evidence: host.go per-function coverage (6 targeted helpers = 100%)
normalizePRState 100.0% | statusName 100.0% | statusBucket 100.0% | normalizePipelineUUID 100.0% | pipelineUUIDFromStatusURL 100.0% | failedPipelineUUIDs 100.0% | LatestStatuses 85.7% (transitive). Package total: 59.9% of statements.
Evidence: Mutation-test evidence (tests catch regressions)
M1 statusBucket->pass => FAIL TestStatusBucket/failed_canonical (want fail, got pass). M2 drop ToLower => FAIL TestNormalizePipelineUUID/uppercase_lowered (+2). M3 swap frag/path order => FAIL TestPipelineUUIDFromStatusURL/fragment_consulted_before_path. All reverted; host.go restored to committed state.

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • go test -race ./...
  • go test ./internal/bitbucket/... -v (all 6 new test fns + existing bitbucket tests pass, 89 new subtests)
  • go test -race ./internal/bitbucket/... (clean)
  • go test ./internal/bitbucket/ -coverprofile (verified normalizePRState/statusName/statusBucket/normalizePipelineUUID/pipelineUUIDFromStatusURL/failedPipelineUUIDs each at 100%)
  • Mutation test: statusBucket FAILED-case -> pass (TestStatusBucket caught it), then reverted
  • Mutation test: normalizePipelineUUID drop ToLower (TestNormalizePipelineUUID caught it), then reverted
  • Mutation test: pipelineUUIDFromStatusURL swap fragment/path order (TestPipelineUUIDFromStatusURL caught it), then reverted
  • git diff 1432f3b..07a5c6f --stat (confirms only host_test.go added; host.go unchanged)
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@kunchenguid

Copy link
Copy Markdown
Owner

Thanks for the contribution, @e-jung! One process note on this repo: PRs need to be raised through no-mistakes (git push no-mistakes), which runs the review/test/lint/CI pipeline and stamps the required signature into the PR body. This PR is currently failing the "PR must be raised via no-mistakes" check because it wasn't raised that way.

If working from a fork was the blocker, that's fixed as of v1.30.0 (#306). Per CONTRIBUTING.md: point origin at this parent repo, run no-mistakes init --fork-url <your-fork-url>, then git push no-mistakes.

I won't be merging PRs that bypass the gate going forward, but I'd genuinely love to land your work once it's re-raised. Thanks for understanding! 🙏

@e-jung e-jung force-pushed the test/bitbucket-host-tests branch from f073373 to 07a5c6f Compare June 30, 2026 17:48
@e-jung e-jung changed the title test(bitbucket): add host.go state-mapping tests (300 lines, 0 → covered) test(bitbucket): add host.go state-mapping tests Jun 30, 2026
@kunchenguid kunchenguid merged commit bd1e1a6 into kunchenguid:main Jul 1, 2026
7 of 8 checks passed
@kunchenguid

Copy link
Copy Markdown
Owner

Thanks @e-jung - merged! Really appreciate the contribution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants