Coverage for tests/tests/test_vol_schemas.py: 0%

1#!/usr/bin/env python3

2"""RAMSES RF - Test the configuration parsers."""

3

4 from typing import Any

5

6 import pytest

7 import voluptuous as vol

8 import yaml

9

10 from ramses_rf.schemas import (

11 SCH_GATEWAY_DICT,

12 SCH_GLOBAL_SCHEMAS,

13 SCH_GLOBAL_SCHEMAS_DICT,

14 SCH_RESTORE_CACHE_DICT,

15)

16 from ramses_tx.schemas import (

17 SCH_ENGINE_DICT,

18 SCH_GLOBAL_TRAITS_DICT,

19 sch_packet_log_dict_factory,

20 sch_serial_port_dict_factory,

21)

22

23 SCH_PACKET_LOG_DICT = sch_packet_log_dict_factory(default_backups=7)

24 SCH_SERIAL_PORT_DICT = sch_serial_port_dict_factory()

25

26 SCH_GATEWAY = vol.Schema(SCH_GATEWAY_DICT | SCH_ENGINE_DICT, extra=vol.PREVENT_EXTRA)

27 SCH_GLOBAL_TRAITS = vol.Schema(SCH_GLOBAL_TRAITS_DICT, extra=vol.PREVENT_EXTRA)

28 SCH_PACKET_LOG = vol.Schema(SCH_PACKET_LOG_DICT, extra=vol.PREVENT_EXTRA)

29 SCH_RESTORE_CACHE = vol.Schema(SCH_RESTORE_CACHE_DICT, extra=vol.PREVENT_EXTRA)

30 SCH_SERIAL_PORT = vol.Schema(SCH_SERIAL_PORT_DICT, extra=vol.PREVENT_EXTRA)

31

32

33# TODO: These schema pass testing, but shouldn't

34

35 _FAIL_BUT_VALID = (

36 """

37 01:333333: {is_vcs: true}

38 """,

39 """

40 02:333333: {is_vcs: true}

41 """,

42)

43 _PASS_BUT_INVALID = (

44 """

45 main_tcs: 01:111111 # no TCS schema

46 """,

47 """

48 known_list:

49 01:111111: {}

50 block_list:

51 01:111111: {} # also in known_list

52 """,

53)

54

55

56 def no_duplicates_constructor(

57 loader: yaml.Loader, node: yaml.Node, deep: bool = False

58) -> Any:

59 """Check for duplicate keys."""

60 mapping: dict[str, Any] = {}

61 for key_node, value_node in node.value:

62 key = loader.construct_object(key_node, deep=deep) # type: ignore[no-untyped-call]

63 if key in mapping:

64 raise yaml.constructor.ConstructorError(

65 f"Duplicate key: {key} ('{mapping[key]}' overwrites '{value_node}')"

66 )

67 value = loader.construct_object(value_node, deep=deep) # type: ignore[no-untyped-call]

68 mapping[key] = value

69 return loader.construct_mapping(node, deep)

70

71

72 class CheckForDuplicatesLoader(yaml.Loader):

73 """Local class to prevent pollution of global yaml.Loader."""

74

75 pass

76

77

78 CheckForDuplicatesLoader.add_constructor(

79 yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, no_duplicates_constructor

80)

81

82

83 def _test_schema(validator: vol.Schema, config: str) -> dict:

84 return validator(yaml.load(config, CheckForDuplicatesLoader)) # type: ignore[no-any-return]

85 # return validator(yaml.safe_load(schema)) # PyYAML silently swallows duplicate keys!

86

87

88 def _test_schema_bad(validator: vol.Schema, config: str) -> None:

89 global test_schemas_bad_failed

90 try:

91 _test_schema(validator, config)

92 except (vol.MultipleInvalid, yaml.YAMLError):

93 pass

94 else:

95 test_schemas_bad_failed = True

96 raise TypeError(f"should *not* be valid YAML, but parsed OK: {config}")

97

98

99 def _test_schema_good(validator: vol.Schema, config: str) -> dict:

100 global test_schemas_good_failed

101 try:

102 return _test_schema(validator, config)

103 except vol.MultipleInvalid as err:

104 test_schemas_good_failed = True

105 raise TypeError(

106 f"should parse via voluptuous, but didn't: {config} ({err})"

107 ) from err

108 except yaml.YAMLError as err:

109 test_schemas_good_failed = True

110 raise TypeError(f"should be valid YAML, but isn't: {config} ({err})") from err

111

112

113 GATEWAY_BAD = (

114 """

115 # expected a dictionary

116 """,

117 """

118 other_key: null # extra keys not allowed @ data['other_key']

119 """,

120 """

121 disable_discovery: null

122 """,

123 """

124 max_zones: 19

125 """,

126 """

127 use_regex:

128 """,

129 """

130 disable_discovery: null

131 enable_eavesdrop: null

132 max_zones: null

133 reduce_processing: null

134 use_aliases: null

135 use_native_ot: null

136 """,

137)

138 GATEWAY_GOOD = (

139 """

140 {}

141 """,

142 """

143 disable_discovery: false

144 """,

145 """

146 max_zones: 16

147 """,

148 """

149 disable_discovery: false

150 enable_eavesdrop: false

151 max_zones: 12

152 reduce_processing: 0

153 use_aliases: false

154 use_native_ot: prefer

155 """,

156 """

157 disable_discovery: true

158 enable_eavesdrop: true

159 max_zones: 3

160 reduce_processing: 2

161 use_aliases: true

162 use_native_ot: never

163 use_regex: {}

164 """,

165 """

166 disable_sending: true

167 enforce_known_list: true

168 evofw_flag: "!V"

169 use_regex:

170 inbound: {}

171 outbound: {}

172 """,

173)

174

175

176@pytest.mark.parametrize("index", range(len(GATEWAY_BAD)))

177 def test_gateway_bad(index: int, configs: tuple[str, ...] = GATEWAY_BAD) -> None:

178 _test_schema_bad(SCH_GATEWAY, configs[index])

179

180

181@pytest.mark.parametrize("index", range(len(GATEWAY_GOOD)))

182 def test_gateway_good(index: int, configs: tuple[str, ...] = GATEWAY_GOOD) -> None:

183 _test_schema_good(SCH_GATEWAY, configs[index])

184

185

186 KNOWN_LIST_BAD = (

187 """

188 # expected a dictionary

189 """,

190 """

191 other_key: null # extra keys not allowed @ data['other_key']

192 """,

193 """

194 known_list: [] # expected a dictionary for dictionary value @ data['known_list']

195 """,

196 """

197 known_list:

198 01:111111: {class: xxx}

199 """,

200 # """

201 # known_list:

202 # 01:111111: {class: CTL, notes: this is invalid _note}

203 # 02:111111: {class: UFC, extra: this is invalid too}

204 # """,

205 """

206 known_list:

207 01:111111: {class: CTL}

208 01:111111: {class: UFC} # Duplicate key: 01:111111

209 """,

210 # """

211 # known_list:

212 # 05:111111: {class: REM, scheme: xxxxxx}

213 # """,

214 # """

215 # known_list:

216 # 01:111111: {class: FAN}

217 # 02:111111: {class: RFS}

218 # 03:111111: {class: CO2, faked: true}

219 # 04:111111: {class: HUM, faked: true}

220 # 05:111111: {class: REM, faked: true, scheme: nuaire}

221 # 06:111111:

222 # class: DIS

223 # scheme: orcon

224 # _note: this is a note

225 # """,

226)

227 KNOWN_LIST_GOOD = (

228 """

229 {}

230 """,

231 """

232 known_list: {}

233 """,

234 """

235 known_list: {}

236 block_list: {}

237 """,

238 """

239 known_list:

240 01:111111: {}

241 block_list:

242 01:222222: {}

243 """,

244 """

245 known_list:

246 01:111111: {class: CTL}

247 02:111111: {class: UFC}

248 03:111111: {class: THM, faked: true}

249 04:111111: {class: TRV}

250 07:111111: {class: DHW, faked: true}

251 10:111111: {class: OTB}

252 12:111111: {class: THM}

253 13:111111: {class: BDR}

254 17:111111: {class: OUT, faked: true}

255 18:111111: {class: HGI}

256 22:111111: {class: THM}

257 23:111111: {class: THM}

258 30:111111: {class: RFG}

259 34:111111: {class: THM, _note: this is a note}

260 """,

261 """

262 known_list:

263 01:111111: {class: FAN}

264 02:111111: {class: RFS}

265 03:111111: {class: CO2, faked: true}

266 04:111111: {class: HUM, faked: true}

267 05:111111: {class: REM, faked: true, scheme: nuaire}

268 06:111111:

269 class: DIS

270 _note: this is a note

271 """,

272)

273

274

275@pytest.mark.parametrize("index", range(len(KNOWN_LIST_BAD)))

276 def test_known_list_bad(index: int, configs: tuple[str, ...] = KNOWN_LIST_BAD) -> None:

277 _test_schema_bad(SCH_GLOBAL_TRAITS, configs[index])

278

279

280@pytest.mark.parametrize("index", range(len(KNOWN_LIST_GOOD)))

281 def test_known_list_good(

282 index: int, configs: tuple[str, ...] = KNOWN_LIST_GOOD

283) -> None:

284 _test_schema_good(SCH_GLOBAL_TRAITS, configs[index])

285

286

287 PACKET_LOG_BAD = (

288 """

289 # expected a dictionary

290 """,

291 """

292 other_key: null # extra keys not allowed @ data['other_key']

293 """,

294 """

295 packet_log:

296 file_name: null # expected str for dictionary value @ data['packet_log']['file_name']

297 """,

298 """

299 packet_log: # required key not provided @ data['packet_log']['file_name']

300 rotate_backups: 7

301 rotate_bytes: 204800

302 """,

303)

304 PACKET_LOG_GOOD = (

305 """

306 {}

307 """,

308 """

309 packet_log: packet.log

310 """,

311 """

312 packet_log: null # expected str for dictionary value @ data['packet_log']

313 """,

314 """

315 packet_log:

316 file_name: packet.log

317 """,

318 """

319 packet_log:

320 file_name: packet.log

321 rotate_backups: 7

322 """,

323 """

324 packet_log:

325 file_name: packet.log

326 rotate_bytes: 204800

327 """,

328 """

329 packet_log:

330 file_name: packet.log

331 rotate_backups: 7

332 rotate_bytes: 204800

333 """,

334)

335

336

337@pytest.mark.parametrize("index", range(len(PACKET_LOG_BAD)))

338 def test_packet_log_bad(index: int, configs: tuple[str, ...] = PACKET_LOG_BAD) -> None:

339 _test_schema_bad(SCH_PACKET_LOG, configs[index])

340

341

342@pytest.mark.parametrize("index", range(len(PACKET_LOG_GOOD)))

343 def test_packet_log_good(

344 index: int, configs: tuple[str, ...] = PACKET_LOG_GOOD

345) -> None:

346 _test_schema_good(SCH_PACKET_LOG, configs[index])

347

348

349 RESTORE_CACHE_BAD = (

350 """

351 # expected a dictionary

352 """,

353 """

354 other_key: null # extra keys not allowed @ data['other_key']

355 """,

356 """

357 restore_cache: none # should be boolean

358 """,

359 """

360 restore_cache: true

361 restore_schema: true # yaml.scanner.ScannerError

362 """,

363 """

364 restore_schema: true # should be: restore_cache: restore_schema: true

365 """,

366 """

367 restore_state: false # should be: restore_cache: restore_state: true

368 """,

369)

370 RESTORE_CACHE_GOOD = (

371 """

372 {}

373 """,

374 """

375 restore_cache: false

376 """,

377 """

378 restore_cache: true

379 """,

380 """

381 restore_cache:

382 restore_schema: true

383 """,

384 """

385 restore_cache:

386 restore_state: true

387 """,

388 """

389 restore_cache:

390 restore_schema: true

391 restore_state: false

392 """,

393 """

394 restore_cache:

395 restore_schema: false

396 restore_state: true

397 """,

398)

399

400

401@pytest.mark.parametrize("index", range(len(RESTORE_CACHE_BAD)))

402 def test_restore_cache_bad(

403 index: int, configs: tuple[str, ...] = RESTORE_CACHE_BAD

404) -> None:

405 _test_schema_bad(SCH_RESTORE_CACHE, configs[index])

406

407

408@pytest.mark.parametrize("index", range(len(RESTORE_CACHE_GOOD)))

409 def test_restore_cache_good(

410 index: int, configs: tuple[str, ...] = RESTORE_CACHE_GOOD

411) -> None:

412 _test_schema_good(SCH_RESTORE_CACHE, configs[index])

413

414

415 SERIAL_PORT_BAD = (

416 """

417 # expected a dictionary

418 """,

419 """

420 {} # required key not provided @ data['serial_port']

421 """,

422 """

423 other_key: null # extra keys not allowed @ data['other_key']

424 """,

425 """

426 serial_name: /dev/ttyMOCK # should be: serial_port:

427 """,

428 """

429 serial_port: /dev/ttyMOCK # yaml.parser.ParserError

430 baudrate: 115200 # default

431 """,

432 """

433 serial_port:

434 port_name: /dev/ttyMOCK

435 baud_rate: 57600 # should be: baudrate:

436 """,

437 """

438 serial_port:

439 port_name: /dev/ttyMOCK

440 baudrate: 57600 # yaml.parser.ScannerError

441 """,

442)

443 SERIAL_PORT_GOOD = (

444 """

445 serial_port: /dev/ttyMOCK

446 """,

447 """

448 serial_port:

449 port_name: /dev/ttyMOCK

450 """,

451 """

452 serial_port:

453 port_name: /dev/ttyMOCK

454 baudrate: 115200 # default

455 """,

456 """

457 serial_port:

458 port_name: /dev/ttyMOCK

459 baudrate: 57600

460 """,

461 """

462 serial_port:

463 port_name: /dev/ttyMOCK

464 baudrate: 57600

465 """,

466 """

467 serial_port:

468 port_name: /dev/ttyMOCK

469 baudrate: 57600

470 dsrdtr: false

471 rtscts: false

472 timeout: 0

473 xonxoff: true

474 """,

475)

476

477

478@pytest.mark.parametrize("index", range(len(SERIAL_PORT_BAD)))

479 def test_serial_port_bad(

480 index: int, configs: tuple[str, ...] = SERIAL_PORT_BAD

481) -> None:

482 _test_schema_bad(SCH_SERIAL_PORT, configs[index])

483

484

485@pytest.mark.parametrize("index", range(len(SERIAL_PORT_GOOD)))

486 def test_serial_port_good(

487 index: int, configs: tuple[str, ...] = SERIAL_PORT_GOOD

488) -> None:

489 _test_schema_good(SCH_SERIAL_PORT, configs[index])

490

491

492 SCHEMAS_TCS_BAD = (

493 """

494 # expected a dictionary

495 """,

496 """

497 other_key: null # extra keys not allowed @ data['other_key']

498 """,

499 """

500 01:111111: # expected a dictionary for dictionary value @ data['01:111111']

501 """,

502 """

503 01:111111:

504 system: # expected a dictionary for dictionary value @ data['01:111111']['system']

505 """,

506 """

507 13:111111: # should be: 01:111111

508 system: {} # The ventilation control system schema must include at least one of [remotes, sensors] @ data['13:111111']

509 """,

510 """

511 01:111111:

512 system:

513 appliance_control: 10:111111

514 zones: # should be "00"

515 00: {} # extra keys not allowed @ data['01:111111']['zones'][0]

516 """,

517 """

518 01:111111:

519 system:

520 appliance_control: 10:111111

521 zones:

522 "00": # extra keys not allowed @ data['01:111111']['zones']['00']

523 """,

524 """

525 01:111111:

526 system:

527 appliance_control: 10:111111

528 zones:

529 "1C": {} # extra keys not allowed @ data['01:111111']['zones']['1C']

530 """,

531 """

532 01:111111:

533 system:

534 appliance_control: 10:111111

535 01:111111: {remotes: [29:111111, 29:222222]}

536 """,

537)

538 SCHEMAS_TCS_GOOD = (

539 """

540 {}

541 """,

542 """

543 01:111111: {}

544 """,

545 """

546 01:111111: {is_tcs: true}

547 """,

548 """

549 01:111111:

550 system: {}

551 """,

552 """

553 01:111111:

554 system:

555 appliance_control:

556 """,

557 """

558 01:111111:

559 system:

560 appliance_control: null

561 """,

562 """

563 01:111111:

564 system:

565 appliance_control: 10:111111

566 """,

567 """

568 01:111111:

569 system:

570 appliance_control: 10:111111

571 01:222222:

572 system:

573 appliance_control: 13:222222

574 """,

575 """

576 main_tcs: null

577 01:111111:

578 system:

579 appliance_control: 10:111111

580 zones:

581 "0B":

582 sensor: 01:111111

583 """,

584 """

585 main_tcs: 01:222222

586 01:111111:

587 system:

588 appliance_control: 10:111111

589 zones:

590 "00": {}

591 "01": {sensor: 03:111111}

592 "02": {actuators: [04:111111, 04:222222]}

593 "03": {actuators: [13:111111, 13:222222]}

594 """,

595 """

596 main_tcs: 01:222222

597 01:111111:

598 system:

599 appliance_control: 10:111111

600 01:222222:

601 system:

602 appliance_control: 10:222222

603 """,

604)

605

606

607@pytest.mark.parametrize("index", range(len(SCHEMAS_TCS_BAD)))

608 def test_schemas_tcs_bad(

609 index: int, configs: tuple[str, ...] = SCHEMAS_TCS_BAD

610) -> None:

611 _test_schema_bad(SCH_GLOBAL_SCHEMAS, configs[index])

612

613

614@pytest.mark.parametrize("index", range(len(SCHEMAS_TCS_GOOD)))

615 def test_schemas_tcs_good(

616 index: int, configs: tuple[str, ...] = SCHEMAS_TCS_GOOD

617) -> None:

618 _test_schema_good(SCH_GLOBAL_SCHEMAS, configs[index])

619

620

621 SCHEMAS_VCS_BAD = (

622 """

623 # expected a dictionary

624 """,

625 """

626 other_key: null # extra keys not allowed @ data['other_key']

627 """,

628 """

629 32:111111: # expected a dictionary for dictionary value @ data['01:111111']

630 """,

631 """

632 32:111111: {is_vcs: true}

633 """,

634 """

635 01:111111:

636 remotes: []

637 is_tcs: true

638 """,

639 """

640 32:111111: # should not duplicate device_id

641 remotes: [29:111111, 29:111111] # not a valid value for dictionary value @ data['32:111111']['remotes']

642 """,

643 """

644 32:111111: {remotes: [29:111111, 29:222222]}

645 32:111111: {remotes: [29:111111, 29:222222]} # has duplicate key

646 32:333333: {remotes: [29:111111, 29:222222]}

647 """,

648)

649 SCHEMAS_VCS_GOOD = (

650 """

651 {}

652 """,

653 """

654 01:333333:

655 remotes: []

656 """,

657 """

658 32:111111:

659 remotes: []

660 """,

661 """

662 32:111111:

663 remotes: [29:111111]

664 """,

665 """

666 32:111111:

667 remotes: [29:111111, 29:222222]

668 sensors: [29:111111, 29:333333]

669 """,

670 """

671 32:111111: {remotes: [29:111111, 29:222222]}

672 32:222222: {remotes: [29:111111, 29:222222]}

673 32:333333: {remotes: [29:111111, 29:222222]}

674 """,

675)

676

677

678@pytest.mark.parametrize("index", range(len(SCHEMAS_VCS_BAD)))

679 def test_schemas_vcs_bad(

680 index: int, configs: tuple[str, ...] = SCHEMAS_VCS_BAD

681) -> None:

682 _test_schema_bad(SCH_GLOBAL_SCHEMAS, configs[index])

683

684

685@pytest.mark.parametrize("index", range(len(SCHEMAS_VCS_GOOD)))

686 def test_schemas_vcs_good(

687 index: int, configs: tuple[str, ...] = SCHEMAS_VCS_GOOD

688) -> None:

689 _test_schema_good(SCH_GLOBAL_SCHEMAS, configs[index])

690

691

692 SCHEMAS_MIXED_BAD = tuple(x + y for x in SCHEMAS_TCS_GOOD for y in SCHEMAS_VCS_BAD[1:])

693 SCHEMAS_MIXED_BAD += tuple(

694 x + y for x in SCHEMAS_TCS_BAD[1:] for y in SCHEMAS_VCS_GOOD[1:]

695)

696 SCHEMAS_MIXED_GOOD = tuple(

697 x + y for x in SCHEMAS_TCS_GOOD[1:] for y in SCHEMAS_VCS_GOOD[1:]

698)

699

700

701 test_schemas_bad_failed = False

702 test_schemas_good_failed = False

703

704

705@pytest.mark.parametrize("index", range(len(SCHEMAS_MIXED_BAD)))

706 def test_schemas_mixed_bad(

707 index: int, configs: tuple[str, ...] = SCHEMAS_MIXED_BAD

708) -> None:

709 global test_schemas_bad_failed

710 if not test_schemas_bad_failed:

711 _test_schema_bad(SCH_GLOBAL_SCHEMAS, configs[index])

712

713

714@pytest.mark.parametrize("index", range(len(SCHEMAS_MIXED_GOOD)))

715 def test_schemas_mixed_good(

716 index: int, configs: tuple[str, ...] = SCHEMAS_MIXED_GOOD

717) -> None:

718 global test_schemas_good_failed

719 if not test_schemas_good_failed:

720 _test_schema_good(SCH_GLOBAL_SCHEMAS, configs[index])

721

722

723 SCHEMAS_HASS_BAD = (

724 """

725 # expected a dictionary

726 """,

727 """

728 {}

729 """,

730 """

731 other_key: null # extra keys not allowed @ data['other_key']

732 """,

733 """

734 ramses_rf:

735 serial_port: /dev/ttyUSB0

736

737 ramses_rf:

738 enforce_known_list: true

739 """,

740 """

741 ramses_cc:

742 serial_port: /dev/ttyUSB0

743

744 ramses_cc:

745 enforce_known_list: true

746 """,

747 """

748 ramses_cc:

749 serial_port: /dev/ttyUSB0

750

751 schema: # this not used

752 01:111111: # Temperature control system (e.g. evohome)

753 system:

754 appliance_control: 10:123446

755 zones:

756 "07": {sensor: 01:111111}

757

758 orphans_hvac: [30:111111, 32:333333, 32:555555, 32:666666]

759 """,

760 """

761 ramses_cc:

762 serial_port: /dev/ttyACM0

763 01:123456: {}

764 01:123456: {} # Duplicate key: 01:123456...

765 """,

766)

767 SCHEMAS_HASS_GOOD = (

768 """

769 ramses_cc:

770 serial_port: /dev/ttyACM0

771 """,

772 """

773 ramses_cc:

774 serial_port: /dev/ttyUSB0

775 restore_cache: false

776 known_list:

777 """,

778 """

779 ramses_cc:

780 serial_port: rfc2217://localhost:5001

781 restore_cache:

782 restore_schema: true

783 known_list: {}

784 block_list:

785 """,

786 """

787 ramses_cc:

788 serial_port: /dev/serial/by-id/usb-SHK_NANO_CUL_868-if00-port0

789 restore_cache:

790 restore_state: true

791 known_list:

792 30:111111: # becomes empty {}

793 32:333333: {} #

794 32:555555: {class: CO2} #

795 block_list: {}

796 """,

797 """

798 ramses_cc:

799 serial_port: /dev/serial/by-id/usb-SHK_NANO_CUL_868-if00-port0

800

801 scan_interval: 300

802

803 restore_cache:

804 restore_schema: true

805 restore_state: true

806

807 packet_log:

808 file_name: packet.log

809 rotate_backups: 28

810

811 ramses_rf:

812 enforce_known_list: true

813

814 main_tcs: 01:111111

815

816 01:111111: # Temperature control system (e.g. evohome)

817 system:

818 appliance_control: 10:123446

819 zones:

820 "07": {sensor: 01:111111}

821

822 orphans_heat: [02:123456]

823 orphans_hvac: [30:111111, 32:333333, 32:555555, 32:666666]

824

825 known_list:

826 30:111111: {class: FAN} # Orcon HRU

827 32:333333: {class: REM, faked: true} # an impersonatable remote

828 32:555555: {class: CO2, faked: true} # a fully faked sensor

829 32:666666: {class: HUM}

830

831 block_list:

832 23:111111: {}

833

834 advanced_features:

835 send_packet: true

836 """,

837 """

838 ramses_cc:

839 serial_port: /dev/ttyACM0

840 01:123456: {}

841 01:123457: {}

842 """,

843)

844

845

846 SCH_DOMAIN_CONFIG = vol.All( # as per ramses_cc.schemas, TODO: add advanced_features

847 vol.Schema(

848 {

849 vol.Optional("ramses_rf", default={}): SCH_GATEWAY,

850 vol.Optional("scan_interval"): int,

851 vol.Optional("advanced_features", default={}): dict,

852 },

853 extra=vol.PREVENT_EXTRA,

854 )

855 .extend(SCH_GLOBAL_SCHEMAS_DICT)

856 .extend(SCH_GLOBAL_TRAITS_DICT)

857 .extend(SCH_PACKET_LOG_DICT)

858 .extend(SCH_RESTORE_CACHE_DICT)

859 .extend(SCH_SERIAL_PORT_DICT),

860)

861

862 SCH_GLOBAL_HASS = vol.Schema(

863 {vol.Required("ramses_cc"): SCH_DOMAIN_CONFIG}, extra=vol.PREVENT_EXTRA

864)

865

866

867@pytest.mark.parametrize("index", range(len(SCHEMAS_HASS_BAD)))

868 def test_schemas_hass_bad(

869 index: int, configs: tuple[str, ...] = SCHEMAS_HASS_BAD

870) -> None:

871 _test_schema_bad(SCH_GLOBAL_HASS, configs[index])

872

873

874@pytest.mark.parametrize("index", range(len(SCHEMAS_HASS_GOOD)))

875 def test_schemas_hass_good(

876 index: int, configs: tuple[str, ...] = SCHEMAS_HASS_GOOD

877) -> None:

878 _test_schema_good(SCH_GLOBAL_HASS, configs[index])