Coverage for tests/test_parser.py: 98%

340 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-08-31 04:18 +0200

1"""Test BEEP parser.""" 

2 

3from unittest import TestCase 

4 

5from galaxy_graph import Connection, Graph, Node 

6from galaxy_graph.graph import NodeIsOnline 

7 

8from galaxy_beep_frame.converter import Parser, Token, TokenType 

9from galaxy_beep_frame.exception import ( 

10 InvalidTokenTypeError, 

11 NodeCountError, 

12 NodeNoneError, 

13 TokenMutableError, 

14 UnknownContinuationIndicatorError, 

15 UnknownMsgTypeError, 

16) 

17from galaxy_beep_frame.exception.frame import AboveRangeError 

18from galaxy_beep_frame.frame import ContinuationIndicator 

19from galaxy_beep_frame.header_type import PossibleHeaderType 

20 

21 

22class TestParser(TestCase): 

23 """Test BEEP parsing from graph Token.""" 

24 

25 def _generate_graph(self) -> Node[Token]: 

26 """Generate a default lexer output.""" 

27 

28 graph = Graph() 

29 frame = graph.add(Node(data=Token(type=TokenType.FRAME))) 

30 frame.data.end() 

31 

32 header = graph.add(Node(data=Token(type=TokenType.HEADER))) 

33 header.data.end() 

34 frame.connect(Connection("has header"), header) 

35 

36 message_type = graph.add( 

37 Node(data=Token(type=TokenType.MESSAGE_TYPE, value=b"MSG")) 

38 ) 

39 message_type.data.end() 

40 channel_number = graph.add( 

41 Node(data=Token(type=TokenType.CHANNEL_NUMBER, value=b"0")) 

42 ) 

43 channel_number.data.end() 

44 message_number = graph.add( 

45 Node(data=Token(type=TokenType.MESSAGE_NUMBER, value=b"1")) 

46 ) 

47 message_number.data.end() 

48 continuation_indicator = graph.add( 

49 Node( 

50 data=Token( 

51 type=TokenType.CONTINUATION_INDICATOR, value=b"." 

52 ) 

53 ) 

54 ) 

55 continuation_indicator.data.end() 

56 sequence_number = graph.add( 

57 Node( 

58 data=Token(type=TokenType.SEQUENCE_NUMBER, value=b"52") 

59 ) 

60 ) 

61 sequence_number.data.end() 

62 header.connect(Connection("has message type"), message_type) 

63 header.connect(Connection("has channel number"), channel_number) 

64 header.connect(Connection("has message number"), message_number) 

65 header.connect( 

66 Connection("has continuation indicator"), 

67 continuation_indicator, 

68 ) 

69 header.connect( 

70 Connection("has sequence number"), sequence_number 

71 ) 

72 

73 payload = graph.add( 

74 Node(data=Token(type=TokenType.PAYLOAD, value=b"")) 

75 ) 

76 payload.data.end() 

77 frame.connect(Connection("has payload"), payload) 

78 

79 return frame 

80 

81 def test_good(self) -> None: 

82 """Test parsing a good lexer output.""" 

83 

84 Parser.parse_frame(self._generate_graph()) 

85 

86 def test_graph_empty(self) -> None: 

87 """Test parsing an empty Node.""" 

88 

89 graph = Graph() 

90 node = graph.add(Node()) 

91 

92 with self.assertRaises(NodeNoneError): 

93 Parser.parse_frame(node) 

94 

95 def test_answer_number(self) -> None: 

96 """Test parsing incorrect or missing answer number.""" 

97 

98 frame = self._generate_graph() 

99 

100 (header,) = frame.get_outgoing_neighbors_by_connection_data( 

101 lambda data: data == "has header" 

102 ) 

103 header.connect( 

104 Connection("has answer number"), 

105 Node(data=Token(type=TokenType.ANSWER_NUMBER, value=b"0")), 

106 ) 

107 

108 with self.assertRaises(NodeCountError): 

109 Parser.parse_frame(frame) 

110 

111 (message_type,) = ( 

112 header.get_outgoing_neighbors_by_connection_data( 

113 lambda data: data == "has message type" 

114 ) 

115 ) 

116 message_type.data.value = b"ANS" 

117 

118 (answer_number,) = ( 

119 header.get_outgoing_neighbors_by_connection_data( 

120 lambda data: data == "has answer number" 

121 ) 

122 ) 

123 

124 answer_number.data = "" 

125 

126 with self.assertRaises(NodeNoneError): 

127 Parser.parse_frame(frame) 

128 

129 online = header.state.online 

130 if not isinstance(online, NodeIsOnline): 

131 raise TypeError 

132 

133 online.graph.delete(answer_number.identifier) 

134 

135 with self.assertRaises(NodeCountError): 

136 Parser.parse_frame(frame) 

137 

138 answer_number = Node( 

139 data=Token(type=TokenType.ANSWER_NUMBER, value=b"0") 

140 ) 

141 header.connect( 

142 Connection("has answer number"), 

143 answer_number, 

144 ) 

145 

146 answer_number.data = Token( 

147 type=TokenType.ANSWER_NUMBER, 

148 value=b"999999999999999999999", 

149 ) 

150 

151 with self.assertRaises(TokenMutableError): 

152 Parser.parse_frame(frame) 

153 

154 answer_number.data.end() 

155 

156 with self.assertRaises(AboveRangeError): 

157 Parser.parse_frame(frame) 

158 

159 answer_number_token = Token( 

160 type=TokenType.CONTINUATION_INDICATOR, value=b"0" 

161 ) 

162 answer_number_token.end() 

163 answer_number.data = answer_number_token 

164 

165 with self.assertRaises(InvalidTokenTypeError): 

166 Parser.parse_frame(frame) 

167 

168 valid_mapping = { 

169 0: b"0", 

170 100: b"100", 

171 2147483647: b"2147483647", 

172 } 

173 

174 for result, value in valid_mapping.items(): 

175 with self.subTest(msg="Test with valid answer number"): 

176 answer_number_token = Token( 

177 type=TokenType.ANSWER_NUMBER, value=value 

178 ) 

179 answer_number_token.end() 

180 answer_number.data = answer_number_token 

181 

182 answer_number_number = Parser._get_answer_number( 

183 header, PossibleHeaderType.ANS 

184 ) 

185 

186 self.assertEqual(answer_number_number, result) 

187 

188 answer_number2 = Node( 

189 data=Token(type=TokenType.ANSWER_NUMBER, value=b"1") 

190 ) 

191 header.connect( 

192 Connection("has answer number"), 

193 answer_number2, 

194 ) 

195 answer_number2.data.end() 

196 

197 with self.assertRaises(NodeCountError): 

198 Parser.parse_frame(frame) 

199 

200 def test_message_number(self) -> None: 

201 """Test parsing incorrect or missing message number.""" 

202 

203 frame = self._generate_graph() 

204 

205 (header,) = frame.get_outgoing_neighbors_by_connection_data( 

206 lambda data: data == "has header" 

207 ) 

208 (message_number,) = ( 

209 header.get_outgoing_neighbors_by_connection_data( 

210 lambda data: data == "has message number" 

211 ) 

212 ) 

213 message_number.data = Token( 

214 type=TokenType.MESSAGE_NUMBER, 

215 value=b"999999999999999999999", 

216 ) 

217 

218 with self.assertRaises(TokenMutableError): 

219 Parser.parse_frame(frame) 

220 

221 message_number.data.end() 

222 

223 with self.assertRaises(AboveRangeError): 

224 Parser.parse_frame(frame) 

225 

226 message_number.data = b"0" 

227 

228 with self.assertRaises(NodeNoneError): 

229 Parser.parse_frame(frame) 

230 

231 online = message_number.state.online 

232 if not isinstance(online, NodeIsOnline): 

233 raise TypeError 

234 

235 online.graph.delete(message_number.identifier) 

236 

237 with self.assertRaises(NodeCountError): 

238 Parser.parse_frame(frame) 

239 

240 message_number = Node( 

241 data=Token(type=TokenType.MESSAGE_TYPE, value=b"0") 

242 ) 

243 header.connect( 

244 Connection("has message number"), 

245 message_number, 

246 ) 

247 message_number.data.end() 

248 

249 with self.assertRaises(InvalidTokenTypeError): 

250 Parser.parse_frame(frame) 

251 

252 valid_mapping = { 

253 0: b"0", 

254 100: b"100", 

255 2147483647: b"2147483647", 

256 } 

257 

258 for result, value in valid_mapping.items(): 

259 with self.subTest(msg="Test with valid message number"): 

260 message_number_token = Token( 

261 type=TokenType.MESSAGE_NUMBER, value=value 

262 ) 

263 message_number_token.end() 

264 message_number.data = message_number_token 

265 

266 message_number_number = Parser._get_message_number( 

267 header 

268 ) 

269 

270 self.assertEqual(message_number_number, result) 

271 

272 message_number2 = Node( 

273 data=Token(type=TokenType.MESSAGE_NUMBER, value=b"1") 

274 ) 

275 header.connect( 

276 Connection("has message number"), 

277 message_number2, 

278 ) 

279 message_number2.data.end() 

280 

281 with self.assertRaises(NodeCountError): 

282 Parser.parse_frame(frame) 

283 

284 def test_message_type(self) -> None: 

285 """Test parsing incorrect or missing message type.""" 

286 

287 frame = self._generate_graph() 

288 

289 (header,) = frame.get_outgoing_neighbors_by_connection_data( 

290 lambda data: data == "has header" 

291 ) 

292 (message_type,) = ( 

293 header.get_outgoing_neighbors_by_connection_data( 

294 lambda data: data == "has message type" 

295 ) 

296 ) 

297 message_type.data = Token( 

298 type=TokenType.MESSAGE_TYPE, value=b"MST" 

299 ) 

300 

301 with self.assertRaises(TokenMutableError): 

302 Parser.parse_frame(frame) 

303 

304 message_type.data.end() 

305 

306 with self.assertRaises(UnknownMsgTypeError): 

307 Parser.parse_frame(frame) 

308 

309 message_type.data = b"MSG" 

310 

311 with self.assertRaises(NodeNoneError): 

312 Parser.parse_frame(frame) 

313 

314 online = message_type.state.online 

315 if not isinstance(online, NodeIsOnline): 

316 raise TypeError 

317 online.graph.delete(message_type.identifier) 

318 

319 with self.assertRaises(NodeCountError): 

320 Parser.parse_frame(frame) 

321 

322 message_type = Node( 

323 data=Token(type=TokenType.MESSAGE_NUMBER, value=b"MSG") 

324 ) 

325 header.connect(Connection("has message type"), message_type) 

326 message_type.data.end() 

327 

328 with self.assertRaises(InvalidTokenTypeError): 

329 Parser.parse_frame(frame) 

330 

331 valid_mapping = { 

332 PossibleHeaderType.MSG: b"MSG", 

333 PossibleHeaderType.ANS: b"ANS", 

334 PossibleHeaderType.ERR: b"ERR", 

335 PossibleHeaderType.NUL: b"NUL", 

336 PossibleHeaderType.RPY: b"RPY", 

337 } 

338 

339 for result, value in valid_mapping.items(): 

340 with self.subTest(msg="Test with valid message type."): 

341 message_type_token = Token( 

342 type=TokenType.MESSAGE_TYPE, 

343 value=value, 

344 ) 

345 message_type_token.end() 

346 message_type.data = message_type_token 

347 

348 message_type_type = Parser._get_message_type(header) 

349 

350 self.assertEqual(message_type_type, result) 

351 

352 message_type2 = Node( 

353 data=Token(type=TokenType.MESSAGE_TYPE, value=b"MSG") 

354 ) 

355 header.connect( 

356 Connection("has message type"), 

357 message_type2, 

358 ) 

359 message_type2.data.end() 

360 

361 with self.assertRaises(NodeCountError): 

362 Parser.parse_frame(frame) 

363 

364 def test_channel_number(self) -> None: 

365 """Test parsing incorrect or missing channel number.""" 

366 

367 frame = self._generate_graph() 

368 

369 (header,) = frame.get_outgoing_neighbors_by_connection_data( 

370 lambda data: data == "has header" 

371 ) 

372 (channel_number,) = ( 

373 header.get_outgoing_neighbors_by_connection_data( 

374 lambda data: data == "has channel number" 

375 ) 

376 ) 

377 channel_number.data = Token( 

378 type=TokenType.CHANNEL_NUMBER, 

379 value=b"999999999999999999999", 

380 ) 

381 

382 with self.assertRaises(TokenMutableError): 

383 Parser.parse_frame(frame) 

384 

385 channel_number.data.end() 

386 

387 with self.assertRaises(AboveRangeError): 

388 Parser.parse_frame(frame) 

389 

390 channel_number.data = b"0" 

391 

392 with self.assertRaises(NodeNoneError): 

393 Parser.parse_frame(frame) 

394 

395 online = channel_number.state.online 

396 if not isinstance(online, NodeIsOnline): 

397 raise TypeError 

398 online.graph.delete(channel_number.identifier) 

399 

400 with self.assertRaises(NodeCountError): 

401 Parser.parse_frame(frame) 

402 

403 channel_number = Node( 

404 data=Token( 

405 type=TokenType.CONTINUATION_INDICATOR, value=b"0" 

406 ) 

407 ) 

408 header.connect( 

409 Connection("has channel number"), 

410 channel_number, 

411 ) 

412 channel_number.data.end() 

413 

414 with self.assertRaises(InvalidTokenTypeError): 

415 Parser.parse_frame(frame) 

416 

417 valid_mapping = { 

418 0: b"0", 

419 100: b"100", 

420 2147483647: b"2147483647", 

421 } 

422 

423 for result, value in valid_mapping.items(): 

424 with self.subTest(msg="Test with valid channel number"): 

425 channel_number_token = Token( 

426 type=TokenType.CHANNEL_NUMBER, value=value 

427 ) 

428 channel_number_token.end() 

429 channel_number.data = channel_number_token 

430 

431 channel_number_number = Parser._get_channel_number( 

432 header 

433 ) 

434 

435 self.assertEqual(channel_number_number, result) 

436 

437 channel_number2 = Node( 

438 data=Token(type=TokenType.CHANNEL_NUMBER, value=b"1") 

439 ) 

440 header.connect( 

441 Connection("has channel number"), 

442 channel_number2, 

443 ) 

444 channel_number2.data.end() 

445 

446 with self.assertRaises(NodeCountError): 

447 Parser.parse_frame(frame) 

448 

449 def test_continuation_indicator(self) -> None: 

450 """Test parsing incorrect or missing continuation indicator.""" 

451 

452 frame = self._generate_graph() 

453 

454 (header,) = frame.get_outgoing_neighbors_by_connection_data( 

455 lambda data: data == "has header" 

456 ) 

457 (continuation_indicator,) = ( 

458 header.get_outgoing_neighbors_by_connection_data( 

459 lambda data: data == "has continuation indicator" 

460 ) 

461 ) 

462 continuation_indicator.data = Token( 

463 type=TokenType.CONTINUATION_INDICATOR, 

464 value=b"-", 

465 ) 

466 

467 with self.assertRaises(TokenMutableError): 

468 Parser.parse_frame(frame) 

469 

470 continuation_indicator.data.end() 

471 

472 with self.assertRaises(UnknownContinuationIndicatorError): 

473 Parser.parse_frame(frame) 

474 

475 continuation_indicator.data = b"" 

476 

477 with self.assertRaises(NodeNoneError): 

478 Parser.parse_frame(frame) 

479 

480 online = continuation_indicator.state.online 

481 if not isinstance(online, NodeIsOnline): 

482 raise TypeError 

483 online.graph.delete(continuation_indicator.identifier) 

484 

485 with self.assertRaises(NodeCountError): 

486 Parser.parse_frame(frame) 

487 

488 continuation_indicator = Node( 

489 data=Token(type=TokenType.MESSAGE_TYPE, value=b".") 

490 ) 

491 header.connect( 

492 Connection("has continuation indicator"), 

493 continuation_indicator, 

494 ) 

495 continuation_indicator.data.end() 

496 

497 with self.assertRaises(InvalidTokenTypeError): 

498 Parser.parse_frame(frame) 

499 

500 valid_mapping = { 

501 ContinuationIndicator.COMPLETE: b".", 

502 ContinuationIndicator.INTERMEDIATE: b"*", 

503 } 

504 

505 for result, value in valid_mapping.items(): 

506 with self.subTest( 

507 msg="Test with valid continuation indicator." 

508 ): 

509 continuation_indicator_token = Token( 

510 TokenType.CONTINUATION_INDICATOR, value=value 

511 ) 

512 continuation_indicator_token.end() 

513 continuation_indicator.data = ( 

514 continuation_indicator_token 

515 ) 

516 

517 continuation_indicator_indicator = ( 

518 Parser._get_continuation_indicator(header) 

519 ) 

520 

521 self.assertEqual( 

522 continuation_indicator_indicator, result 

523 ) 

524 

525 continuation_indicator2 = Node( 

526 data=Token( 

527 type=TokenType.CONTINUATION_INDICATOR, value=b"*" 

528 ) 

529 ) 

530 header.connect( 

531 Connection("has continuation indicator"), 

532 continuation_indicator2, 

533 ) 

534 continuation_indicator2.data.end() 

535 

536 with self.assertRaises(NodeCountError): 

537 Parser.parse_frame(frame) 

538 

539 def test_header(self) -> None: 

540 """Test parsing incorrect or missing header.""" 

541 

542 frame = self._generate_graph() 

543 

544 (header,) = frame.get_outgoing_neighbors_by_connection_data( 

545 lambda data: data == "has header" 

546 ) 

547 

548 header.data = Token( 

549 type=TokenType.CONTINUATION_INDICATOR, value=b"" 

550 ) 

551 

552 with self.assertRaises(TokenMutableError): 

553 Parser.parse_frame(frame) 

554 

555 header.data.end() 

556 

557 with self.assertRaises(InvalidTokenTypeError): 

558 Parser.parse_frame(frame) 

559 

560 header.data = b"" 

561 

562 with self.assertRaises(NodeNoneError): 

563 Parser.parse_frame(frame) 

564 

565 online = header.state.online 

566 if not isinstance(online, NodeIsOnline): 

567 raise TypeError 

568 online.graph.delete(header.identifier) 

569 

570 with self.assertRaises(NodeCountError): 

571 Parser.parse_frame(frame) 

572 

573 header = Node(data=Token(TokenType.HEADER)) 

574 frame.connect(Connection("has header"), header) 

575 header.data.end() 

576 

577 Parser._get_header(frame) 

578 

579 header2 = Node(data=Token(type=TokenType.HEADER)) 

580 header.connect( 

581 Connection("has header"), 

582 header2, 

583 ) 

584 header2.data.end() 

585 

586 with self.assertRaises(NodeCountError): 

587 Parser.parse_frame(frame) 

588 

589 def test_frame(self) -> None: 

590 """Test parsing incorrect or missing frame.""" 

591 

592 frame = self._generate_graph() 

593 

594 frame.data = Token( 

595 type=TokenType.CONTINUATION_INDICATOR, value=b"" 

596 ) 

597 

598 with self.assertRaises(TokenMutableError): 

599 Parser.parse_frame(frame) 

600 

601 frame.data.end() 

602 

603 with self.assertRaises(InvalidTokenTypeError): 

604 Parser.parse_frame(frame) 

605 

606 frame.data = b"" 

607 

608 with self.assertRaises(NodeNoneError): 

609 Parser.parse_frame(frame) 

610 

611 frame.data = Token(type=TokenType.FRAME) 

612 frame.data.end() 

613 

614 Parser.parse_frame(frame) 

615 

616 def test_payload(self) -> None: 

617 """Test parsing incorrect or missing payload.""" 

618 

619 frame = self._generate_graph() 

620 (payload,) = frame.get_outgoing_neighbors_by_connection_data( 

621 lambda data: data == "has payload" 

622 ) 

623 

624 payload.data = Token(TokenType.CONTINUATION_INDICATOR) 

625 

626 with self.assertRaises(TokenMutableError): 

627 Parser.parse_frame(frame) 

628 

629 payload.data.end() 

630 

631 with self.assertRaises(InvalidTokenTypeError): 

632 Parser.parse_frame(frame) 

633 

634 payload.data = b"" 

635 

636 with self.assertRaises(NodeNoneError): 

637 Parser.parse_frame(frame) 

638 

639 online = payload.state.online 

640 if not isinstance(online, NodeIsOnline): 

641 raise TypeError 

642 online.graph.delete(payload.identifier) 

643 

644 with self.assertRaises(NodeCountError): 

645 Parser.parse_frame(frame) 

646 

647 payload = Node(data=Token(TokenType.PAYLOAD, b"abc")) 

648 frame.connect(Connection("has payload"), payload) 

649 payload.data.end() 

650 

651 payload_data = Parser._get_payload(frame) 

652 

653 self.assertEqual(payload_data, b"abc") 

654 

655 payload2 = Node( 

656 data=Token(type=TokenType.PAYLOAD, value=b"def") 

657 ) 

658 frame.connect( 

659 Connection("has payload"), 

660 payload2, 

661 ) 

662 payload2.data.end() 

663 

664 with self.assertRaises(NodeCountError): 

665 Parser.parse_frame(frame) 

666 

667 def test_sequence_number(self) -> None: 

668 """Test parsing incorrect or missing sequence number.""" 

669 

670 frame = self._generate_graph() 

671 

672 (header,) = frame.get_outgoing_neighbors_by_connection_data( 

673 lambda data: data == "has header" 

674 ) 

675 (sequence_number,) = ( 

676 header.get_outgoing_neighbors_by_connection_data( 

677 lambda data: data == "has sequence number" 

678 ) 

679 ) 

680 sequence_number.data = Token( 

681 type=TokenType.SEQUENCE_NUMBER, 

682 value=b"999999999999999999999", 

683 ) 

684 

685 with self.assertRaises(TokenMutableError): 

686 Parser.parse_frame(frame) 

687 

688 sequence_number.data.end() 

689 

690 with self.assertRaises(AboveRangeError): 

691 Parser.parse_frame(frame) 

692 

693 sequence_number.data = b"0" 

694 

695 with self.assertRaises(NodeNoneError): 

696 Parser.parse_frame(frame) 

697 

698 online = sequence_number.state.online 

699 if not isinstance(online, NodeIsOnline): 

700 raise TypeError 

701 online.graph.delete(sequence_number.identifier) 

702 

703 with self.assertRaises(NodeCountError): 

704 Parser.parse_frame(frame) 

705 

706 sequence_number = Node( 

707 data=Token( 

708 type=TokenType.CONTINUATION_INDICATOR, value=b"0" 

709 ) 

710 ) 

711 header.connect( 

712 Connection("has sequence number"), 

713 sequence_number, 

714 ) 

715 sequence_number.data.end() 

716 

717 with self.assertRaises(InvalidTokenTypeError): 

718 Parser.parse_frame(frame) 

719 

720 valid_mapping = { 

721 0: b"0", 

722 100: b"100", 

723 2147483647: b"2147483647", 

724 } 

725 

726 for result, value in valid_mapping.items(): 

727 with self.subTest(msg="Test with valid sequence number"): 

728 sequence_number_token = Token( 

729 type=TokenType.SEQUENCE_NUMBER, value=value 

730 ) 

731 sequence_number_token.end() 

732 sequence_number.data = sequence_number_token 

733 

734 sequence_number_number = Parser._get_sequence_number( 

735 header 

736 ) 

737 

738 self.assertEqual(sequence_number_number, result) 

739 

740 sequence_number2 = Node( 

741 data=Token(type=TokenType.SEQUENCE_NUMBER, value=b"1") 

742 ) 

743 header.connect( 

744 Connection("has sequence number"), 

745 sequence_number2, 

746 ) 

747 sequence_number2.data.end() 

748 

749 with self.assertRaises(NodeCountError): 

750 Parser.parse_frame(frame)