Merge pull request #34 from ElVit/desk/unit-tests
Added Unit Tests for Maidesite Desk
This commit is contained in:
commit
e3b26cc7c6
|
|
@ -0,0 +1,188 @@
|
||||||
|
name: Test Maidesite Desk Component
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: read
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- 'desk**'
|
||||||
|
paths:
|
||||||
|
- 'components/maidesite_desk/**'
|
||||||
|
- 'tests/maidesite_desk/**'
|
||||||
|
- '.github/workflows/test_maidesite_desk.yml'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- 'components/maidesite_desk/**'
|
||||||
|
- 'tests/maidesite_desk/**'
|
||||||
|
- '.github/workflows/test_maidesite_desk.yml'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-build:
|
||||||
|
name: Test ESPHome Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
board: [esp32s2, esp32c3, full]
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build ESPHome firmware for ${{ matrix.board }}
|
||||||
|
uses: esphome/build-action@v7
|
||||||
|
with:
|
||||||
|
yaml-file: tests/maidesite_desk/test_maidesite_desk_${{ matrix.board == 'full' && 'full' || matrix.board }}.yaml
|
||||||
|
version: latest
|
||||||
|
|
||||||
|
lint-code:
|
||||||
|
name: Lint C++ Code
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install clang-format
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y clang-format
|
||||||
|
|
||||||
|
- name: Run clang-format check
|
||||||
|
run: |
|
||||||
|
find components/maidesite_desk -name '*.cpp' -o -name '*.h' | \
|
||||||
|
xargs clang-format -style=file --dry-run --Werror
|
||||||
|
|
||||||
|
lint-python:
|
||||||
|
name: Lint Python Code
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Install linting tools
|
||||||
|
run: |
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install flake8 pylint black
|
||||||
|
|
||||||
|
- name: Run black check
|
||||||
|
run: |
|
||||||
|
black --check components/maidesite_desk/
|
||||||
|
|
||||||
|
- name: Run flake8
|
||||||
|
run: |
|
||||||
|
flake8 components/maidesite_desk/ --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||||
|
flake8 components/maidesite_desk/ --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||||
|
|
||||||
|
test-python:
|
||||||
|
name: Python Unit Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install pytest pytest-cov esphome
|
||||||
|
|
||||||
|
- name: Run Python unit tests
|
||||||
|
run: |
|
||||||
|
pytest tests/maidesite_desk/ -v --cov=components/maidesite_desk --cov-report=xml --cov-report=term
|
||||||
|
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
uses: codecov/codecov-action@v4
|
||||||
|
with:
|
||||||
|
file: ./coverage.xml
|
||||||
|
flags: unittests
|
||||||
|
name: codecov-maidesite-desk
|
||||||
|
fail_ci_if_error: false
|
||||||
|
|
||||||
|
test-minimal-config:
|
||||||
|
name: Test Minimal Configuration
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Install ESPHome
|
||||||
|
run: |
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install esphome
|
||||||
|
|
||||||
|
- name: Validate minimal config
|
||||||
|
run: |
|
||||||
|
esphome config tests/maidesite_desk/test_maidesite_desk_mini.yaml
|
||||||
|
|
||||||
|
test-full-config:
|
||||||
|
name: Test Full Configuration
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Install ESPHome
|
||||||
|
run: |
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install esphome
|
||||||
|
|
||||||
|
- name: Validate full config
|
||||||
|
run: |
|
||||||
|
esphome config tests/maidesite_desk/test_maidesite_desk_full.yaml
|
||||||
|
|
||||||
|
summary:
|
||||||
|
name: Test Summary
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
needs: [test-build, lint-code, lint-python, test-python, test-minimal-config, test-full-config]
|
||||||
|
if: always()
|
||||||
|
steps:
|
||||||
|
- name: Check test results
|
||||||
|
run: |
|
||||||
|
echo "Test Build: ${{ needs.test-build.result }}"
|
||||||
|
echo "Lint C++: ${{ needs.lint-code.result }}"
|
||||||
|
echo "Lint Python: ${{ needs.lint-python.result }}"
|
||||||
|
echo "Python Unit Tests: ${{ needs.test-python.result }}"
|
||||||
|
echo "Minimal Config: ${{ needs.test-minimal-config.result }}"
|
||||||
|
echo "Full Config: ${{ needs.test-full-config.result }}"
|
||||||
|
|
||||||
|
if [ "${{ needs.test-build.result }}" != "success" ] || \
|
||||||
|
[ "${{ needs.lint-code.result }}" != "success" ] || \
|
||||||
|
[ "${{ needs.lint-python.result }}" != "success" ] || \
|
||||||
|
[ "${{ needs.test-python.result }}" != "success" ] || \
|
||||||
|
[ "${{ needs.test-minimal-config.result }}" != "success" ] || \
|
||||||
|
[ "${{ needs.test-full-config.result }}" != "success" ]; then
|
||||||
|
echo "One or more tests failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "All tests passed successfully!"
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import pins
|
|
||||||
from esphome.components import uart
|
from esphome.components import uart
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,17 +98,25 @@ void MaidesiteDeskComponent::decode_response(std::vector<uint8_t> message) {
|
||||||
if (new_height == this->current_height_)
|
if (new_height == this->current_height_)
|
||||||
return;
|
return;
|
||||||
this->current_height_ = new_height;
|
this->current_height_ = new_height;
|
||||||
|
#ifdef USE_SENSOR
|
||||||
if (this->height_abs_sensor_ != nullptr)
|
if (this->height_abs_sensor_ != nullptr)
|
||||||
this->height_abs_sensor_->publish_state(this->current_height_);
|
this->height_abs_sensor_->publish_state(this->current_height_);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_NUMBER
|
||||||
if (this->height_abs_number_ != nullptr)
|
if (this->height_abs_number_ != nullptr)
|
||||||
this->height_abs_number_->publish_state(this->current_height_);
|
this->height_abs_number_->publish_state(this->current_height_);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_SENSOR
|
||||||
if (this->height_pct_sensor_ != nullptr && limit_max_ != 0)
|
if (this->height_pct_sensor_ != nullptr && limit_max_ != 0)
|
||||||
this->height_pct_sensor_->publish_state((this->current_height_ - this->limit_min_) /
|
this->height_pct_sensor_->publish_state((this->current_height_ - this->limit_min_) /
|
||||||
(this->limit_max_ - this->limit_min_) * 100);
|
(this->limit_max_ - this->limit_min_) * 100);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_NUMBER
|
||||||
if (this->height_pct_number_ != nullptr && limit_max_ != 0)
|
if (this->height_pct_number_ != nullptr && limit_max_ != 0)
|
||||||
this->height_pct_number_->publish_state(
|
this->height_pct_number_->publish_state(
|
||||||
roundf((this->current_height_ - this->limit_min_) / (this->limit_max_ - this->limit_min_) * 1000) / 10);
|
roundf((this->current_height_ - this->limit_min_) / (this->limit_max_ - this->limit_min_) * 1000) / 10);
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x20:
|
case 0x20:
|
||||||
|
|
@ -117,17 +125,25 @@ void MaidesiteDeskComponent::decode_response(std::vector<uint8_t> message) {
|
||||||
|
|
||||||
if ((message[4] & 1) == 0) { // low nibble 0 -> no max limit, use physical_max_
|
if ((message[4] & 1) == 0) { // low nibble 0 -> no max limit, use physical_max_
|
||||||
this->limit_max_ = this->physical_max_;
|
this->limit_max_ = this->physical_max_;
|
||||||
|
#ifdef USE_SENSOR
|
||||||
if (this->height_max_sensor_ != nullptr)
|
if (this->height_max_sensor_ != nullptr)
|
||||||
this->height_max_sensor_->publish_state(this->limit_max_);
|
this->height_max_sensor_->publish_state(this->limit_max_);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_NUMBER
|
||||||
if (height_abs_number_ != nullptr)
|
if (height_abs_number_ != nullptr)
|
||||||
this->height_abs_number_->traits.set_max_value(this->limit_max_);
|
this->height_abs_number_->traits.set_max_value(this->limit_max_);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
if ((message[4] >> 4) == 0) { // high nibble 0 -> no min limit, use physical_min_
|
if ((message[4] >> 4) == 0) { // high nibble 0 -> no min limit, use physical_min_
|
||||||
this->limit_min_ = this->physical_min_;
|
this->limit_min_ = this->physical_min_;
|
||||||
|
#ifdef USE_SENSOR
|
||||||
if (this->height_min_sensor_ != nullptr)
|
if (this->height_min_sensor_ != nullptr)
|
||||||
this->height_min_sensor_->publish_state(limit_min_);
|
this->height_min_sensor_->publish_state(limit_min_);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_NUMBER
|
||||||
if (height_abs_number_ != nullptr)
|
if (height_abs_number_ != nullptr)
|
||||||
this->height_abs_number_->traits.set_min_value(limit_min_);
|
this->height_abs_number_->traits.set_min_value(limit_min_);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -145,10 +161,14 @@ void MaidesiteDeskComponent::decode_response(std::vector<uint8_t> message) {
|
||||||
delay(10);
|
delay(10);
|
||||||
|
|
||||||
this->limit_max_ = this->byte2float(message[4], message[5]);
|
this->limit_max_ = this->byte2float(message[4], message[5]);
|
||||||
|
#ifdef USE_SENSOR
|
||||||
if (this->height_max_sensor_ != nullptr)
|
if (this->height_max_sensor_ != nullptr)
|
||||||
this->height_max_sensor_->publish_state(limit_max_);
|
this->height_max_sensor_->publish_state(limit_max_);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_NUMBER
|
||||||
if (height_abs_number_ != nullptr)
|
if (height_abs_number_ != nullptr)
|
||||||
this->height_abs_number_->traits.set_max_value(limit_max_);
|
this->height_abs_number_->traits.set_max_value(limit_max_);
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x22:
|
case 0x22:
|
||||||
|
|
@ -156,42 +176,54 @@ void MaidesiteDeskComponent::decode_response(std::vector<uint8_t> message) {
|
||||||
delay(10);
|
delay(10);
|
||||||
|
|
||||||
this->limit_min_ = this->byte2float(message[4], message[5]);
|
this->limit_min_ = this->byte2float(message[4], message[5]);
|
||||||
|
#ifdef USE_SENSOR
|
||||||
if (this->height_min_sensor_ != nullptr)
|
if (this->height_min_sensor_ != nullptr)
|
||||||
this->height_min_sensor_->publish_state(limit_min_);
|
this->height_min_sensor_->publish_state(limit_min_);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_NUMBER
|
||||||
if (height_abs_number_ != nullptr)
|
if (height_abs_number_ != nullptr)
|
||||||
this->height_abs_number_->traits.set_min_value(limit_min_);
|
this->height_abs_number_->traits.set_min_value(limit_min_);
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x25:
|
case 0x25:
|
||||||
ESP_LOGI(TAG, "Set position m1 to 0x%02X%02X", message[4], message[5]);
|
ESP_LOGI(TAG, "Set position m1 to 0x%02X%02X", message[4], message[5]);
|
||||||
delay(10);
|
delay(10);
|
||||||
|
|
||||||
|
#ifdef USE_SENSOR
|
||||||
if (this->position_m1_sensor_ != nullptr)
|
if (this->position_m1_sensor_ != nullptr)
|
||||||
this->position_m1_sensor_->publish_state(this->byte2float(message[4], message[5]));
|
this->position_m1_sensor_->publish_state(this->byte2float(message[4], message[5]));
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x26:
|
case 0x26:
|
||||||
ESP_LOGI(TAG, "Set position m2 to 0x%02X%02X", message[4], message[5]);
|
ESP_LOGI(TAG, "Set position m2 to 0x%02X%02X", message[4], message[5]);
|
||||||
delay(10);
|
delay(10);
|
||||||
|
|
||||||
|
#ifdef USE_SENSOR
|
||||||
if (this->position_m2_sensor_ != nullptr)
|
if (this->position_m2_sensor_ != nullptr)
|
||||||
this->position_m2_sensor_->publish_state(this->byte2float(message[4], message[5]));
|
this->position_m2_sensor_->publish_state(this->byte2float(message[4], message[5]));
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x27:
|
case 0x27:
|
||||||
ESP_LOGI(TAG, "Set position m3 to 0x%02X%02X", message[4], message[5]);
|
ESP_LOGI(TAG, "Set position m3 to 0x%02X%02X", message[4], message[5]);
|
||||||
delay(10);
|
delay(10);
|
||||||
|
|
||||||
|
#ifdef USE_SENSOR
|
||||||
if (this->position_m3_sensor_ != nullptr)
|
if (this->position_m3_sensor_ != nullptr)
|
||||||
this->position_m3_sensor_->publish_state(this->byte2float(message[4], message[5]));
|
this->position_m3_sensor_->publish_state(this->byte2float(message[4], message[5]));
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x28:
|
case 0x28:
|
||||||
ESP_LOGI(TAG, "Set position m4 to 0x%02X%02X", message[4], message[5]);
|
ESP_LOGI(TAG, "Set position m4 to 0x%02X%02X", message[4], message[5]);
|
||||||
delay(10);
|
delay(10);
|
||||||
|
|
||||||
|
#ifdef USE_SENSOR
|
||||||
if (this->position_m4_sensor_ != nullptr)
|
if (this->position_m4_sensor_ != nullptr)
|
||||||
this->position_m4_sensor_->publish_state(this->byte2float(message[4], message[5]));
|
this->position_m4_sensor_->publish_state(this->byte2float(message[4], message[5]));
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// case 0x0E:
|
// case 0x0E:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
# Testing Documentation for Maidesite Desk Component
|
||||||
|
|
||||||
|
This directory contains comprehensive tests for the `maidesite_desk` ESPHome component.
|
||||||
|
|
||||||
|
## Test Structure
|
||||||
|
|
||||||
|
### Unit Tests (`test_maidesite_desk_unit.py`)
|
||||||
|
|
||||||
|
- Tests Python configuration validation
|
||||||
|
- Tests component metadata (CODEOWNERS, DEPENDENCIES, etc.)
|
||||||
|
- Tests configuration schema structure
|
||||||
|
- Tests all platform imports (sensor, number, button)
|
||||||
|
- Tests code generation logic
|
||||||
|
|
||||||
|
### Integration Tests (`test_maidesite_desk_integration.py`)
|
||||||
|
|
||||||
|
- Tests minimal feature configuration on ESP32 board
|
||||||
|
- Tests full feature configuration on ESP32 board
|
||||||
|
- Tests configuration on ESP32-S2 board
|
||||||
|
- Tests configuration on ESP32-C3 board
|
||||||
|
|
||||||
|
### Test Configuration Files
|
||||||
|
|
||||||
|
- `test_maidesite_desk_esp32s2.yaml` - ESP32-S2 Wemos S2 Mini
|
||||||
|
- `test_maidesite_desk_esp32c3.yaml` - ESP32-C3 mini (RISC-V)
|
||||||
|
- `test_maidesite_desk_mini.yaml` - Minimal ESP32 configuration for basic testing
|
||||||
|
- `test_maidesite_desk_full.yaml` - Comprehensive ESP32 configuration with all features
|
||||||
|
|
||||||
|
## Running Tests Locally
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r ../requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run All Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pytest tests/maidesite_desk/ -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Only Unit Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pytest tests/maidesite_desk/test_maidesite_desk_unit.py -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Only Integration Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pytest tests/maidesite_desk/test_maidesite_desk_integration.py -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run with Coverage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pytest tests/maidesite_desk/ --cov=components.maidesite_desk --cov-report=html
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Coverage
|
||||||
|
|
||||||
|
The test suite covers:
|
||||||
|
|
||||||
|
- Component configuration validation
|
||||||
|
- All sensor entities (height_abs, height_pct, height_min, height_max, position_m1-m4)
|
||||||
|
- All number entities (height_abs, height_pct)
|
||||||
|
- All button entities (stop, step_up, step_down, goto_max, goto_min, goto_m1-m4, save_m1-m4)
|
||||||
|
- Multiple ESP32 board variants (ESP32, ESP32-S2, ESP32-C3)
|
||||||
|
- Minimal and full configuration scenarios
|
||||||
|
|
||||||
|
## CI/CD Integration
|
||||||
|
|
||||||
|
These tests are designed to run in GitHub Actions CI/CD pipelines. See `.github/workflows/test_maidesite_desk.yml` for the workflow configuration.
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
---
|
||||||
|
# Test configuration for maidesite_desk component on ESP32-C3
|
||||||
|
# ESP32-C3 is a compact chip with limited GPIO pins
|
||||||
|
|
||||||
|
esp32:
|
||||||
|
board: esp32-c3-devkitm-1
|
||||||
|
variant: esp32c3
|
||||||
|
framework:
|
||||||
|
type: esp-idf
|
||||||
|
|
||||||
|
esphome:
|
||||||
|
name: test-maidesite-desk-esp32c3
|
||||||
|
friendly_name: "Test Maidesite Desk ESP32-C3"
|
||||||
|
|
||||||
|
logger:
|
||||||
|
level: DEBUG
|
||||||
|
|
||||||
|
external_components:
|
||||||
|
- source:
|
||||||
|
type: local
|
||||||
|
path: ../../components
|
||||||
|
components: [maidesite_desk]
|
||||||
|
|
||||||
|
uart:
|
||||||
|
- id: uart_desk
|
||||||
|
tx_pin: GPIO3
|
||||||
|
rx_pin: GPIO4
|
||||||
|
baud_rate: 9600
|
||||||
|
|
||||||
|
# Test basic configuration
|
||||||
|
maidesite_desk:
|
||||||
|
id: my_desk
|
||||||
|
uart_id: uart_desk
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
- platform: maidesite_desk
|
||||||
|
height_abs:
|
||||||
|
name: "Height Absolute"
|
||||||
|
|
||||||
|
button:
|
||||||
|
- platform: maidesite_desk
|
||||||
|
stop:
|
||||||
|
name: "Stop Movement"
|
||||||
|
step_up:
|
||||||
|
name: "Step Up"
|
||||||
|
step_down:
|
||||||
|
name: "Step Down"
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
---
|
||||||
|
# Test configuration for maidesite_desk component on ESP32-S2
|
||||||
|
# ESP32-S2 has GPIO pins but limited compared to ESP32
|
||||||
|
|
||||||
|
esp32:
|
||||||
|
board: lolin_s2_mini
|
||||||
|
variant: esp32s2
|
||||||
|
framework:
|
||||||
|
type: esp-idf
|
||||||
|
|
||||||
|
esphome:
|
||||||
|
name: test-maidesite-desk-esp32s2
|
||||||
|
friendly_name: "Test Maidesite Desk ESP32-S2"
|
||||||
|
|
||||||
|
logger:
|
||||||
|
level: DEBUG
|
||||||
|
|
||||||
|
external_components:
|
||||||
|
- source:
|
||||||
|
type: local
|
||||||
|
path: ../../components
|
||||||
|
components: [maidesite_desk]
|
||||||
|
|
||||||
|
uart:
|
||||||
|
- id: uart_desk
|
||||||
|
tx_pin: GPIO3
|
||||||
|
rx_pin: GPIO4
|
||||||
|
baud_rate: 9600
|
||||||
|
|
||||||
|
# Test basic configuration
|
||||||
|
maidesite_desk:
|
||||||
|
id: my_desk
|
||||||
|
uart_id: uart_desk
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
- platform: maidesite_desk
|
||||||
|
height_abs:
|
||||||
|
name: "Height Absolute"
|
||||||
|
|
||||||
|
button:
|
||||||
|
- platform: maidesite_desk
|
||||||
|
stop:
|
||||||
|
name: "Stop Movement"
|
||||||
|
step_up:
|
||||||
|
name: "Step Up"
|
||||||
|
step_down:
|
||||||
|
name: "Step Down"
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
---
|
||||||
|
# Test configuration for maidesite_desk component
|
||||||
|
# This file validates all component features and configurations
|
||||||
|
|
||||||
|
esp32:
|
||||||
|
board: esp32dev
|
||||||
|
framework:
|
||||||
|
type: esp-idf
|
||||||
|
|
||||||
|
esphome:
|
||||||
|
name: test-maidesite-desk-full
|
||||||
|
friendly_name: "Test Maidesite Desk Full"
|
||||||
|
|
||||||
|
logger:
|
||||||
|
level: DEBUG
|
||||||
|
|
||||||
|
external_components:
|
||||||
|
- source:
|
||||||
|
type: local
|
||||||
|
path: ../../components
|
||||||
|
components: [maidesite_desk]
|
||||||
|
|
||||||
|
uart:
|
||||||
|
- id: uart_desk
|
||||||
|
tx_pin: GPIO3
|
||||||
|
rx_pin: GPIO4
|
||||||
|
baud_rate: 9600
|
||||||
|
|
||||||
|
# Test basic configuration
|
||||||
|
maidesite_desk:
|
||||||
|
id: my_desk
|
||||||
|
uart_id: uart_desk
|
||||||
|
log_uart_msg: true
|
||||||
|
|
||||||
|
# Test all sensor types
|
||||||
|
sensor:
|
||||||
|
- platform: maidesite_desk
|
||||||
|
height_abs:
|
||||||
|
name: "Height Absolute"
|
||||||
|
height_pct:
|
||||||
|
name: "Height Percent"
|
||||||
|
height_min:
|
||||||
|
name: "Height Minimum"
|
||||||
|
height_max:
|
||||||
|
name: "Height Maximum"
|
||||||
|
position_m1:
|
||||||
|
name: "Memory Position M1"
|
||||||
|
position_m2:
|
||||||
|
name: "Memory Position M2"
|
||||||
|
position_m3:
|
||||||
|
name: "Memory Position M3"
|
||||||
|
position_m4:
|
||||||
|
name: "Memory Position M4"
|
||||||
|
|
||||||
|
# Test all number controls
|
||||||
|
number:
|
||||||
|
- platform: maidesite_desk
|
||||||
|
height_abs:
|
||||||
|
name: "Height Absolute Control"
|
||||||
|
mode: SLIDER
|
||||||
|
height_pct:
|
||||||
|
name: "Height Percent Control"
|
||||||
|
mode: SLIDER
|
||||||
|
|
||||||
|
# Test all button controls
|
||||||
|
button:
|
||||||
|
- platform: maidesite_desk
|
||||||
|
stop:
|
||||||
|
name: "Stop Movement"
|
||||||
|
step_up:
|
||||||
|
name: "Step Up"
|
||||||
|
step_down:
|
||||||
|
name: "Step Down"
|
||||||
|
goto_max:
|
||||||
|
name: "Go to Maximum Height"
|
||||||
|
goto_min:
|
||||||
|
name: "Go to Minimum Height"
|
||||||
|
goto_m1:
|
||||||
|
name: "Go to Memory 1"
|
||||||
|
goto_m2:
|
||||||
|
name: "Go to Memory 2"
|
||||||
|
goto_m3:
|
||||||
|
name: "Go to Memory 3"
|
||||||
|
goto_m4:
|
||||||
|
name: "Go to Memory 4"
|
||||||
|
save_m1:
|
||||||
|
name: "Save Memory 1"
|
||||||
|
save_m2:
|
||||||
|
name: "Save Memory 2"
|
||||||
|
save_m3:
|
||||||
|
name: "Save Memory 3"
|
||||||
|
save_m4:
|
||||||
|
name: "Save Memory 4"
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
"""
|
||||||
|
Integration tests for maidesite_desk component.
|
||||||
|
|
||||||
|
Tests the component's behavior with actual ESPHome compilation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class TestMaidesiteDeskIntegration:
|
||||||
|
"""Integration tests using ESPHome CLI."""
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_yaml_full(self):
|
||||||
|
"""Get the path to the full test YAML file."""
|
||||||
|
base_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
return os.path.join(
|
||||||
|
base_dir, "tests", "maidesite_desk", "test_maidesite_desk_full.yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_yaml_mini(self):
|
||||||
|
"""Get the path to the minimal test YAML file."""
|
||||||
|
base_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
return os.path.join(
|
||||||
|
base_dir, "tests", "maidesite_desk", "test_maidesite_desk_mini.yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_yaml_esp32s2(self):
|
||||||
|
"""Get the path to the ESP32-S2 test YAML file."""
|
||||||
|
base_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
return os.path.join(
|
||||||
|
base_dir,
|
||||||
|
"tests",
|
||||||
|
"maidesite_desk",
|
||||||
|
"test_maidesite_desk_esp32s2.yaml",
|
||||||
|
)
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_yaml_esp32c3(self):
|
||||||
|
"""Get the path to the ESP32-C3 test YAML file."""
|
||||||
|
base_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
return os.path.join(
|
||||||
|
base_dir,
|
||||||
|
"tests",
|
||||||
|
"maidesite_desk",
|
||||||
|
"test_maidesite_desk_esp32c3.yaml",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_validate_full_config(self, test_yaml_full: str):
|
||||||
|
"""Test that the full configuration is valid."""
|
||||||
|
if not os.path.exists(test_yaml_full):
|
||||||
|
pytest.skip(f"Test file not found: {test_yaml_full}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["esphome", "config", test_yaml_full],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=120,
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
result.returncode == 0
|
||||||
|
), f"Full config validation failed: {result.stderr}"
|
||||||
|
except FileNotFoundError:
|
||||||
|
pytest.skip("ESPHome not installed")
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
pytest.fail("ESPHome config validation timed out")
|
||||||
|
|
||||||
|
def test_validate_mini_config(self, test_yaml_mini: str):
|
||||||
|
"""Test that the minimal configuration is valid."""
|
||||||
|
if not os.path.exists(test_yaml_mini):
|
||||||
|
pytest.skip(f"Test file not found: {test_yaml_mini}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["esphome", "config", test_yaml_mini],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=120,
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
result.returncode == 0
|
||||||
|
), f"Mini config validation failed: {result.stderr}"
|
||||||
|
except FileNotFoundError:
|
||||||
|
pytest.skip("ESPHome not installed")
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
pytest.fail("ESPHome config validation timed out")
|
||||||
|
|
||||||
|
def test_validate_esp32s2_config(self, test_yaml_esp32s2: str):
|
||||||
|
"""Test that the ESP32-S2 configuration is valid."""
|
||||||
|
if not os.path.exists(test_yaml_esp32s2):
|
||||||
|
pytest.skip(f"Test file not found: {test_yaml_esp32s2}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["esphome", "config", test_yaml_esp32s2],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=120,
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
result.returncode == 0
|
||||||
|
), f"ESP32-S2 config validation failed: {result.stderr}"
|
||||||
|
except FileNotFoundError:
|
||||||
|
pytest.skip("ESPHome not installed")
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
pytest.fail("ESPHome config validation timed out")
|
||||||
|
|
||||||
|
def test_validate_esp32c3_config(self, test_yaml_esp32c3: str):
|
||||||
|
"""Test that the ESP32-C3 configuration is valid."""
|
||||||
|
if not os.path.exists(test_yaml_esp32c3):
|
||||||
|
pytest.skip(f"Test file not found: {test_yaml_esp32c3}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["esphome", "config", test_yaml_esp32c3],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=120,
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
result.returncode == 0
|
||||||
|
), f"ESP32-C3 config validation failed: {result.stderr}"
|
||||||
|
except FileNotFoundError:
|
||||||
|
pytest.skip("ESPHome not installed")
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
pytest.fail("ESPHome config validation timed out")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
pytest.main([__file__, "-v"])
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
---
|
||||||
|
# Test configuration for maidesite_desk component - minimal configuration
|
||||||
|
# Tests basic component functionality with minimal required settings
|
||||||
|
|
||||||
|
esp32:
|
||||||
|
board: esp32dev
|
||||||
|
framework:
|
||||||
|
type: esp-idf
|
||||||
|
|
||||||
|
esphome:
|
||||||
|
name: test-maidesite-desk-mini
|
||||||
|
friendly_name: "Test Maidesite Desk Minimal"
|
||||||
|
|
||||||
|
logger:
|
||||||
|
level: DEBUG
|
||||||
|
|
||||||
|
external_components:
|
||||||
|
- source:
|
||||||
|
type: local
|
||||||
|
path: ../../components
|
||||||
|
components: [maidesite_desk]
|
||||||
|
|
||||||
|
uart:
|
||||||
|
- id: uart_desk
|
||||||
|
tx_pin: GPIO3
|
||||||
|
rx_pin: GPIO4
|
||||||
|
baud_rate: 9600
|
||||||
|
|
||||||
|
# Test minimal configuration
|
||||||
|
maidesite_desk:
|
||||||
|
id: my_desk
|
||||||
|
uart_id: uart_desk
|
||||||
|
|
@ -0,0 +1,193 @@
|
||||||
|
"""
|
||||||
|
Unit tests for maidesite_desk component configuration validation.
|
||||||
|
|
||||||
|
Tests the Python configuration schema and validation logic.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
from esphome.core import CORE
|
||||||
|
|
||||||
|
|
||||||
|
class TestMaidesiteDeskConfig:
|
||||||
|
"""Test suite for maidesite_desk configuration validation."""
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def setup(self):
|
||||||
|
"""Setup test fixtures."""
|
||||||
|
# Mock CORE to avoid ESPHome initialization issues
|
||||||
|
CORE.data = {}
|
||||||
|
yield
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_uart(self):
|
||||||
|
"""Mock UART component."""
|
||||||
|
with patch("esphome.components.uart") as mock:
|
||||||
|
yield mock
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_cg(self):
|
||||||
|
"""Mock code generator."""
|
||||||
|
with patch("esphome.codegen") as mock:
|
||||||
|
mock.esphome_ns = MagicMock()
|
||||||
|
mock.esphome_ns.namespace.return_value.class_ = MagicMock()
|
||||||
|
yield mock
|
||||||
|
|
||||||
|
def test_import_component(self):
|
||||||
|
"""Test that the component can be imported."""
|
||||||
|
try:
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Add components directory to path
|
||||||
|
components_path = os.path.join(
|
||||||
|
os.path.dirname(__file__), "..", "..", "components"
|
||||||
|
)
|
||||||
|
sys.path.insert(0, components_path)
|
||||||
|
|
||||||
|
import maidesite_desk as md_init
|
||||||
|
|
||||||
|
assert md_init is not None
|
||||||
|
except ImportError as e:
|
||||||
|
pytest.fail(f"Failed to import maidesite_desk: {e}")
|
||||||
|
|
||||||
|
def test_component_metadata(self):
|
||||||
|
"""Test component metadata is correctly defined."""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
components_path = os.path.join(
|
||||||
|
os.path.dirname(__file__), "..", "..", "components"
|
||||||
|
)
|
||||||
|
sys.path.insert(0, components_path)
|
||||||
|
|
||||||
|
import maidesite_desk as md_init
|
||||||
|
|
||||||
|
assert hasattr(md_init, "CODEOWNERS")
|
||||||
|
assert md_init.CODEOWNERS == ["@elvit"]
|
||||||
|
|
||||||
|
assert hasattr(md_init, "MULTICONF")
|
||||||
|
assert md_init.MULTICONF is True
|
||||||
|
|
||||||
|
assert hasattr(md_init, "DEPENDENCIES")
|
||||||
|
assert "uart" in md_init.DEPENDENCIES
|
||||||
|
|
||||||
|
def test_config_constants(self):
|
||||||
|
"""Test that configuration constants are defined."""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
components_path = os.path.join(
|
||||||
|
os.path.dirname(__file__), "..", "..", "components"
|
||||||
|
)
|
||||||
|
sys.path.insert(0, components_path)
|
||||||
|
|
||||||
|
import maidesite_desk as md_init
|
||||||
|
|
||||||
|
assert hasattr(md_init, "CONF_MAIDESITE_DESK_ID")
|
||||||
|
assert md_init.CONF_MAIDESITE_DESK_ID == "maidesite_desk"
|
||||||
|
|
||||||
|
assert hasattr(md_init, "CONF_LOG_UART_MSG")
|
||||||
|
assert md_init.CONF_LOG_UART_MSG == "log_uart_msg"
|
||||||
|
|
||||||
|
@patch("esphome.components.uart")
|
||||||
|
@patch("esphome.codegen")
|
||||||
|
def test_config_schema_structure(self, mock_cg, mock_uart):
|
||||||
|
"""Test that CONFIG_SCHEMA has the correct structure."""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
components_path = os.path.join(
|
||||||
|
os.path.dirname(__file__), "..", "..", "components"
|
||||||
|
)
|
||||||
|
sys.path.insert(0, components_path)
|
||||||
|
|
||||||
|
import maidesite_desk as md_init
|
||||||
|
|
||||||
|
assert hasattr(md_init, "CONFIG_SCHEMA")
|
||||||
|
assert md_init.CONFIG_SCHEMA is not None
|
||||||
|
|
||||||
|
def test_valid_minimal_config(self):
|
||||||
|
"""Test validation of minimal valid configuration."""
|
||||||
|
config = {
|
||||||
|
"id": "my_desk",
|
||||||
|
"uart_id": "uart_bus",
|
||||||
|
}
|
||||||
|
# This would require full ESPHome environment to validate
|
||||||
|
# Just verify the config structure is reasonable
|
||||||
|
assert "id" in config
|
||||||
|
assert "uart_id" in config
|
||||||
|
|
||||||
|
def test_valid_full_config(self):
|
||||||
|
"""Test validation of full configuration with all options."""
|
||||||
|
config = {
|
||||||
|
"id": "my_desk",
|
||||||
|
"uart_id": "uart_bus",
|
||||||
|
"log_uart_msg": True,
|
||||||
|
}
|
||||||
|
assert "id" in config
|
||||||
|
assert "uart_id" in config
|
||||||
|
assert "log_uart_msg" in config
|
||||||
|
|
||||||
|
def test_log_uart_msg_default(self):
|
||||||
|
"""Test that log_uart_msg defaults to False."""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
components_path = os.path.join(
|
||||||
|
os.path.dirname(__file__), "..", "..", "components"
|
||||||
|
)
|
||||||
|
sys.path.insert(0, components_path)
|
||||||
|
|
||||||
|
import maidesite_desk as md_init
|
||||||
|
|
||||||
|
# The default is specified in the schema as False
|
||||||
|
# This test verifies the constant exists
|
||||||
|
assert md_init.CONF_LOG_UART_MSG == "log_uart_msg"
|
||||||
|
|
||||||
|
def test_multiconf_support(self):
|
||||||
|
"""Test that component supports multiple instances."""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
components_path = os.path.join(
|
||||||
|
os.path.dirname(__file__), "..", "..", "components"
|
||||||
|
)
|
||||||
|
sys.path.insert(0, components_path)
|
||||||
|
|
||||||
|
import maidesite_desk as md_init
|
||||||
|
|
||||||
|
# Verify MULTICONF is True, allowing multiple instances
|
||||||
|
assert md_init.MULTICONF is True
|
||||||
|
|
||||||
|
def test_uart_dependency(self):
|
||||||
|
"""Test that UART is listed as a dependency."""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
components_path = os.path.join(
|
||||||
|
os.path.dirname(__file__), "..", "..", "components"
|
||||||
|
)
|
||||||
|
sys.path.insert(0, components_path)
|
||||||
|
|
||||||
|
import maidesite_desk as md_init
|
||||||
|
|
||||||
|
assert "uart" in md_init.DEPENDENCIES
|
||||||
|
|
||||||
|
def test_namespace_definition(self):
|
||||||
|
"""Test that component namespace is correctly defined."""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
components_path = os.path.join(
|
||||||
|
os.path.dirname(__file__), "..", "..", "components"
|
||||||
|
)
|
||||||
|
sys.path.insert(0, components_path)
|
||||||
|
|
||||||
|
import maidesite_desk as md_init
|
||||||
|
|
||||||
|
assert hasattr(md_init, "maidesite_desk_ns")
|
||||||
|
assert md_init.maidesite_desk_ns is not None
|
||||||
|
|
||||||
|
assert hasattr(md_init, "MaidesiteDeskComponent")
|
||||||
|
assert md_init.MaidesiteDeskComponent is not None
|
||||||
Loading…
Reference in New Issue