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
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-31 04:18 +0200
1"""Test BEEP parser."""
3from unittest import TestCase
5from galaxy_graph import Connection, Graph, Node
6from galaxy_graph.graph import NodeIsOnline
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
22class TestParser(TestCase):
23 """Test BEEP parsing from graph Token."""
25 def _generate_graph(self) -> Node[Token]:
26 """Generate a default lexer output."""
28 graph = Graph()
29 frame = graph.add(Node(data=Token(type=TokenType.FRAME)))
30 frame.data.end()
32 header = graph.add(Node(data=Token(type=TokenType.HEADER)))
33 header.data.end()
34 frame.connect(Connection("has header"), header)
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 )
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)
79 return frame
81 def test_good(self) -> None:
82 """Test parsing a good lexer output."""
84 Parser.parse_frame(self._generate_graph())
86 def test_graph_empty(self) -> None:
87 """Test parsing an empty Node."""
89 graph = Graph()
90 node = graph.add(Node())
92 with self.assertRaises(NodeNoneError):
93 Parser.parse_frame(node)
95 def test_answer_number(self) -> None:
96 """Test parsing incorrect or missing answer number."""
98 frame = self._generate_graph()
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 )
108 with self.assertRaises(NodeCountError):
109 Parser.parse_frame(frame)
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"
118 (answer_number,) = (
119 header.get_outgoing_neighbors_by_connection_data(
120 lambda data: data == "has answer number"
121 )
122 )
124 answer_number.data = ""
126 with self.assertRaises(NodeNoneError):
127 Parser.parse_frame(frame)
129 online = header.state.online
130 if not isinstance(online, NodeIsOnline):
131 raise TypeError
133 online.graph.delete(answer_number.identifier)
135 with self.assertRaises(NodeCountError):
136 Parser.parse_frame(frame)
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 )
146 answer_number.data = Token(
147 type=TokenType.ANSWER_NUMBER,
148 value=b"999999999999999999999",
149 )
151 with self.assertRaises(TokenMutableError):
152 Parser.parse_frame(frame)
154 answer_number.data.end()
156 with self.assertRaises(AboveRangeError):
157 Parser.parse_frame(frame)
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
165 with self.assertRaises(InvalidTokenTypeError):
166 Parser.parse_frame(frame)
168 valid_mapping = {
169 0: b"0",
170 100: b"100",
171 2147483647: b"2147483647",
172 }
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
182 answer_number_number = Parser._get_answer_number(
183 header, PossibleHeaderType.ANS
184 )
186 self.assertEqual(answer_number_number, result)
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()
197 with self.assertRaises(NodeCountError):
198 Parser.parse_frame(frame)
200 def test_message_number(self) -> None:
201 """Test parsing incorrect or missing message number."""
203 frame = self._generate_graph()
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 )
218 with self.assertRaises(TokenMutableError):
219 Parser.parse_frame(frame)
221 message_number.data.end()
223 with self.assertRaises(AboveRangeError):
224 Parser.parse_frame(frame)
226 message_number.data = b"0"
228 with self.assertRaises(NodeNoneError):
229 Parser.parse_frame(frame)
231 online = message_number.state.online
232 if not isinstance(online, NodeIsOnline):
233 raise TypeError
235 online.graph.delete(message_number.identifier)
237 with self.assertRaises(NodeCountError):
238 Parser.parse_frame(frame)
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()
249 with self.assertRaises(InvalidTokenTypeError):
250 Parser.parse_frame(frame)
252 valid_mapping = {
253 0: b"0",
254 100: b"100",
255 2147483647: b"2147483647",
256 }
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
266 message_number_number = Parser._get_message_number(
267 header
268 )
270 self.assertEqual(message_number_number, result)
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()
281 with self.assertRaises(NodeCountError):
282 Parser.parse_frame(frame)
284 def test_message_type(self) -> None:
285 """Test parsing incorrect or missing message type."""
287 frame = self._generate_graph()
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 )
301 with self.assertRaises(TokenMutableError):
302 Parser.parse_frame(frame)
304 message_type.data.end()
306 with self.assertRaises(UnknownMsgTypeError):
307 Parser.parse_frame(frame)
309 message_type.data = b"MSG"
311 with self.assertRaises(NodeNoneError):
312 Parser.parse_frame(frame)
314 online = message_type.state.online
315 if not isinstance(online, NodeIsOnline):
316 raise TypeError
317 online.graph.delete(message_type.identifier)
319 with self.assertRaises(NodeCountError):
320 Parser.parse_frame(frame)
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()
328 with self.assertRaises(InvalidTokenTypeError):
329 Parser.parse_frame(frame)
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 }
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
348 message_type_type = Parser._get_message_type(header)
350 self.assertEqual(message_type_type, result)
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()
361 with self.assertRaises(NodeCountError):
362 Parser.parse_frame(frame)
364 def test_channel_number(self) -> None:
365 """Test parsing incorrect or missing channel number."""
367 frame = self._generate_graph()
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 )
382 with self.assertRaises(TokenMutableError):
383 Parser.parse_frame(frame)
385 channel_number.data.end()
387 with self.assertRaises(AboveRangeError):
388 Parser.parse_frame(frame)
390 channel_number.data = b"0"
392 with self.assertRaises(NodeNoneError):
393 Parser.parse_frame(frame)
395 online = channel_number.state.online
396 if not isinstance(online, NodeIsOnline):
397 raise TypeError
398 online.graph.delete(channel_number.identifier)
400 with self.assertRaises(NodeCountError):
401 Parser.parse_frame(frame)
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()
414 with self.assertRaises(InvalidTokenTypeError):
415 Parser.parse_frame(frame)
417 valid_mapping = {
418 0: b"0",
419 100: b"100",
420 2147483647: b"2147483647",
421 }
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
431 channel_number_number = Parser._get_channel_number(
432 header
433 )
435 self.assertEqual(channel_number_number, result)
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()
446 with self.assertRaises(NodeCountError):
447 Parser.parse_frame(frame)
449 def test_continuation_indicator(self) -> None:
450 """Test parsing incorrect or missing continuation indicator."""
452 frame = self._generate_graph()
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 )
467 with self.assertRaises(TokenMutableError):
468 Parser.parse_frame(frame)
470 continuation_indicator.data.end()
472 with self.assertRaises(UnknownContinuationIndicatorError):
473 Parser.parse_frame(frame)
475 continuation_indicator.data = b""
477 with self.assertRaises(NodeNoneError):
478 Parser.parse_frame(frame)
480 online = continuation_indicator.state.online
481 if not isinstance(online, NodeIsOnline):
482 raise TypeError
483 online.graph.delete(continuation_indicator.identifier)
485 with self.assertRaises(NodeCountError):
486 Parser.parse_frame(frame)
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()
497 with self.assertRaises(InvalidTokenTypeError):
498 Parser.parse_frame(frame)
500 valid_mapping = {
501 ContinuationIndicator.COMPLETE: b".",
502 ContinuationIndicator.INTERMEDIATE: b"*",
503 }
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 )
517 continuation_indicator_indicator = (
518 Parser._get_continuation_indicator(header)
519 )
521 self.assertEqual(
522 continuation_indicator_indicator, result
523 )
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()
536 with self.assertRaises(NodeCountError):
537 Parser.parse_frame(frame)
539 def test_header(self) -> None:
540 """Test parsing incorrect or missing header."""
542 frame = self._generate_graph()
544 (header,) = frame.get_outgoing_neighbors_by_connection_data(
545 lambda data: data == "has header"
546 )
548 header.data = Token(
549 type=TokenType.CONTINUATION_INDICATOR, value=b""
550 )
552 with self.assertRaises(TokenMutableError):
553 Parser.parse_frame(frame)
555 header.data.end()
557 with self.assertRaises(InvalidTokenTypeError):
558 Parser.parse_frame(frame)
560 header.data = b""
562 with self.assertRaises(NodeNoneError):
563 Parser.parse_frame(frame)
565 online = header.state.online
566 if not isinstance(online, NodeIsOnline):
567 raise TypeError
568 online.graph.delete(header.identifier)
570 with self.assertRaises(NodeCountError):
571 Parser.parse_frame(frame)
573 header = Node(data=Token(TokenType.HEADER))
574 frame.connect(Connection("has header"), header)
575 header.data.end()
577 Parser._get_header(frame)
579 header2 = Node(data=Token(type=TokenType.HEADER))
580 header.connect(
581 Connection("has header"),
582 header2,
583 )
584 header2.data.end()
586 with self.assertRaises(NodeCountError):
587 Parser.parse_frame(frame)
589 def test_frame(self) -> None:
590 """Test parsing incorrect or missing frame."""
592 frame = self._generate_graph()
594 frame.data = Token(
595 type=TokenType.CONTINUATION_INDICATOR, value=b""
596 )
598 with self.assertRaises(TokenMutableError):
599 Parser.parse_frame(frame)
601 frame.data.end()
603 with self.assertRaises(InvalidTokenTypeError):
604 Parser.parse_frame(frame)
606 frame.data = b""
608 with self.assertRaises(NodeNoneError):
609 Parser.parse_frame(frame)
611 frame.data = Token(type=TokenType.FRAME)
612 frame.data.end()
614 Parser.parse_frame(frame)
616 def test_payload(self) -> None:
617 """Test parsing incorrect or missing payload."""
619 frame = self._generate_graph()
620 (payload,) = frame.get_outgoing_neighbors_by_connection_data(
621 lambda data: data == "has payload"
622 )
624 payload.data = Token(TokenType.CONTINUATION_INDICATOR)
626 with self.assertRaises(TokenMutableError):
627 Parser.parse_frame(frame)
629 payload.data.end()
631 with self.assertRaises(InvalidTokenTypeError):
632 Parser.parse_frame(frame)
634 payload.data = b""
636 with self.assertRaises(NodeNoneError):
637 Parser.parse_frame(frame)
639 online = payload.state.online
640 if not isinstance(online, NodeIsOnline):
641 raise TypeError
642 online.graph.delete(payload.identifier)
644 with self.assertRaises(NodeCountError):
645 Parser.parse_frame(frame)
647 payload = Node(data=Token(TokenType.PAYLOAD, b"abc"))
648 frame.connect(Connection("has payload"), payload)
649 payload.data.end()
651 payload_data = Parser._get_payload(frame)
653 self.assertEqual(payload_data, b"abc")
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()
664 with self.assertRaises(NodeCountError):
665 Parser.parse_frame(frame)
667 def test_sequence_number(self) -> None:
668 """Test parsing incorrect or missing sequence number."""
670 frame = self._generate_graph()
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 )
685 with self.assertRaises(TokenMutableError):
686 Parser.parse_frame(frame)
688 sequence_number.data.end()
690 with self.assertRaises(AboveRangeError):
691 Parser.parse_frame(frame)
693 sequence_number.data = b"0"
695 with self.assertRaises(NodeNoneError):
696 Parser.parse_frame(frame)
698 online = sequence_number.state.online
699 if not isinstance(online, NodeIsOnline):
700 raise TypeError
701 online.graph.delete(sequence_number.identifier)
703 with self.assertRaises(NodeCountError):
704 Parser.parse_frame(frame)
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()
717 with self.assertRaises(InvalidTokenTypeError):
718 Parser.parse_frame(frame)
720 valid_mapping = {
721 0: b"0",
722 100: b"100",
723 2147483647: b"2147483647",
724 }
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
734 sequence_number_number = Parser._get_sequence_number(
735 header
736 )
738 self.assertEqual(sequence_number_number, result)
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()
749 with self.assertRaises(NodeCountError):
750 Parser.parse_frame(frame)