9 Commits

Author SHA1 Message Date
gitea-actions[bot]
45bb09af27 release: prepare v1.2.0 2026-03-21 23:15:26 +00:00
Micheal Wilkinson
995e397bff docs: update upx fallback note
All checks were successful
Push Validation / coverage-badge (push) Successful in 1m24s
Push Validation / recommend-release (push) Successful in 28s
2026-03-21 23:12:30 +00:00
Micheal Wilkinson
8bf7184479 chore(workflows): install upx via ghaction-upx 2026-03-21 23:12:30 +00:00
Micheal Wilkinson
41918cd5de docs: note containerized upx fallback
All checks were successful
Push Validation / coverage-badge (push) Successful in 1m23s
Push Validation / recommend-release (push) Successful in 25s
2026-03-21 23:02:35 +00:00
Micheal Wilkinson
0cec30c9bb chore(workflows): add container upx fallback 2026-03-21 23:02:29 +00:00
Micheal Wilkinson
24dd65da67 docs: log upx fallback in release workflows
All checks were successful
Push Validation / coverage-badge (push) Successful in 1m18s
Push Validation / recommend-release (push) Successful in 25s
2026-03-21 23:00:08 +00:00
Micheal Wilkinson
1ab56b0536 chore(workflows): allow release builds without upx 2026-03-21 23:00:05 +00:00
Micheal Wilkinson
6919061240 docs: log coverage-gate security hardening
Some checks failed
Push Validation / recommend-release (push) Has been cancelled
Push Validation / coverage-badge (push) Has been cancelled
2026-03-21 22:47:04 +00:00
Micheal Wilkinson
7b739e04c8 chore(go): harden coverage-gate file input handling 2026-03-21 22:47:02 +00:00
8 changed files with 131 additions and 47 deletions

View File

@@ -39,11 +39,10 @@ jobs:
cache: true cache: true
cache-dependency-path: go.sum cache-dependency-path: go.sum
- name: Install release build tools - name: Install UPX
run: | uses: crazy-max/ghaction-upx@v3
set -euo pipefail with:
apt-get update install-only: true
apt-get install -y upx-ucl || apt-get install -y upx
- name: Validate formatting - name: Validate formatting
run: test -z "$(gofmt -l .)" run: test -z "$(gofmt -l .)"
@@ -248,13 +247,13 @@ jobs:
run: | run: |
set -euo pipefail set -euo pipefail
upx_cmd=""
if command -v upx >/dev/null 2>&1; then if command -v upx >/dev/null 2>&1; then
upx_cmd=upx upx_cmd=upx
elif command -v upx-ucl >/dev/null 2>&1; then elif command -v upx-ucl >/dev/null 2>&1; then
upx_cmd=upx-ucl upx_cmd=upx-ucl
else else
echo "UPX is not available on PATH after installation." >&2 echo "UPX is not available on PATH after install step; continuing without binary compression." >&2
exit 1
fi fi
mkdir -p dist mkdir -p dist
@@ -265,7 +264,9 @@ jobs:
bin="vociferate_${RELEASE_VERSION}_${os}_${arch}" bin="vociferate_${RELEASE_VERSION}_${os}_${arch}"
GOOS="$os" GOARCH="$arch" go build -trimpath -ldflags="-s -w" -o "dist/${bin}" ./cmd/vociferate GOOS="$os" GOARCH="$arch" go build -trimpath -ldflags="-s -w" -o "dist/${bin}" ./cmd/vociferate
if [[ -n "${upx_cmd}" ]]; then
"${upx_cmd}" --best --lzma "dist/${bin}" "${upx_cmd}" --best --lzma "dist/${bin}"
fi
done done
( (
@@ -345,7 +346,7 @@ jobs:
echo "- Tag: ${TAG_NAME}" echo "- Tag: ${TAG_NAME}"
echo "- Release notes sourced from changelog entry ${RELEASE_VERSION}." echo "- Release notes sourced from changelog entry ${RELEASE_VERSION}."
echo "- Published assets: vociferate_${RELEASE_VERSION}_linux_amd64, vociferate_${RELEASE_VERSION}_linux_arm64, checksums.txt" echo "- Published assets: vociferate_${RELEASE_VERSION}_linux_amd64, vociferate_${RELEASE_VERSION}_linux_arm64, checksums.txt"
echo "- Release binaries were compressed with UPX before upload." echo "- Release binaries are compressed with UPX from crazy-max/ghaction-upx@v3 when available, otherwise uploaded uncompressed."
} >> "$SUMMARY_FILE" } >> "$SUMMARY_FILE"
else else
{ {

View File

@@ -122,11 +122,10 @@ jobs:
cache: true cache: true
cache-dependency-path: go.sum cache-dependency-path: go.sum
- name: Install release build tools - name: Install UPX
run: | uses: crazy-max/ghaction-upx@v3
set -euo pipefail with:
apt-get update install-only: true
apt-get install -y upx-ucl || apt-get install -y upx
- name: Preflight release API access - name: Preflight release API access
env: env:
@@ -170,13 +169,13 @@ jobs:
run: | run: |
set -euo pipefail set -euo pipefail
upx_cmd=""
if command -v upx >/dev/null 2>&1; then if command -v upx >/dev/null 2>&1; then
upx_cmd=upx upx_cmd=upx
elif command -v upx-ucl >/dev/null 2>&1; then elif command -v upx-ucl >/dev/null 2>&1; then
upx_cmd=upx-ucl upx_cmd=upx-ucl
else else
echo "UPX is not available on PATH after installation." >&2 echo "UPX is not available on PATH after install step; continuing without binary compression." >&2
exit 1
fi fi
mkdir -p dist mkdir -p dist
@@ -187,7 +186,9 @@ jobs:
bin="vociferate_${RELEASE_VERSION}_${os}_${arch}" bin="vociferate_${RELEASE_VERSION}_${os}_${arch}"
GOOS="$os" GOARCH="$arch" go build -trimpath -ldflags="-s -w" -o "dist/${bin}" ./cmd/vociferate GOOS="$os" GOARCH="$arch" go build -trimpath -ldflags="-s -w" -o "dist/${bin}" ./cmd/vociferate
if [[ -n "${upx_cmd}" ]]; then
"${upx_cmd}" --best --lzma "dist/${bin}" "${upx_cmd}" --best --lzma "dist/${bin}"
fi
done done
( (
@@ -267,7 +268,7 @@ jobs:
echo "- Tag: ${TAG_NAME}" echo "- Tag: ${TAG_NAME}"
echo "- Release notes sourced from changelog entry ${RELEASE_VERSION}." echo "- Release notes sourced from changelog entry ${RELEASE_VERSION}."
echo "- Published assets: vociferate_${RELEASE_VERSION}_linux_amd64, vociferate_${RELEASE_VERSION}_linux_arm64, checksums.txt" echo "- Published assets: vociferate_${RELEASE_VERSION}_linux_amd64, vociferate_${RELEASE_VERSION}_linux_arm64, checksums.txt"
echo "- Release binaries were compressed with UPX before upload." echo "- Release binaries are compressed with UPX from crazy-max/ghaction-upx@v3 when available, otherwise uploaded uncompressed."
} >> "$SUMMARY_FILE" } >> "$SUMMARY_FILE"
else else
{ {

View File

@@ -4,15 +4,15 @@ This guide is for agentic coding partners that need to integrate the composite a
## Source Of Truth ## Source Of Truth
Pin all action references to a released tag (for example `@v1.1.0`) and keep all vociferate references on the same tag in a workflow. Pin all action references to a released tag (for example `@v1.2.0`) and keep all vociferate references on the same tag in a workflow.
Published composite actions: Published composite actions:
- `https://git.hrafn.xyz/aether/vociferate@v1.1.0` (root action) - `https://git.hrafn.xyz/aether/vociferate@v1.2.0` (root action)
- `https://git.hrafn.xyz/aether/vociferate/prepare@v1.1.0` - `https://git.hrafn.xyz/aether/vociferate/prepare@v1.2.0`
- `https://git.hrafn.xyz/aether/vociferate/publish@v1.1.0` - `https://git.hrafn.xyz/aether/vociferate/publish@v1.2.0`
- `https://git.hrafn.xyz/aether/vociferate/coverage-badge@v1.1.0` - `https://git.hrafn.xyz/aether/vociferate/coverage-badge@v1.2.0`
- `https://git.hrafn.xyz/aether/vociferate/decorate-pr@v1.1.0` - `https://git.hrafn.xyz/aether/vociferate/decorate-pr@v1.2.0`
## Action Selection Matrix ## Action Selection Matrix
@@ -88,7 +88,7 @@ Minimal template:
```yaml ```yaml
jobs: jobs:
release: release:
uses: https://git.hrafn.xyz/aether/vociferate/.gitea/workflows/release.yml@v1.1.0 uses: https://git.hrafn.xyz/aether/vociferate/.gitea/workflows/release.yml@v1.2.0
with: with:
version: ${{ inputs.version }} version: ${{ inputs.version }}
secrets: inherit secrets: inherit
@@ -99,7 +99,7 @@ jobs:
```yaml ```yaml
jobs: jobs:
release: release:
uses: https://git.hrafn.xyz/aether/vociferate/.gitea/workflows/update-release.yml@v1.1.0 uses: https://git.hrafn.xyz/aether/vociferate/.gitea/workflows/update-release.yml@v1.2.0
with: with:
tag: v1.2.3 tag: v1.2.3
secrets: inherit secrets: inherit
@@ -121,7 +121,7 @@ jobs:
- name: Run tests with coverage - name: Run tests with coverage
run: go test -covermode=atomic -coverprofile=coverage.out ./... run: go test -covermode=atomic -coverprofile=coverage.out ./...
- id: badge - id: badge
uses: https://git.hrafn.xyz/aether/vociferate/coverage-badge@v1.1.0 uses: https://git.hrafn.xyz/aether/vociferate/coverage-badge@v1.2.0
with: with:
artefact-bucket-name: ${{ vars.ARTEFACT_BUCKET_NAME }} artefact-bucket-name: ${{ vars.ARTEFACT_BUCKET_NAME }}
artefact-bucket-endpoint: ${{ vars.ARTEFACT_BUCKET_ENDPONT }} artefact-bucket-endpoint: ${{ vars.ARTEFACT_BUCKET_ENDPONT }}
@@ -144,12 +144,12 @@ jobs:
- name: Run tests with coverage - name: Run tests with coverage
run: go test -covermode=atomic -coverprofile=coverage.out ./... run: go test -covermode=atomic -coverprofile=coverage.out ./...
- id: badge - id: badge
uses: https://git.hrafn.xyz/aether/vociferate/coverage-badge@v1.1.0 uses: https://git.hrafn.xyz/aether/vociferate/coverage-badge@v1.2.0
with: with:
artefact-bucket-name: ${{ vars.ARTEFACT_BUCKET_NAME }} artefact-bucket-name: ${{ vars.ARTEFACT_BUCKET_NAME }}
artefact-bucket-endpoint: ${{ vars.ARTEFACT_BUCKET_ENDPONT }} artefact-bucket-endpoint: ${{ vars.ARTEFACT_BUCKET_ENDPONT }}
- name: Decorate PR - name: Decorate PR
uses: https://git.hrafn.xyz/aether/vociferate/decorate-pr@v1.1.0 uses: https://git.hrafn.xyz/aether/vociferate/decorate-pr@v1.2.0
with: with:
coverage-percentage: ${{ steps.badge.outputs.total }} coverage-percentage: ${{ steps.badge.outputs.total }}
badge-url: ${{ steps.badge.outputs.badge-url }} badge-url: ${{ steps.badge.outputs.badge-url }}

View File

@@ -13,6 +13,18 @@ A `### Breaking` section is used in addition to Keep a Changelog's standard sect
### Added ### Added
### Changed
### Removed
### Fixed
## [1.2.0] - 2026-03-21
### Breaking
### Added
- Extracted `coverage-gate` action and tool from Cue for reuse across Æther projects. - Extracted `coverage-gate` action and tool from Cue for reuse across Æther projects.
- Coverage gate now available as reusable composite action with JSON metrics output (`passes`, `total_coverage`, `packages_checked`, `packages_failed`). - Coverage gate now available as reusable composite action with JSON metrics output (`passes`, `total_coverage`, `packages_checked`, `packages_failed`).
- Support for per-package coverage threshold policy via JSON configuration in `coverage-gate` tool. - Support for per-package coverage threshold policy via JSON configuration in `coverage-gate` tool.
@@ -23,6 +35,9 @@ A `### Breaking` section is used in addition to Keep a Changelog's standard sect
### Fixed ### Fixed
- Hardened `coverage-gate` file input handling by validating and normalizing policy/profile paths before opening files, resolving `G304` findings in `coverage-gate/parse.go`.
- Made release binary builds resilient by installing UPX via `crazy-max/ghaction-upx@v3` and falling back to uncompressed artifacts when UPX is still unavailable in both `release.yml` and `update-release.yml`.
## [1.1.0] - 2026-03-21 ## [1.1.0] - 2026-03-21
### Breaking ### Breaking
@@ -175,10 +190,11 @@ A `### Breaking` section is used in addition to Keep a Changelog's standard sect
- Project/automation rename from `releaseprep` to `vociferate` (entrypoint, package paths, outputs). - Project/automation rename from `releaseprep` to `vociferate` (entrypoint, package paths, outputs).
- README guidance focused on primary cross-repository reuse workflows. - README guidance focused on primary cross-repository reuse workflows.
[Unreleased]: https://git.hrafn.xyz/aether/vociferate/compare/v1.1.0...main [Unreleased]: https://git.hrafn.xyz/aether/vociferate/compare/v1.2.0...main
[1.2.0]: https://git.hrafn.xyz/aether/vociferate/compare/v1.1.0...v1.2.0
[1.1.0]: https://git.hrafn.xyz/aether/vociferate/compare/v1.0.2...v1.1.0 [1.1.0]: https://git.hrafn.xyz/aether/vociferate/compare/v1.0.2...v1.1.0
[1.0.2]: https://git.hrafn.xyz/aether/vociferate/compare/v1.0.1...v1.0.2 [1.0.2]: https://git.hrafn.xyz/aether/vociferate/compare/v1.0.1...v1.0.2
[1.0.1]: https://git.hrafn.xyz/aether/vociferate/compare/v1.0.0...v1.0.1 [1.0.1]: https://git.hrafn.xyz/aether/vociferate/compare/v1.0.0...v1.0.1
[1.0.0]: https://git.hrafn.xyz/aether/vociferate/compare/v0.2.0...v1.0.0 [1.0.0]: https://git.hrafn.xyz/aether/vociferate/compare/v0.2.0...v1.0.0
[0.2.0]: https://git.hrafn.xyz/aether/vociferate/compare/v0.1.0...v0.2.0 [0.2.0]: https://git.hrafn.xyz/aether/vociferate/compare/v0.1.0...v0.2.0
[0.1.0]: https://git.hrafn.xyz/aether/vociferate/compare/81dced6...v0.1.0 [0.1.0]: https://git.hrafn.xyz/aether/vociferate/compare/995e397...v0.1.0

View File

@@ -17,7 +17,7 @@ revision.
## Use In Other Repositories ## Use In Other Repositories
Vociferate ships composite actions covering release preparation, release publication, coverage badge publishing, and pull request decoration. Vociferate ships composite actions covering release preparation, release publication, coverage badge publishing, and pull request decoration.
Release tags now exist; pin all action and reusable-workflow references to the same released tag (for example, `@v1.1.0`) instead of `@main`. Release tags now exist; pin all action and reusable-workflow references to the same released tag (for example, `@v1.2.0`) instead of `@main`.
For agentic coding partners, see [`AGENTS.md`](AGENTS.md) for a direct integration playbook, selection matrix, and copy-paste workflow patterns. For agentic coding partners, see [`AGENTS.md`](AGENTS.md) for a direct integration playbook, selection matrix, and copy-paste workflow patterns.
@@ -41,13 +41,13 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: https://git.hrafn.xyz/aether/vociferate/prepare@v1.1.0 - uses: https://git.hrafn.xyz/aether/vociferate/prepare@v1.2.0
with: with:
version: ${{ inputs.version }} version: ${{ inputs.version }}
publish: publish:
needs: prepare needs: prepare
uses: https://git.hrafn.xyz/aether/vociferate/.gitea/workflows/update-release.yml@v1.1.0 uses: https://git.hrafn.xyz/aether/vociferate/.gitea/workflows/update-release.yml@v1.2.0
with: with:
tag: ${{ needs.prepare.outputs.version }} tag: ${{ needs.prepare.outputs.version }}
secrets: inherit secrets: inherit
@@ -61,7 +61,7 @@ For repositories that embed the version inside source code, pass `version-file`
and `version-pattern`: and `version-pattern`:
```yaml ```yaml
- uses: https://git.hrafn.xyz/aether/vociferate/prepare@v1.1.0 - uses: https://git.hrafn.xyz/aether/vociferate/prepare@v1.2.0
with: with:
token: ${{ secrets.RELEASE_PAT }} token: ${{ secrets.RELEASE_PAT }}
version-file: internal/myapp/version/version.go version-file: internal/myapp/version/version.go
@@ -86,7 +86,7 @@ on:
jobs: jobs:
release: release:
uses: https://git.hrafn.xyz/aether/vociferate/.gitea/workflows/update-release.yml@v1.1.0 uses: https://git.hrafn.xyz/aether/vociferate/.gitea/workflows/update-release.yml@v1.2.0
with: with:
tag: ${{ inputs.tag }} tag: ${{ inputs.tag }}
secrets: inherit secrets: inherit
@@ -105,7 +105,7 @@ assets after it runs:
```yaml ```yaml
- id: publish - id: publish
uses: https://git.hrafn.xyz/aether/vociferate/publish@v1.1.0 uses: https://git.hrafn.xyz/aether/vociferate/publish@v1.2.0
- name: Upload my binary - name: Upload my binary
run: | run: |
@@ -125,7 +125,7 @@ Run your coverage tests first, then call the action to generate `coverage.html`,
run: go test -covermode=atomic -coverprofile=coverage.out ./... run: go test -covermode=atomic -coverprofile=coverage.out ./...
- id: coverage - id: coverage
uses: https://git.hrafn.xyz/aether/vociferate/coverage-badge@v1.1.0 uses: https://git.hrafn.xyz/aether/vociferate/coverage-badge@v1.2.0
with: with:
artefact-bucket-name: ${{ vars.ARTEFACT_BUCKET_NAME }} artefact-bucket-name: ${{ vars.ARTEFACT_BUCKET_NAME }}
artefact-bucket-endpoint: ${{ vars.ARTEFACT_BUCKET_ENDPONT }} artefact-bucket-endpoint: ${{ vars.ARTEFACT_BUCKET_ENDPONT }}
@@ -150,14 +150,14 @@ with a clear message when token permissions are insufficient.
run: go test -covermode=atomic -coverprofile=coverage.out ./... run: go test -covermode=atomic -coverprofile=coverage.out ./...
- id: coverage - id: coverage
uses: https://git.hrafn.xyz/aether/vociferate/coverage-badge@v1.1.0 uses: https://git.hrafn.xyz/aether/vociferate/coverage-badge@v1.2.0
with: with:
artefact-bucket-name: ${{ vars.ARTEFACT_BUCKET_NAME }} artefact-bucket-name: ${{ vars.ARTEFACT_BUCKET_NAME }}
artefact-bucket-endpoint: ${{ vars.ARTEFACT_BUCKET_ENDPONT }} artefact-bucket-endpoint: ${{ vars.ARTEFACT_BUCKET_ENDPONT }}
- name: Decorate pull request - name: Decorate pull request
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
uses: https://git.hrafn.xyz/aether/vociferate/decorate-pr@v1.1.0 uses: https://git.hrafn.xyz/aether/vociferate/decorate-pr@v1.2.0
with: with:
coverage-percentage: ${{ steps.coverage.outputs.total }} coverage-percentage: ${{ steps.coverage.outputs.total }}
badge-url: ${{ steps.coverage.outputs.badge-url }} badge-url: ${{ steps.coverage.outputs.badge-url }}
@@ -170,7 +170,7 @@ Enable changelog validation to enforce that code changes include `Unreleased` ch
```yaml ```yaml
- name: Decorate pull request with changelog gate - name: Decorate pull request with changelog gate
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
uses: https://git.hrafn.xyz/aether/vociferate/decorate-pr@v1.1.0 uses: https://git.hrafn.xyz/aether/vociferate/decorate-pr@v1.2.0
with: with:
coverage-percentage: ${{ steps.coverage.outputs.total }} coverage-percentage: ${{ steps.coverage.outputs.total }}
badge-url: ${{ steps.coverage.outputs.badge-url }} badge-url: ${{ steps.coverage.outputs.badge-url }}
@@ -196,7 +196,7 @@ Decision outputs enable downstream workflow logic:
- name: Decorate PR and check gate - name: Decorate PR and check gate
id: decorate id: decorate
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
uses: https://git.hrafn.xyz/aether/vociferate/decorate-pr@v1.1.0 uses: https://git.hrafn.xyz/aether/vociferate/decorate-pr@v1.2.0
with: with:
coverage-percentage: ${{ steps.coverage.outputs.total }} coverage-percentage: ${{ steps.coverage.outputs.total }}
badge-url: ${{ steps.coverage.outputs.badge-url }} badge-url: ${{ steps.coverage.outputs.badge-url }}

View File

@@ -43,9 +43,9 @@ type PackageResult struct {
// LoadPolicy reads policy JSON from disk. // LoadPolicy reads policy JSON from disk.
func LoadPolicy(path string) (Policy, error) { func LoadPolicy(path string) (Policy, error) {
f, err := os.Open(path) f, err := openValidatedReadOnlyFile(path, ".json", "policy")
if err != nil { if err != nil {
return Policy{}, fmt.Errorf("open policy: %w", err) return Policy{}, err
} }
defer f.Close() defer f.Close()
@@ -61,9 +61,9 @@ func LoadPolicy(path string) (Policy, error) {
// ParseCoverProfile parses a Go coverprofile and aggregates package coverage. // ParseCoverProfile parses a Go coverprofile and aggregates package coverage.
func ParseCoverProfile(profilePath string, policy Policy) (map[string]Coverage, error) { func ParseCoverProfile(profilePath string, policy Policy) (map[string]Coverage, error) {
f, err := os.Open(profilePath) f, err := openValidatedReadOnlyFile(profilePath, "", "coverage profile")
if err != nil { if err != nil {
return nil, fmt.Errorf("open coverage profile: %w", err) return nil, err
} }
defer f.Close() defer f.Close()
@@ -121,6 +121,41 @@ func ParseCoverProfile(profilePath string, policy Policy) (map[string]Coverage,
return coverage, nil return coverage, nil
} }
func openValidatedReadOnlyFile(path string, requiredExt string, label string) (*os.File, error) {
cleaned := filepath.Clean(strings.TrimSpace(path))
if cleaned == "" || cleaned == "." {
return nil, fmt.Errorf("invalid %s path", label)
}
if requiredExt != "" {
ext := strings.ToLower(filepath.Ext(cleaned))
if ext != strings.ToLower(requiredExt) {
return nil, fmt.Errorf("invalid %s file extension: got %q, want %q", label, ext, requiredExt)
}
}
absPath, err := filepath.Abs(cleaned)
if err != nil {
return nil, fmt.Errorf("resolve %s path: %w", label, err)
}
info, err := os.Stat(absPath)
if err != nil {
return nil, fmt.Errorf("stat %s: %w", label, err)
}
if info.IsDir() {
return nil, fmt.Errorf("%s path must be a file, got directory", label)
}
// #nosec G304 -- path is explicitly cleaned, normalized, and pre-validated as an existing file.
f, err := os.Open(absPath)
if err != nil {
return nil, fmt.Errorf("open %s: %w", label, err)
}
return f, nil
}
// EvaluateCoverage evaluates package coverage against policy thresholds. // EvaluateCoverage evaluates package coverage against policy thresholds.
func EvaluateCoverage(packages []string, byPackage map[string]Coverage, policy Policy) []PackageResult { func EvaluateCoverage(packages []string, byPackage map[string]Coverage, policy Policy) []PackageResult {
results := make([]PackageResult, 0, len(packages)) results := make([]PackageResult, 0, len(packages))

View File

@@ -3,6 +3,7 @@ package main
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
) )
@@ -86,3 +87,33 @@ func TestEvaluateCoverage_NoStatementsPasses(t *testing.T) {
t.Fatalf("expected pass for no-statement package, got %+v", results[0]) t.Fatalf("expected pass for no-statement package, got %+v", results[0])
} }
} }
func TestLoadPolicy_RejectsNonJSONPath(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
policyPath := filepath.Join(tmp, "policy.yaml")
if err := os.WriteFile(policyPath, []byte("minimum_statement_coverage: 80\n"), 0600); err != nil {
t.Fatalf("write policy file: %v", err)
}
_, err := LoadPolicy(policyPath)
if err == nil {
t.Fatal("expected LoadPolicy to fail for non-json extension")
}
if !strings.Contains(err.Error(), "invalid policy file extension") {
t.Fatalf("expected extension error, got: %v", err)
}
}
func TestParseCoverProfile_RejectsDirectoryPath(t *testing.T) {
t.Parallel()
_, err := ParseCoverProfile(t.TempDir(), Policy{MinimumStatementCoverage: 80})
if err == nil {
t.Fatal("expected ParseCoverProfile to fail for directory path")
}
if !strings.Contains(err.Error(), "coverage profile path must be a file") {
t.Fatalf("expected directory path error, got: %v", err)
}
}

View File

@@ -1 +1 @@
1.1.0 1.2.0