From a9f69261049e45b36119fd502dd0d7fc2be2691c Mon Sep 17 00:00:00 2001 From: Jorijn Schrijvershof Date: Thu, 8 Jan 2026 17:16:53 +0100 Subject: [PATCH] test: add comprehensive pytest test suite with 95% coverage (#29) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: add comprehensive pytest test suite with 95% coverage Add full unit and integration test coverage for the meshcore-stats project: - 1020 tests covering all modules (db, charts, html, reports, client, etc.) - 95.95% code coverage with pytest-cov (95% threshold enforced) - GitHub Actions CI workflow for automated testing on push/PR - Proper mocking of external dependencies (meshcore, serial, filesystem) - SVG snapshot infrastructure for chart regression testing - Integration tests for collection and rendering pipelines Test organization: - tests/charts/: Chart rendering and statistics - tests/client/: MeshCore client and connection handling - tests/config/: Environment and configuration parsing - tests/database/: SQLite operations and migrations - tests/html/: HTML generation and Jinja templates - tests/reports/: Report generation and formatting - tests/retry/: Circuit breaker and retry logic - tests/unit/: Pure unit tests for utilities - tests/integration/: End-to-end pipeline tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * chore: add test-engineer agent configuration Add project-local test-engineer agent for pytest test development, coverage analysis, and test review tasks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * docs: comprehensive test suite review with 956 tests analyzed Conducted thorough review of all 956 test cases across 47 test files: - Unit Tests: 338 tests (battery, metrics, log, telemetry, env, charts, html, reports, formatters) - Config Tests: 53 tests (env loading, config file parsing) - Database Tests: 115 tests (init, insert, queries, migrations, maintenance, validation) - Retry Tests: 59 tests (circuit breaker, async retries, factory) - Charts Tests: 76 tests (transforms, statistics, timeseries, rendering, I/O) - HTML Tests: 81 tests (site generation, Jinja2, metrics builders, reports index) - Reports Tests: 149 tests (location, JSON/TXT formatting, aggregation, counter totals) - Client Tests: 63 tests (contacts, connection, meshcore availability, commands) - Integration Tests: 22 tests (reports, collection, rendering pipelines) Results: - Overall Pass Rate: 99.7% (953/956) - 3 tests marked for improvement (empty test bodies in client tests) - 0 tests requiring fixes Key findings documented in test_review/tests.md including quality observations, F.I.R.S.T. principle adherence, and recommendations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * test: implement snapshot testing for charts and reports Add comprehensive snapshot testing infrastructure: SVG Chart Snapshots: - Deterministic fixtures with fixed timestamps (2024-01-15 12:00:00) - Tests for gauge/counter metrics in light/dark themes - Empty chart and single-point edge cases - Extended normalize_svg_for_snapshot_full() for reproducible comparisons TXT Report Snapshots: - Monthly/yearly report snapshots for repeater and companion - Empty report handling tests - Tests in tests/reports/test_snapshots.py Infrastructure: - tests/snapshots/conftest.py with shared fixtures - UPDATE_SNAPSHOTS=1 environment variable for regeneration - scripts/generate_snapshots.py for batch snapshot generation Run `UPDATE_SNAPSHOTS=1 pytest tests/charts/test_chart_render.py::TestSvgSnapshots` to generate initial snapshots. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * test: fix SVG normalization and generate initial snapshots Fix normalize_svg_for_snapshot() to handle: - clipPath IDs like id="p47c77a2a6e" - url(#p...) references - xlink:href="#p..." references - timestamps Generated initial snapshot files: - 7 SVG chart snapshots (gauge, counter, empty, single-point in light/dark) - 6 TXT report snapshots (monthly/yearly for repeater/companion + empty) All 13 snapshot tests now pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * test: fix SVG normalization to preserve axis rendering The SVG normalization was replacing all matplotlib-generated IDs with the same value, causing duplicate IDs that broke SVG rendering: - Font glyphs, clipPaths, and tick marks all got id="normalized" - References couldn't resolve to the correct elements - X and Y axes failed to render in normalized snapshots Fix uses type-specific prefixes with sequential numbering: - glyph_N for font glyphs (DejaVuSans-XX patterns) - clip_N for clipPath definitions (p[0-9a-f]{8,} patterns) - tick_N for tick marks (m[0-9a-f]{8,} patterns) This ensures all IDs remain unique while still being deterministic for snapshot comparison. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * chore: add coverage and pytest artifacts to gitignore Add .coverage, .coverage.*, htmlcov/, and .pytest_cache/ to prevent test artifacts from being committed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * style: fix all ruff lint errors across codebase - Sort and organize imports (I001) - Use modern type annotations (X | Y instead of Union, collections.abc) - Remove unused imports (F401) - Combine nested if statements (SIM102) - Use ternary operators where appropriate (SIM108) - Combine nested with statements (SIM117) - Use contextlib.suppress instead of try-except-pass (SIM105) - Add noqa comments for intentional SIM115 violations (file locks) - Add TYPE_CHECKING import for forward references - Fix exception chaining (B904) All 1033 tests pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * docs: add TDD workflow and pre-commit requirements to CLAUDE.md - Add mandatory test-driven development workflow (write tests first) - Add pre-commit requirements (must run lint and tests before committing) - Document test organization and running commands - Document 95% coverage requirement 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix: resolve mypy type checking errors with proper structural fixes - charts.py: Create PeriodConfig dataclass for type-safe period configuration, use mdates.date2num() for matplotlib datetime handling, fix x-axis limits for single-point charts - db.py: Add explicit int() conversion with None handling for SQLite returns - env.py: Add class-level type annotations to Config class - html.py: Add MetricDisplay TypedDict, fix import order, add proper type annotations for table data functions - meshcore_client.py: Add return type annotation Update tests to use new dataclass attribute access and regenerate SVG snapshots. Add mypy step to CLAUDE.md pre-commit requirements. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix: cast Jinja2 template.render() to str for mypy Jinja2's type stubs declare render() as returning Any, but it actually returns str. Wrap with str() to satisfy mypy's no-any-return check. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * ci: improve workflow security and reliability - test.yml: Pin all actions by SHA, add concurrency control to cancel in-progress runs on rapid pushes - release-please.yml: Pin action by SHA, add 10-minute timeout - conftest.py: Fix snapshot_base_time to use explicit UTC timezone for consistent behavior across CI and local environments Regenerate SVG snapshots with UTC-aware timestamps. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix: add mypy command to permissions in settings.local.json * test: add comprehensive script tests with coroutine warning fixes - Add tests/scripts/ with tests for collect_companion, collect_repeater, and render scripts (1135 tests total, 96% coverage) - Fix unawaited coroutine warnings by using AsyncMock properly for async functions and async_context_manager_factory fixture for context managers - Add --cov=scripts to CI workflow and pyproject.toml coverage config - Omit scripts/generate_snapshots.py from coverage (dev utility) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * docs: migrate claude setup to codex skills * feat: migrate dependencies to uv (#31) * fix: run tests through uv * test: fix ruff lint issues in tests Consolidate patch context managers and clean unused imports/variables Use datetime.UTC in snapshot fixtures * test: avoid unawaited async mocks in entrypoint tests * ci: replace codecov with github coverage artifacts Add junit XML output and coverage summary in job output Upload HTML and XML coverage artifacts (3.12 only) on every run --------- Co-authored-by: Claude Opus 4.5 --- .claude/agents/frontend-expert.md | 83 - .claude/agents/python-code-reviewer.md | 103 - .claude/agents/test-engineer.md | 140 + .claude/settings.local.json | 20 - .codex/skills/frontend-expert/SKILL.md | 87 + .codex/skills/python-code-reviewer/SKILL.md | 52 + .codex/skills/test-engineer/SKILL.md | 83 + .dockerignore | 2 +- .github/workflows/release-please.yml | 3 +- .github/workflows/test.yml | 114 + .gitignore | 6 + CLAUDE.md => AGENTS.md | 119 +- Dockerfile | 9 +- README.md | 5 +- pyproject.toml | 84 + requirements.txt | 5 - scripts/collect_companion.py | 4 +- scripts/collect_repeater.py | 19 +- scripts/generate_snapshots.py | 357 ++ scripts/render_charts.py | 2 +- scripts/render_reports.py | 37 +- scripts/render_site.py | 4 +- src/meshmon/charts.py | 123 +- src/meshmon/db.py | 34 +- src/meshmon/env.py | 67 +- src/meshmon/formatters.py | 20 +- src/meshmon/html.py | 94 +- src/meshmon/log.py | 1 + src/meshmon/meshcore_client.py | 38 +- src/meshmon/metrics.py | 5 +- src/meshmon/reports.py | 85 +- src/meshmon/retry.py | 9 +- src/meshmon/telemetry.py | 9 +- test_review/tests.md | 4329 +++++++++++++++++ tests/__init__.py | 1 + tests/charts/__init__.py | 1 + tests/charts/conftest.py | 339 ++ tests/charts/test_chart_io.py | 202 + tests/charts/test_chart_render.py | 418 ++ tests/charts/test_statistics.py | 183 + tests/charts/test_timeseries.py | 185 + tests/charts/test_transforms.py | 207 + tests/client/__init__.py | 1 + tests/client/conftest.py | 99 + tests/client/test_connect.py | 440 ++ tests/client/test_contacts.py | 275 ++ tests/client/test_meshcore_available.py | 218 + tests/client/test_run_command.py | 228 + tests/config/__init__.py | 1 + tests/config/conftest.py | 49 + tests/config/test_config_file.py | 248 + tests/config/test_env.py | 211 + tests/conftest.py | 168 + tests/database/__init__.py | 1 + tests/database/conftest.py | 59 + tests/database/test_db_init.py | 179 + tests/database/test_db_insert.py | 207 + tests/database/test_db_maintenance.py | 195 + tests/database/test_db_migrations.py | 331 ++ tests/database/test_db_queries.py | 312 ++ tests/database/test_db_validation.py | 210 + tests/html/__init__.py | 1 + tests/html/conftest.py | 74 + tests/html/test_jinja_env.py | 155 + tests/html/test_metrics_builders.py | 189 + tests/html/test_page_context.py | 224 + tests/html/test_reports_index.py | 98 + tests/html/test_write_site.py | 195 + tests/integration/__init__.py | 1 + tests/integration/conftest.py | 111 + tests/integration/test_collection_pipeline.py | 177 + tests/integration/test_rendering_pipeline.py | 268 + tests/integration/test_reports_pipeline.py | 325 ++ tests/reports/__init__.py | 1 + tests/reports/conftest.py | 111 + tests/reports/test_aggregation.py | 184 + tests/reports/test_aggregation_helpers.py | 388 ++ tests/reports/test_counter_total.py | 152 + tests/reports/test_format_json.py | 256 + tests/reports/test_format_txt.py | 626 +++ tests/reports/test_location.py | 189 + tests/reports/test_snapshots.py | 557 +++ tests/reports/test_table_builders.py | 310 ++ tests/retry/__init__.py | 1 + tests/retry/conftest.py | 66 + tests/retry/test_circuit_breaker.py | 314 ++ tests/retry/test_get_circuit_breaker.py | 63 + tests/retry/test_with_retries.py | 342 ++ tests/scripts/__init__.py | 1 + tests/scripts/conftest.py | 189 + tests/scripts/test_collect_companion.py | 641 +++ tests/scripts/test_collect_repeater.py | 867 ++++ tests/scripts/test_render_scripts.py | 632 +++ tests/scripts/test_smoke.py | 121 + tests/snapshots/__init__.py | 14 + tests/snapshots/conftest.py | 100 + tests/snapshots/svg/.gitkeep | 14 + tests/snapshots/svg/bat_day_dark.svg | 633 +++ tests/snapshots/svg/bat_day_light.svg | 633 +++ tests/snapshots/svg/empty_day_dark.svg | 697 +++ tests/snapshots/svg/empty_day_light.svg | 697 +++ tests/snapshots/svg/nb_recv_day_dark.svg | 644 +++ tests/snapshots/svg/nb_recv_day_light.svg | 644 +++ .../snapshots/svg/single_point_day_light.svg | 586 +++ tests/snapshots/txt/.gitkeep | 13 + tests/snapshots/txt/empty_monthly_report.txt | 12 + tests/snapshots/txt/empty_yearly_report.txt | 11 + .../txt/monthly_report_companion.txt | 17 + .../snapshots/txt/monthly_report_repeater.txt | 17 + .../snapshots/txt/yearly_report_companion.txt | 14 + .../snapshots/txt/yearly_report_repeater.txt | 14 + tests/unit/__init__.py | 1 + tests/unit/test_battery.py | 155 + tests/unit/test_charts_helpers.py | 469 ++ tests/unit/test_env_parsing.py | 299 ++ tests/unit/test_formatters.py | 317 ++ tests/unit/test_html_builders.py | 386 ++ tests/unit/test_html_formatters.py | 285 ++ tests/unit/test_log.py | 175 + tests/unit/test_metrics.py | 251 + tests/unit/test_reports_formatting.py | 441 ++ tests/unit/test_telemetry.py | 282 ++ tests/utils/__init__.py | 1 + tests/utils/data_generators.py | 113 + uv.lock | 1569 ++++++ 125 files changed, 28005 insertions(+), 448 deletions(-) delete mode 100644 .claude/agents/frontend-expert.md delete mode 100644 .claude/agents/python-code-reviewer.md create mode 100644 .claude/agents/test-engineer.md delete mode 100644 .claude/settings.local.json create mode 100644 .codex/skills/frontend-expert/SKILL.md create mode 100644 .codex/skills/python-code-reviewer/SKILL.md create mode 100644 .codex/skills/test-engineer/SKILL.md create mode 100644 .github/workflows/test.yml rename CLAUDE.md => AGENTS.md (90%) create mode 100644 pyproject.toml delete mode 100644 requirements.txt create mode 100644 scripts/generate_snapshots.py create mode 100644 test_review/tests.md create mode 100644 tests/__init__.py create mode 100644 tests/charts/__init__.py create mode 100644 tests/charts/conftest.py create mode 100644 tests/charts/test_chart_io.py create mode 100644 tests/charts/test_chart_render.py create mode 100644 tests/charts/test_statistics.py create mode 100644 tests/charts/test_timeseries.py create mode 100644 tests/charts/test_transforms.py create mode 100644 tests/client/__init__.py create mode 100644 tests/client/conftest.py create mode 100644 tests/client/test_connect.py create mode 100644 tests/client/test_contacts.py create mode 100644 tests/client/test_meshcore_available.py create mode 100644 tests/client/test_run_command.py create mode 100644 tests/config/__init__.py create mode 100644 tests/config/conftest.py create mode 100644 tests/config/test_config_file.py create mode 100644 tests/config/test_env.py create mode 100644 tests/conftest.py create mode 100644 tests/database/__init__.py create mode 100644 tests/database/conftest.py create mode 100644 tests/database/test_db_init.py create mode 100644 tests/database/test_db_insert.py create mode 100644 tests/database/test_db_maintenance.py create mode 100644 tests/database/test_db_migrations.py create mode 100644 tests/database/test_db_queries.py create mode 100644 tests/database/test_db_validation.py create mode 100644 tests/html/__init__.py create mode 100644 tests/html/conftest.py create mode 100644 tests/html/test_jinja_env.py create mode 100644 tests/html/test_metrics_builders.py create mode 100644 tests/html/test_page_context.py create mode 100644 tests/html/test_reports_index.py create mode 100644 tests/html/test_write_site.py create mode 100644 tests/integration/__init__.py create mode 100644 tests/integration/conftest.py create mode 100644 tests/integration/test_collection_pipeline.py create mode 100644 tests/integration/test_rendering_pipeline.py create mode 100644 tests/integration/test_reports_pipeline.py create mode 100644 tests/reports/__init__.py create mode 100644 tests/reports/conftest.py create mode 100644 tests/reports/test_aggregation.py create mode 100644 tests/reports/test_aggregation_helpers.py create mode 100644 tests/reports/test_counter_total.py create mode 100644 tests/reports/test_format_json.py create mode 100644 tests/reports/test_format_txt.py create mode 100644 tests/reports/test_location.py create mode 100644 tests/reports/test_snapshots.py create mode 100644 tests/reports/test_table_builders.py create mode 100644 tests/retry/__init__.py create mode 100644 tests/retry/conftest.py create mode 100644 tests/retry/test_circuit_breaker.py create mode 100644 tests/retry/test_get_circuit_breaker.py create mode 100644 tests/retry/test_with_retries.py create mode 100644 tests/scripts/__init__.py create mode 100644 tests/scripts/conftest.py create mode 100644 tests/scripts/test_collect_companion.py create mode 100644 tests/scripts/test_collect_repeater.py create mode 100644 tests/scripts/test_render_scripts.py create mode 100644 tests/scripts/test_smoke.py create mode 100644 tests/snapshots/__init__.py create mode 100644 tests/snapshots/conftest.py create mode 100644 tests/snapshots/svg/.gitkeep create mode 100644 tests/snapshots/svg/bat_day_dark.svg create mode 100644 tests/snapshots/svg/bat_day_light.svg create mode 100644 tests/snapshots/svg/empty_day_dark.svg create mode 100644 tests/snapshots/svg/empty_day_light.svg create mode 100644 tests/snapshots/svg/nb_recv_day_dark.svg create mode 100644 tests/snapshots/svg/nb_recv_day_light.svg create mode 100644 tests/snapshots/svg/single_point_day_light.svg create mode 100644 tests/snapshots/txt/.gitkeep create mode 100644 tests/snapshots/txt/empty_monthly_report.txt create mode 100644 tests/snapshots/txt/empty_yearly_report.txt create mode 100644 tests/snapshots/txt/monthly_report_companion.txt create mode 100644 tests/snapshots/txt/monthly_report_repeater.txt create mode 100644 tests/snapshots/txt/yearly_report_companion.txt create mode 100644 tests/snapshots/txt/yearly_report_repeater.txt create mode 100644 tests/unit/__init__.py create mode 100644 tests/unit/test_battery.py create mode 100644 tests/unit/test_charts_helpers.py create mode 100644 tests/unit/test_env_parsing.py create mode 100644 tests/unit/test_formatters.py create mode 100644 tests/unit/test_html_builders.py create mode 100644 tests/unit/test_html_formatters.py create mode 100644 tests/unit/test_log.py create mode 100644 tests/unit/test_metrics.py create mode 100644 tests/unit/test_reports_formatting.py create mode 100644 tests/unit/test_telemetry.py create mode 100644 tests/utils/__init__.py create mode 100644 tests/utils/data_generators.py create mode 100644 uv.lock diff --git a/.claude/agents/frontend-expert.md b/.claude/agents/frontend-expert.md deleted file mode 100644 index ea8759a..0000000 --- a/.claude/agents/frontend-expert.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -name: frontend-expert -description: Use this agent when working on frontend development tasks including HTML structure, CSS styling, JavaScript interactions, accessibility compliance, UI/UX design decisions, responsive layouts, or component architecture. This agent should be engaged for reviewing frontend code quality, implementing new UI features, fixing accessibility issues, or optimizing user interfaces.\n\nExamples:\n\n\nContext: User asks to create a new HTML page or component\nuser: "Create a navigation menu for the dashboard"\nassistant: "I'll use the frontend-expert agent to design and implement an accessible, well-structured navigation menu."\n\n\n\n\nContext: User has written frontend code that needs review\nuser: "I just added this form to the page, can you check it?"\nassistant: "Let me use the frontend-expert agent to review your form for accessibility, semantic HTML, and UI best practices."\n\n\n\n\nContext: User needs help with CSS or responsive design\nuser: "The charts on the dashboard look bad on mobile"\nassistant: "I'll engage the frontend-expert agent to analyze and fix the responsive layout issues for the charts."\n\n\n\n\nContext: Proactive use after implementing UI changes\nassistant: "I've added the new status indicators to the HTML template. Now let me use the frontend-expert agent to verify the accessibility and semantic correctness of these changes."\n\n -model: opus ---- - -You are a senior frontend development expert with deep expertise in web standards, accessibility, and user interface design. You have comprehensive knowledge spanning HTML5 semantics, CSS architecture, JavaScript patterns, WCAG accessibility guidelines, and modern UI/UX principles. - -## Core Expertise Areas - -### Semantic HTML -- You enforce proper document structure with appropriate landmark elements (`
`, `