Skip to content

1.7.0-3 Bugfixes

1.7.0-3 Bugfixes #153

Workflow file for this run

name: CI
on:
pull_request:
push:
branches:
- main
jobs:
hassfest:
name: Hassfest validation
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
repository: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || github.repository }}
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}
- name: Detect hassfest auto-fix check
id: hassfest_autofix
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TARGET_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
python - <<'PY'
import json
import os
import re
import sys
import urllib.error
import urllib.request
repository = os.environ["GITHUB_REPOSITORY"]
target_sha = os.environ["TARGET_SHA"]
token = os.environ.get("GITHUB_TOKEN")
request = urllib.request.Request(
f"https://api.github.com/repos/{repository}/commits/{target_sha}/check-runs"
)
request.add_header("Accept", "application/vnd.github+json")
if token:
request.add_header("Authorization", f"Bearer {token}")
try:
with urllib.request.urlopen(request, timeout=30) as response:
payload = json.load(response)
except urllib.error.HTTPError as exc: # pragma: no cover - network error surface
print(f"Warning: unable to query check runs (status={exc.code}). Assuming none present.")
payload = {"check_runs": []}
except urllib.error.URLError as exc: # pragma: no cover - network error surface
print(f"Warning: unable to reach GitHub API ({exc}). Assuming no matching checks.")
payload = {"check_runs": []}
pattern = re.compile(r"(?i)^hassfest( \(auto-fix\)( / hassfest.*)?)?$")
has_match = any(
pattern.search(run.get("name", ""))
for run in payload.get("check_runs", [])
)
output_path = os.environ["GITHUB_OUTPUT"]
with open(output_path, "a", encoding="utf-8") as handle:
handle.write(f"found={'true' if has_match else 'false'}\n")
print(
"Detected hassfest (auto-fix) check: {}".format(
"yes" if has_match else "no"
)
)
PY
- name: Wait for hassfest auto-fix workflow to finish
if: steps.hassfest_autofix.outputs.found == 'true'
uses: lewagon/wait-on-check-action@v1.3.3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
check-regexp: '^[Hh]assfest( \(auto-fix\)( / hassfest.*)?)?$'
allowed-conclusions: 'success,failure,neutral'
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 10
- name: Run hassfest
id: hassfest
uses: home-assistant/actions/hassfest@master
- name: Write hassfest summary
if: always()
run: |
{
echo "## Hassfest Validation"
echo ""
if [ "${{ steps.hassfest.outcome }}" = "success" ]; then
echo "✅ **Manifest validation passed**"
echo ""
echo "The \`manifest.json\` and integration structure are valid."
else
echo "❌ **Manifest validation failed**"
echo ""
echo "Check the logs above for details."
fi
} >> "$GITHUB_STEP_SUMMARY"
hacs:
name: HACS validation
runs-on: ubuntu-latest
needs: hassfest
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
repository: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || github.repository }}
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Probe brands endpoint
id: brands_probe
run: |
set -u
if curl -sSfL https://brands.home-assistant.io/domains.json >/dev/null; then
echo "available=true" >> "$GITHUB_OUTPUT"
else
echo "available=false" >> "$GITHUB_OUTPUT"
echo "::warning ::brands.home-assistant.io unavailable; skipping HACS validation to avoid false negatives."
fi
- name: Run HACS validation
if: steps.brands_probe.outputs.available == 'true'
id: hacs
uses: hacs/action@main
with:
category: integration
ignore: topics issues
- name: Write HACS summary
if: always()
run: |
{
echo "## HACS Validation"
echo ""
if [ "${{ steps.brands_probe.outputs.available }}" != "true" ]; then
echo "⚠️ **Skipped** - brands.home-assistant.io unavailable"
elif [ "${{ steps.hacs.outcome }}" = "success" ]; then
echo "✅ **HACS validation passed**"
echo ""
echo "The integration meets HACS requirements."
else
echo "❌ **HACS validation failed**"
echo ""
echo "Check the logs above for details."
fi
} >> "$GITHUB_STEP_SUMMARY"
lint:
name: Lint (${{ matrix.check }})
runs-on: ubuntu-latest
needs: hassfest
strategy:
fail-fast: false
matrix:
check: [ruff, mypy, codespell, bandit]
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
repository: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || github.repository }}
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: latest
virtualenvs-create: true
virtualenvs-in-project: true
- name: Cache Poetry dependencies
uses: actions/cache@v4
with:
path: .venv
key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Install dependencies
run: poetry install --with dev,test
- name: Run Ruff
if: matrix.check == 'ruff'
id: ruff
run: |
set +e
poetry run ruff check . 2>&1 | tee ruff_output.txt
EXIT_CODE=${PIPESTATUS[0]}
echo "exit_code=$EXIT_CODE" >> "$GITHUB_OUTPUT"
exit $EXIT_CODE
- name: Run mypy (strict)
if: matrix.check == 'mypy'
id: mypy
run: |
set +e
{
poetry run mypy --install-types --non-interactive --strict -p custom_components.googlefindmy
poetry run mypy --install-types --non-interactive --strict -p tests
} 2>&1 | tee mypy_output.txt
EXIT_CODE=${PIPESTATUS[0]}
echo "exit_code=$EXIT_CODE" >> "$GITHUB_OUTPUT"
exit $EXIT_CODE
- name: Run codespell
if: matrix.check == 'codespell'
id: codespell
continue-on-error: true # Non-blocking - informational only
run: |
set +e
poetry run codespell 2>&1 | tee codespell_output.txt
EXIT_CODE=${PIPESTATUS[0]}
echo "exit_code=$EXIT_CODE" >> "$GITHUB_OUTPUT"
exit $EXIT_CODE
- name: Run bandit (security)
if: matrix.check == 'bandit'
id: bandit
run: |
set +e
# Run bandit security scanner on custom_components and tests
# Exclude known false positives and test files that intentionally test security
poetry run bandit -r custom_components -c pyproject.toml -f txt 2>&1 | tee bandit_output.txt
EXIT_CODE=${PIPESTATUS[0]}
echo "exit_code=$EXIT_CODE" >> "$GITHUB_OUTPUT"
exit $EXIT_CODE
- name: Write lint summary
if: always()
run: |
{
echo "## ${{ matrix.check }} Results"
echo ""
if [ "${{ matrix.check }}" = "ruff" ]; then
if [ -f ruff_output.txt ]; then
ISSUES=$(grep -c "^" ruff_output.txt 2>/dev/null || echo "0")
if [ "$ISSUES" = "0" ] || [ ! -s ruff_output.txt ]; then
echo "✅ **No issues found**"
else
echo "❌ **Issues found:**"
echo ""
echo '```'
head -100 ruff_output.txt
echo '```'
fi
fi
elif [ "${{ matrix.check }}" = "mypy" ]; then
if [ -f mypy_output.txt ]; then
ERRORS=$(grep -c "error:" mypy_output.txt 2>/dev/null || echo "0")
if [ "$ERRORS" = "0" ]; then
echo "✅ **No type errors found**"
else
echo "❌ **$ERRORS type error(s) found:**"
echo ""
echo '```'
head -100 mypy_output.txt
echo '```'
fi
fi
elif [ "${{ matrix.check }}" = "codespell" ]; then
if [ -f codespell_output.txt ]; then
if [ ! -s codespell_output.txt ]; then
echo "✅ **No spelling errors found**"
else
echo "ℹ️ **Spelling suggestions (non-blocking):**"
echo ""
echo "These are informational only. Add false positives to \`pyproject.toml\` ignore-words-list."
echo ""
echo '```'
cat codespell_output.txt
echo '```'
fi
fi
elif [ "${{ matrix.check }}" = "bandit" ]; then
if [ -f bandit_output.txt ]; then
ISSUES=$(grep -c "Issue:" bandit_output.txt 2>/dev/null || echo "0")
if [ "$ISSUES" = "0" ]; then
echo "✅ **No security issues found**"
else
echo "⚠️ **$ISSUES potential security issue(s) found:**"
echo ""
echo '```'
cat bandit_output.txt
echo '```'
fi
fi
fi
} >> "$GITHUB_STEP_SUMMARY"
test:
name: Test (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
needs:
- hassfest
- hacs
strategy:
fail-fast: false
matrix:
python-version: ["3.13"]
# Can add more Python versions when available:
# python-version: ["3.13", "3.14"]
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
repository: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || github.repository }}
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: latest
virtualenvs-create: true
virtualenvs-in-project: true
- name: Cache Poetry dependencies
uses: actions/cache@v4
with:
path: .venv
key: ${{ runner.os }}-py${{ matrix.python-version }}-poetry-${{ hashFiles('poetry.lock') }}
restore-keys: |
${{ runner.os }}-py${{ matrix.python-version }}-poetry-
- name: Cache pre-commit environments
uses: actions/cache@v4
with:
path: ~/.cache/pre-commit
key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
restore-keys: |
${{ runner.os }}-pre-commit-
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libc-ares-dev
- name: Install dependencies
run: poetry install --with dev,test
- name: Run pytest with coverage
env:
PYTHONPATH: .
run: |
set -o pipefail
poetry run pytest -q --cov --cov-report=xml --cov-report=term 2>&1 | tee pytest_output.log
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml
flags: unittests
name: codecov-py${{ matrix.python-version }}
fail_ci_if_error: false
verbose: true
- name: Write short summary to GitHub Step Summary
if: always()
run: |
{
echo "## Pytest summary (Python ${{ matrix.python-version }})"
echo
echo '```'
tail -n 200 pytest_output.log
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
# Summary job to require all tests pass
ci-success:
name: CI Success
runs-on: ubuntu-latest
needs: [hassfest, hacs, lint, test]
if: always()
steps:
- name: Check all jobs passed
id: check
run: |
if [[ "${{ needs.hassfest.result }}" != "success" ]] || \
[[ "${{ needs.hacs.result }}" != "success" ]] || \
[[ "${{ needs.lint.result }}" != "success" ]] || \
[[ "${{ needs.test.result }}" != "success" ]]; then
echo "One or more jobs failed"
echo "passed=false" >> "$GITHUB_OUTPUT"
exit 1
fi
echo "All CI jobs passed!"
echo "passed=true" >> "$GITHUB_OUTPUT"
- name: Write CI summary
if: always()
run: |
{
echo "# CI Summary"
echo ""
echo "| Job | Status |"
echo "|-----|--------|"
if [ "${{ needs.hassfest.result }}" = "success" ]; then
echo "| Hassfest | ✅ Pass |"
else
echo "| Hassfest | ❌ Fail |"
fi
if [ "${{ needs.hacs.result }}" = "success" ]; then
echo "| HACS | ✅ Pass |"
else
echo "| HACS | ❌ Fail |"
fi
if [ "${{ needs.lint.result }}" = "success" ]; then
echo "| Lint (ruff, mypy, codespell, bandit) | ✅ Pass |"
else
echo "| Lint (ruff, mypy, codespell, bandit) | ❌ Fail |"
fi
if [ "${{ needs.test.result }}" = "success" ]; then
echo "| Tests | ✅ Pass |"
else
echo "| Tests | ❌ Fail |"
fi
echo ""
if [ "${{ steps.check.outputs.passed }}" = "true" ]; then
echo "## ✅ All checks passed!"
else
echo "## ❌ Some checks failed"
echo ""
echo "Please review the failed jobs above."
fi
} >> "$GITHUB_STEP_SUMMARY"