Skip to content

Cover every pm-slack code path with behavioural tests and raise the coverage gate to 100 - #112

Merged
unbraind merged 5 commits into
mainfrom
test/cover-pm-slack-to-full-coverage
Sep 18, 2026
Merged

unbraind merged 5 commits into
mainfrom
test/cover-pm-slack-to-full-coverage

Conversation

@unbraind

@unbraind unbraind commented Sep 18, 2026 •

Copy link
Copy Markdown
Owner

Summary

Brings pm-slack to a real 100% line / branch / function coverage and raises the coverage gate from 77/85/79 to 100/100/100.

Before / After

Metric Before After
Lines 82.63% 100.00%
Branches 86.28% 100.00%
Functions 86.21% 100.00%

What changed

  • 127 new behavioural tests in test/coverage.test.ts driving every uncovered line, branch, and function through real behaviour: command handlers via the pm SDK test harness, lifecycle hooks via runHook, preflight override via direct run calls, and HTTP paths via local node:http servers on 127.0.0.1.
  • Dead code removed (5 sites, each with a documented invariant):
    • beforeCommand fallback hook — the SDK's ExtensionApiRegistrar always provides api.hooks.afterCommand, so the else if branch is unreachable.
    • postToSlack throw ternary — postToSlackOnce only rejects with Error instances, so lastErr instanceof Error is always true.
    • res.statusCode ?? 0 — HTTP responses always carry a status code.
    • statusIsClosed ?? "" — aggregateDigest always passes a lowercased string.
    • ALL_EVENTS.find(...) ?? "create" (2 sites) — parseEvents always returns a non-empty set of valid EventKind values.
  • toErrorMessage() helper consolidates 4 identical err instanceof Error ? err.message : String(err) ternaries from catch blocks into one function, exported via __test__ with both arms tested.
  • Coverage gate raised from 77/85/79 to 100/100/100 in package.json.

Resolves

pm-slack-hn3n

Summary by Sourcery

Achieve complete behavioral coverage for pm-slack and enforce 100% line, branch, and function coverage.

Enhancements:

  • Remove unreachable fallback paths and centralize caught-error message formatting while preserving runtime behavior.

Build:

  • Raise the package coverage thresholds for lines, branches, and functions to 100%.

Tests:

  • Add comprehensive behavioral coverage for pm-slack command handlers, lifecycle hooks, preflight behavior, formatting and parsing, digest processing, and HTTP/HTTPS failure and retry paths.

Chores:

  • Record the completed coverage work in the changelog and project task history.

Summary by cubic

Raises pm-slack to 100% line, branch, and function coverage and closes pm-slack-hn3n. Runtime behavior is unchanged; the coverage gate in package.json moves from 77/85/79 to 100/100/100.

Tests

  • Adds 128 behavioural tests covering command handlers, lifecycle hooks, preflight, formatting, parsing, and HTTP and HTTPS paths.
  • Uses the @unbrained/pm-cli/sdk test harness and local node:http servers instead of mocks; the HTTPS default-port case fails deterministically at name resolution on an unresolvable .invalid host.

Refactors

  • Removes unreachable branches, including the beforeCommand fallback hook and statusCode ?? 0.
  • Consolidates four error-message ternaries into toErrorMessage(), exported via __test__.
  • Replaces the ALL_EVENTS.find(...) ?? "create" fallback with a primaryEvent() helper so the create-when-empty branch stays real and covered.

Written for commit 645d780. Summary will update on new commits.

Review in cubic

…to 100

- Add test/coverage.test.ts with 127 behavioural tests driving every line,
  branch, and function in index.ts to 100% coverage
- Remove dead beforeCommand fallback hook (SDK always provides afterCommand)
- Remove dead postToSlack throw ternary (postToSlackOnce only rejects Error)
- Remove dead statusCode ?? 0 (HTTP responses always have status codes)
- Simplify statusIsClosed parameter to string (callers always pass strings)
- Remove dead ALL_EVENTS.find ?? 'create' (parseEvents always non-empty)
- Introduce toErrorMessage() helper consolidating 4 catch-block ternaries
- Raise coverageGate thresholds from 77/85/79 to 100/100/100

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @unbraind, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 4 days and 7 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@coderabbitai

coderabbitai Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 78b488c0-f35d-4d9d-996f-9fb110c3cce0


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Sep 18, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR adds a large behavioral test suite that drives pm-slack through real SDK command, hook, preflight, filesystem, and local HTTP paths, removes unreachable fallback code, centralizes error formatting, updates generated distribution files, and raises coverage enforcement to 100% for lines, branches, and functions.

Sequence diagram for pm-slack Slack notification behavior

sequenceDiagram
    participant Test as BehavioralTest
    participant SDK as PMSDKHarness
    participant Handler as CommandHandler
    participant Slack as LocalHTTPServer

    Test->>SDK: run command
    SDK->>Handler: invoke handler
    Handler->>Slack: POST webhook payload
    Slack-->>Handler: HTTP response
    Handler-->>SDK: command result
    SDK-->>Test: assert behavior
Loading

Flow diagram for pm-slack behavioral coverage paths

flowchart LR
    Tests[Behavioral test suite] --> SDK[PM SDK test harness]
    Tests --> Hooks[runHook lifecycle tests]
    Tests --> Preflight[Direct preflight run calls]
    Tests --> HTTP[Local HTTP server tests]
    SDK --> Handlers[Command handlers]
    Hooks --> AfterCommand[afterCommand hook]
    Preflight --> Gate[Webhook validation gate]
    HTTP --> Slack[Slack webhook paths]
    Handlers --> Coverage[100 percent coverage gate]
    AfterCommand --> Coverage
    Gate --> Coverage
    Slack --> Coverage
Loading

File-Level Changes

Change Details Files
Added comprehensive behavioral coverage for pm-slack commands, hooks, preflight behavior, helper logic, digest processing, and HTTP failure/retry paths.
  • Added 127 tests covering command handlers through the SDK harness and lifecycle/preflight behavior through registered runners and direct calls.
  • Exercised HTTP success, status failures, retry-after handling, connection errors, socket hangups, and timeout behavior with local node:http servers.
  • Covered configuration, parsing, filtering, event detection, payload formatting, digest aggregation, routing, output modes, and defensive fallbacks.
  • Added temporary pm-root fixtures to test digest store traversal, valid items, and unreadable files.
test/coverage.test.ts
Removed defensive branches documented as unreachable under established SDK and internal invariants.
  • Removed the beforeCommand fallback because the SDK always supplies afterCommand.
  • Simplified error propagation, HTTP status access, closed-status handling, and event selection by eliminating unreachable fallback branches.
  • Updated status typing and removed the unused BeforeCommandHookContext import.
index.ts
dist/index.js
dist/index.d.ts
dist/index.js.map
dist/index.d.ts.map
Centralized caught-error message extraction while preserving explicit defensive behavior for non-Error values.
  • Added toErrorMessage() and replaced four duplicated catch-block ternaries.
  • Exported the helper through test and added tests for Error and non-Error inputs.
  • Updated generated distribution artifacts.
index.ts
dist/index.js
dist/index.d.ts
dist/index.js.map
dist/index.d.ts.map
Raised the project coverage thresholds to require complete line, branch, and function coverage.
  • Changed the coverage gate from 77/85/79 to 100/100/100.
package.json
Added task and history metadata for the completed pm-slack coverage work.
  • Recorded the task definition and execution history artifacts.
.agents/pm/tasks/pm-slack-hn3n.toon
.agents/pm/history/pm-slack-hn3n.jsonl

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@greptile-apps

greptile-apps Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

The PR appears safe to merge; the recent commits address the prior test-determinism findings and introduce no new actionable failures.

Summary

This PR expands behavioral coverage across the extension and raises the enforced line, branch, and function thresholds to 100%.

  • Adds behavioral tests for command handlers, lifecycle hooks, preflight behavior, digest processing, formatting, parsing, and local HTTP/HTTPS failure paths.
  • Consolidates caught-value formatting in toErrorMessage.
  • Introduces primaryEvent to preserve event-priority selection and an explicit empty-set fallback without unsafe casts.
  • Removes branches that are unreachable under the supported host and runtime contracts.
  • Updates generated distribution artifacts, project metadata, and the changelog.

Reviews (4) · Last reviewed commit: "Fail the default-port HTTPS test at name..."

Comment thread test/coverage.test.ts Outdated
Comment thread test/coverage.test.ts Outdated
Comment thread test/coverage.test.ts
Comment thread test/coverage.test.ts
…ttps test

- Remove unnecessary 100ms sleeps (runHook awaits the hook which awaits postToSlack)
- Use dynamically allocated ports instead of hardcoded port 1
- Use https://hooks.slack.com/services/A/B/C for config-only tests (no network call)
- Add https://localhost test to cover https.request and port 443 default branches
- Fix remaining withEnv async callback to use withEnvAsync
Comment thread test/coverage.test.ts Outdated
The coverage raise replaced 'ALL_EVENTS.find(...) ?? "create"' with 'as EventKind' at both command sites. The fallback was unreachable through parseEvents, which never returns an empty set, but the cast moved that invariant out of the compiler's sight. The next change to parseEvents could then have produced an undefined header verb silently.

primaryEvent(events) restores the original behaviour (first selected event in ALL_EVENTS priority order, create when nothing is selected) as a small pure function. Its empty-set contract is unit-tested directly, so the branch is real and covered, not asserted away. The remaining two narrowings (a ClientRequest response always has statusCode; postToSlack's lastErr is always an Error) now state the runtime invariant they rely on.
Comment thread dist/index.js
…holds port 443

The test drove https://localhost/hook to cover the default-port and https.request branches, and relied on nothing listening on local port 443. A proxy or dev server there would change the outcome or hit the 10-second timeout. The host now uses the RFC 6761 .invalid TLD, which can never resolve, so the transport error is deterministic on every machine.
@unbraind

Copy link
Copy Markdown
Owner Author

Orchestrator review of this PR, applied in e438506 and 645d780:

  • The coverage raise had replaced ALL_EVENTS.find(...) ?? "create" with as EventKind at both command sites. The fallback was unreachable through parseEvents, but the cast hid that invariant from the compiler. primaryEvent(events) restores the original behaviour as a small pure function whose empty-set contract is unit-tested directly.
  • The two remaining narrowings (a ClientRequest response always has statusCode; lastErr is always an Error) now state the runtime invariant they rely on.
  • Removing the beforeCommand fallback hook is intentional: manifest.json pm_min_version 2026.7.29 guarantees api.hooks.afterCommand, so the fallback could never activate on a supported host.
  • Greptile's localhost:443 finding is fixed via an RFC 6761 .invalid host.

Local: 255/255 tests, 100/100/100 coverage, docstrings 53/53, changelog up to date.

@greptileai please review the new commits.
@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- repository status ---'
git status --short
printf '%s\n' '--- recent commits ---'
git log --oneline -8
printf '%s\n' '--- merge base and diff stat ---'
base="$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main)"
printf 'base=%s\n' "$base"
git diff --stat "$base"...HEAD
printf '%s\n' '--- changed files ---'
git diff --name-status "$base"...HEAD
printf '%s\n' '--- source outlines ---'
fd -t f -e ts -e json -e md . | sort | sed -n '1,240p'
printf '%s\n' '--- TypeScript outlines ---'
fd -t f -e ts . -x ast-grep outline {} --items all 2>/dev/null | sed -n '1,360p'
printf '%s\n' '--- package and manifest paths ---'
fd -t f -a 'package.json' -a 'manifest.json' -a 'CHANGELOG.md' . | sort

Length of output: 22291


🏁 Script executed:

#!/bin/bash
set -euo pipefail
base="$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main)"

printf '%s\n' '--- index production diff ---'
git diff --unified=80 "$base"...HEAD -- index.ts
printf '%s\n' '--- package / manifest / generated declaration diff ---'
git diff --unified=40 "$base"...HEAD -- package.json manifest.json dist/index.d.ts CHANGELOG.md
printf '%s\n' '--- generated runtime diff ---'
git diff --unified=25 "$base"...HEAD -- dist/index.js
printf '%s\n' '--- coverage test start and helpers ---'
sed -n '1,260p' test/coverage.test.ts
printf '%s\n' '--- all local HTTP server construction and close calls ---'
rg -n -C 8P '\b(createServer|listen|close)\s*\(' test/coverage.test.ts
printf '%s\n' '--- direct use of exported test helpers ---'
rg -n -C 3 '__test__\.' test/coverage.test.ts
printf '%s\n' '--- manifest compatibility test ---'
sed -n '1,120p' test/compatibility-floor.test.ts
printf '%s\n' '--- relevant manifest ---'
cat manifest.json

Length of output: 125354


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- coverage test index ---'
rg -n '^test\(' test/coverage.test.ts
printf '%s\n' '--- network tests and server lifecycle ---'
rg -n -C 8 -P '\b(createServer|listen|close)\s*\(' test/coverage.test.ts
printf '%s\n' '--- test global mutation sites ---'
rg -n -C 5 -P '(process\.env|console\.(error|log)|process\.stdout\.(write)|process\.stderr\.(write))' test/coverage.test.ts
printf '%s\n' '--- primary event and error helper tests ---'
rg -n -C 12 -P '(primaryEvent|toErrorMessage)' test/coverage.test.ts
printf '%s\n' '--- status code and retry invariants test context ---'
rg -n -C 12 -P '(statusCode|postToSlackOnce|ENOTFOUND|\.invalid|lastErr)' test/coverage.test.ts
printf '%s\n' '--- coverage gate implementation ---'
sed -n '1,380p' scripts/coverage-gate.ts
printf '%s\n' '--- manifest and compatibility floor test ---'
cat manifest.json
sed -n '1,150p' test/compatibility-floor.test.ts
printf '%s\n' '--- artifact export tails ---'
tail -n 90 index.ts
tail -n 90 dist/index.js
printf '%s\n' '--- package lock diff / package scripts ---'
git diff --name-status "$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main)"...HEAD -- package-lock.json
node -e 'const p=require("./package.json"); console.log(JSON.stringify({scripts:p.scripts,devDependencies:p.devDependencies,coverageGate:p.coverageGate},null,2))'

Length of output: 89538


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- coverage test working-directory and temp-root contexts ---'
sed -n '450,530p' test/coverage.test.ts
sed -n '1160,1278p' test/coverage.test.ts
sed -n '1518,1648p' test/coverage.test.ts
sed -n '1970,2130p' test/coverage.test.ts
sed -n '2280,2438p' test/coverage.test.ts
printf '%s\n' '--- working directory mutation searches across tests ---'
rg -n -C 6 -P '(process\.chdir|cwd\(|mkdtempSync|rmSync)' test
printf '%s\n' '--- test runner configuration ---'
cat tsconfig.test.json
cat tsconfig.json
printf '%s\n' '--- workflows and coverage invocations ---'
fd -t f -p '.github' -e yml -e yaml -x sh -c 'echo "--- $1"; rg -n -C 4 -P "(npm run (test|coverage|release:check)|node --test|coverage)" "$1"' _ {}
printf '%s\n' '--- `.invalid` test exact history ---'
git blame -L 2410,2427 test/coverage.test.ts
printf '%s\n' '--- changed source type-check-sensitive declarations ---'
sed -n '1230,1412p' index.ts

Length of output: 62177


@unbraind: I found one test-stability issue.

  • P2 — Do not depend on an external DNS lookup for HTTPS branch coverage. test/coverage.test.ts Line 2420 calls postToSlackOnce("https://pm-slack-test.invalid/hook", ...). RFC 6761 reserves .invalid, but this test still requires the active DNS resolver to return a negative response. The request timeout starts only after the request exists. It does not bound DNS resolution. A stalled, offline, or misconfigured CI resolver can therefore delay or hang the suite before req.setTimeout(10_000, ...) applies.

    Use a deterministic local HTTPS fixture, or inject the request transport/lookup dependency for this test. This keeps the HTTPS and default-port coverage without resolver-dependent behavior.

The remaining reviewed changes are consistent. The primaryEvent() helper preserves the prior fallback. The compatibility floor supports removal of the beforeCommand fallback. The generated artifacts match the inspected source changes.

You are interacting with an AI system.

@unbraind
unbraind merged commit c79294c into main Sep 18, 2026
9 checks passed
@unbraind
unbraind deleted the test/cover-pm-slack-to-full-coverage branch September 18, 2026 05:28
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.

1 participant