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

124 statements  

« prev     ^ index     » next       coverage.py v7.11.3, created at 2026-01-05 21:46 +0100

1#!/usr/bin/env python3 

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

3 

4from typing import Any 

5 

6import pytest 

7import voluptuous as vol 

8import yaml 

9 

10from ramses_rf.schemas import ( 

11 SCH_GATEWAY_DICT, 

12 SCH_GLOBAL_SCHEMAS, 

13 SCH_GLOBAL_SCHEMAS_DICT, 

14 SCH_RESTORE_CACHE_DICT, 

15) 

16from 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 

23SCH_PACKET_LOG_DICT = sch_packet_log_dict_factory(default_backups=7) 

24SCH_SERIAL_PORT_DICT = sch_serial_port_dict_factory() 

25 

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

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

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

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

30SCH_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 

56def 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 

72class CheckForDuplicatesLoader(yaml.Loader): 

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

74 

75 pass 

76 

77 

78CheckForDuplicatesLoader.add_constructor( 

79 yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, no_duplicates_constructor 

80) 

81 

82 

83def _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 

88def _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 

99def _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 

113GATEWAY_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) 

138GATEWAY_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))) 

177def 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))) 

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

183 _test_schema_good(SCH_GATEWAY, configs[index]) 

184 

185 

186KNOWN_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) 

227KNOWN_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))) 

276def 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))) 

281def 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 

287PACKET_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) 

304PACKET_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))) 

338def 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))) 

343def 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 

349RESTORE_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) 

370RESTORE_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))) 

402def 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))) 

409def 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 

415SERIAL_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) 

443SERIAL_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))) 

479def 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))) 

486def 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 

492SCHEMAS_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) 

538SCHEMAS_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))) 

608def 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))) 

615def 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 

621SCHEMAS_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) 

649SCHEMAS_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))) 

679def 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))) 

686def 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 

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

693SCHEMAS_MIXED_BAD += tuple( 

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

695) 

696SCHEMAS_MIXED_GOOD = tuple( 

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

698) 

699 

700 

701test_schemas_bad_failed = False 

702test_schemas_good_failed = False 

703 

704 

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

706def 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))) 

715def 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 

723SCHEMAS_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 """ 

734ramses_rf: 

735 serial_port: /dev/ttyUSB0 

736 

737 ramses_rf: 

738 enforce_known_list: true 

739 """, 

740 """ 

741ramses_cc: 

742 serial_port: /dev/ttyUSB0 

743 

744 ramses_cc: 

745 enforce_known_list: true 

746 """, 

747 """ 

748ramses_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 """ 

761ramses_cc: 

762 serial_port: /dev/ttyACM0 

763 01:123456: {} 

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

765 """, 

766) 

767SCHEMAS_HASS_GOOD = ( 

768 """ 

769ramses_cc: 

770 serial_port: /dev/ttyACM0 

771 """, 

772 """ 

773ramses_cc: 

774 serial_port: /dev/ttyUSB0 

775 restore_cache: false 

776 known_list: 

777 """, 

778 """ 

779ramses_cc: 

780 serial_port: rfc2217://localhost:5001 

781 restore_cache: 

782 restore_schema: true 

783 known_list: {} 

784 block_list: 

785 """, 

786 """ 

787ramses_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 """ 

798ramses_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 """ 

838ramses_cc: 

839 serial_port: /dev/ttyACM0 

840 01:123456: {} 

841 01:123457: {} 

842 """, 

843) 

844 

845 

846SCH_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 

862SCH_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))) 

868def 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))) 

875def test_schemas_hass_good( 

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

877) -> None: 

878 _test_schema_good(SCH_GLOBAL_HASS, configs[index])