Repository: pybel/pybel

Issue:
Update canonicalization of fusion ranges
Fusion ranges should now be output in quotes (it wasn't always like this)

Follow up to #325

Discussion:

Patch:
diff --git a/src/pybel/canonicalize.py b/src/pybel/canonicalize.py
index abda36b3..7a60ccc4 100644
--- a/src/pybel/canonicalize.py
+++ b/src/pybel/canonicalize.py
@@ -135,7 +135,7 @@ def node_to_bel(data):
         )
 
     if FUSION in data:
-        return "{}(fus({}:{}, {}, {}:{}, {}))".format(
+        return '{}(fus({}:{}, "{}", {}:{}, "{}"))'.format(
             rev_abundance_labels[data[FUNCTION]],
             data[FUSION][PARTNER_5P][NAMESPACE],
             data[FUSION][PARTNER_5P][NAME],
diff --git a/src/pybel/dsl/nodes.py b/src/pybel/dsl/nodes.py
index e4e4de5c..2530ba92 100644
--- a/src/pybel/dsl/nodes.py
+++ b/src/pybel/dsl/nodes.py
@@ -904,7 +904,7 @@ missing_fusion_range = MissingFusionRange
 
 
 class EnumeratedFusionRange(FusionRangeBase):
-    """Creates a fusion range data dictionary"""
+    """Creates a fusion range data dictionary."""
 
     def __init__(self, reference, start, stop):
         """
@@ -1000,7 +1000,7 @@ class FusionBase(BaseEntity):
 
         :rtype: str
         """
-        return "{}(fus({}:{}, {}, {}:{}, {}))".format(
+        return '{}(fus({}:{}, "{}", {}:{}, "{}"))'.format(
             rev_abundance_labels[self[FUNCTION]],
             self[FUSION][PARTNER_5P][NAMESPACE],
             self[FUSION][PARTNER_5P][NAME],
diff --git a/src/pybel/parser/modifiers/fusion.py b/src/pybel/parser/modifiers/fusion.py
index ae3ced07..3e82de7e 100644
--- a/src/pybel/parser/modifiers/fusion.py
+++ b/src/pybel/parser/modifiers/fusion.py
@@ -55,11 +55,14 @@ fusion_tags = oneOf(['fus', 'fusion']).setParseAction(replaceWith(FUSION))
 
 
 class FusionParser(BaseParser):
-    """Parses the BEL representation of gene and gene product fusions"""
+    """Parses the BEL representation of gene and gene product fusions."""
 
-    def __init__(self, identifier_parser=None):
+    def __init__(self, identifier_parser=None, permissive=True):
         """
+
         :param IdentifierParser identifier_parser: An identifier parser for checking the 3P and 5P partners
+        :param bool permissive: If true, allows being permissive to unquoted fusion ranges. Turning this off makes the
+         parser more strict and faster.
         """
         self.identifier_parser = identifier_parser if identifier_parser is not None else IdentifierParser()
         identifier = self.identifier_parser.language
@@ -68,9 +71,21 @@ class FusionParser(BaseParser):
         coordinate = pyparsing_common.integer | '?'
         missing = Keyword('?')
 
-        range_coordinate = missing(FUSION_MISSING) | (
-            reference_seq(FUSION_REFERENCE) + Suppress('.') + coordinate(FUSION_START) + Suppress('_') + coordinate(
-                FUSION_STOP))
+        range_coordinate_unquoted = (
+                missing(FUSION_MISSING) |
+                (
+                        reference_seq(FUSION_REFERENCE) +
+                        Suppress('.') +
+                        coordinate(FUSION_START) +
+                        Suppress('_') +
+                        coordinate(FUSION_STOP)
+                )
+        )
+
+        range_coordinate = Suppress('"') + range_coordinate_unquoted + Suppress('"')
+
+        if permissive:  # permissive to wrong quoting
+            range_coordinate = range_coordinate | range_coordinate_unquoted
 
         self.language = fusion_tags + nest(Group(identifier)(PARTNER_5P), Group(range_coordinate)(RANGE_5P),
                                            Group(identifier)(PARTNER_3P), Group(range_coordinate)(RANGE_3P))
@@ -82,8 +97,19 @@ def build_legacy_fusion(identifier, reference):
     break_start = (ppc.integer | '?').setParseAction(fusion_break_handler_wrapper(reference, start=True))
     break_end = (ppc.integer | '?').setParseAction(fusion_break_handler_wrapper(reference, start=False))
 
-    res = identifier(PARTNER_5P) + WCW + fusion_tags + nest(identifier(PARTNER_3P) + Optional(
-        WCW + Group(break_start)(RANGE_5P) + WCW + Group(break_end)(RANGE_3P)))
+    res = (
+            identifier(PARTNER_5P) +
+            WCW +
+            fusion_tags +
+            nest(
+                identifier(PARTNER_3P) +
+                Optional(
+                    WCW +
+                    Group(break_start)(RANGE_5P)
+                    + WCW + Group(break_end)(RANGE_3P)
+                )
+            )
+    )
 
     res.setParseAction(fusion_legacy_handler)
 


Test patch:
diff --git a/tests/test_canonicalization.py b/tests/test_canonicalization.py
index e41ff325..80f3b170 100644
--- a/tests/test_canonicalization.py
+++ b/tests/test_canonicalization.py
@@ -113,14 +113,14 @@ class TestCanonicalize(unittest.TestCase):
             partner_3p=rna(namespace='HGNC', name='ERG'),
             range_3p=fusion_range('r', 312, 5034)
         )
-        self.assertEqual('r(fus(HGNC:TMPRSS2, r.1_79, HGNC:ERG, r.312_5034))', str(node))
+        self.assertEqual('r(fus(HGNC:TMPRSS2, "r.1_79", HGNC:ERG, "r.312_5034"))', str(node))
 
     def test_rna_fusion_unspecified(self):
         node = rna_fusion(
             partner_5p=rna(namespace='HGNC', name='TMPRSS2'),
             partner_3p=rna(namespace='HGNC', name='ERG'),
         )
-        self.assertEqual('r(fus(HGNC:TMPRSS2, ?, HGNC:ERG, ?))', str(node))
+        self.assertEqual('r(fus(HGNC:TMPRSS2, "?", HGNC:ERG, "?"))', str(node))
 
         t = RNA, ('HGNC', 'TMPRSS2'), ('?',), ('HGNC', 'ERG'), ('?',)
         self.assertEqual(t, node.as_tuple())
@@ -133,7 +133,7 @@ class TestCanonicalize(unittest.TestCase):
             range_3p=fusion_range('c', 312, 5034)
         )
 
-        self.assertEqual('g(fus(HGNC:TMPRSS2, c.1_79, HGNC:ERG, c.312_5034))', str(node))
+        self.assertEqual('g(fus(HGNC:TMPRSS2, "c.1_79", HGNC:ERG, "c.312_5034"))', str(node))
         t = GENE, ('HGNC', 'TMPRSS2'), ('c', 1, 79), ('HGNC', 'ERG'), ('c', 312, 5034)
         self.assertEqual(t, node.as_tuple())
 
diff --git a/tests/test_dsl.py b/tests/test_dsl.py
index 4e80bcb7..1010ab1f 100644
--- a/tests/test_dsl.py
+++ b/tests/test_dsl.py
@@ -6,7 +6,9 @@ import unittest
 
 from pybel import BELGraph
 from pybel.constants import ABUNDANCE, COMPLEX, FUNCTION, IDENTIFIER, NAME, NAMESPACE, PROTEIN
-from pybel.dsl import abundance, complex_abundance, entity, fragment, protein
+from pybel.dsl import (
+    abundance, complex_abundance, entity, fragment, fusion_range, gene, gene_fusion, missing_fusion_range, protein,
+)
 from pybel.testing.utils import n
 from pybel.utils import ensure_quotes, hash_node
 
@@ -51,6 +53,7 @@ class TestDSL(unittest.TestCase):
         )
 
     def test_add_named_node(self):
+        """Test adding a named node to a BEL graph."""
         graph = BELGraph()
         namespace, name = n(), n()
         node = protein(namespace=namespace, name=name)
@@ -67,17 +70,24 @@ class TestDSL(unittest.TestCase):
         )
 
     def test_missing_information(self):
-        """Check that entity and abundance functions raise on missing name/identifier."""
+        """Test that entity and abundance functions raise on missing name/identifier."""
         with self.assertRaises(ValueError):
             entity(namespace='test')
 
         with self.assertRaises(ValueError):
             protein(namespace='test')
 
-    def test_str_has_name(self):
-        namespace, name = n(), n()
+    def test_abundance_as_bel_quoted(self):
+        """Test converting an abundance to BEL with a name that needs quotation."""
+        namespace, name = 'HGNC', 'YFG-1'
+        node = abundance(namespace=namespace, name=name)
+        self.assertEqual('a(HGNC:"YFG-1")', node.as_bel())
+
+    def test_abundance_as_bel(self):
+        """Test converting an abundance to BEL with a name that does not need quotation."""
+        namespace, name = 'HGNC', 'YFG'
         node = abundance(namespace=namespace, name=name)
-        self.assertEqual('a({namespace}:{name})'.format(namespace=namespace, name=ensure_quotes(name)), node.as_bel())
+        self.assertEqual('a(HGNC:YFG)', node.as_bel())
 
     def test_str_has_identifier(self):
         namespace, identifier = n(), n()
@@ -126,6 +136,34 @@ class TestDSL(unittest.TestCase):
         self.assertEqual(hash(node_tuple), hash(nine_one_one))
         self.assertEqual(hash_node(node_tuple), nine_one_one.as_sha512())
 
+    def test_gene_fusion(self):
+        """Test serialization of a gene fusion to BEL with a explicit fusion ranges."""
+        dsl = gene_fusion(
+            gene('HGNC', 'TMPRSS2'),
+            gene('HGNC', 'ERG'),
+            fusion_range('c', 1, 79),
+            fusion_range('c', 312, 5034)
+        )
+        self.assertEqual('g(fus(HGNC:TMPRSS2, "c.1_79", HGNC:ERG, "c.312_5034"))', dsl.as_bel())
+
+    def test_gene_fusion_missing_implicit(self):
+        """Test serialization of a gene fusion to BEL with a implicit missing fusion ranges."""
+        dsl = gene_fusion(
+            gene('HGNC', 'TMPRSS2'),
+            gene('HGNC', 'ERG'),
+        )
+        self.assertEqual('g(fus(HGNC:TMPRSS2, "?", HGNC:ERG, "?"))', dsl.as_bel())
+
+    def test_gene_fusion_missing_explicit(self):
+        """Test serialization of a gene fusion to BEL with an explicit missing fusion ranges."""
+        dsl = gene_fusion(
+            gene('HGNC', 'TMPRSS2'),
+            gene('HGNC', 'ERG'),
+            missing_fusion_range(),
+            missing_fusion_range(),
+        )
+        self.assertEqual('g(fus(HGNC:TMPRSS2, "?", HGNC:ERG, "?"))', dsl.as_bel())
+
 
 class TestCentralDogma(unittest.TestCase):
     """Test functions specific for :class:`CentralDogmaAbundance`s."""
@@ -135,21 +173,21 @@ class TestCentralDogma(unittest.TestCase):
         ab42 = protein(name='APP', namespace='HGNC', variants=[fragment(start=672, stop=713)])
         app = ab42.get_parent()
         self.assertEqual('p(HGNC:APP)', app.as_bel())
-        self.assertEqual('p(HGNC:APP, frag(672_713))', ab42.as_bel())
+        self.assertEqual('p(HGNC:APP, frag("672_713"))', ab42.as_bel())
 
     def test_with_variants(self):
         """Test the `with_variant` function in :class:`CentralDogmaAbundance`s."""
         app = protein(name='APP', namespace='HGNC')
         ab42 = app.with_variants(fragment(start=672, stop=713))
         self.assertEqual('p(HGNC:APP)', app.as_bel())
-        self.assertEqual('p(HGNC:APP, frag(672_713))', ab42.as_bel())
+        self.assertEqual('p(HGNC:APP, frag("672_713"))', ab42.as_bel())
 
     def test_with_variants_list(self):
         """Test the `with_variant` function in :class:`CentralDogmaAbundance`s."""
         app = protein(name='APP', namespace='HGNC')
         ab42 = app.with_variants([fragment(start=672, stop=713)])
         self.assertEqual('p(HGNC:APP)', app.as_bel())
-        self.assertEqual('p(HGNC:APP, frag(672_713))', ab42.as_bel())
+        self.assertEqual('p(HGNC:APP, frag("672_713"))', ab42.as_bel())
 
 
 if __name__ == '__main__':
diff --git a/tests/test_parse/test_parse_bel.py b/tests/test_parse/test_parse_bel.py
index e46f1cfe..4ab841fe 100644
--- a/tests/test_parse/test_parse_bel.py
+++ b/tests/test_parse/test_parse_bel.py
@@ -291,9 +291,7 @@ class TestGene(TestTokenParserBase):
 
         self.assert_has_edge(parent, expected_node, relation=HAS_VARIANT)
 
-    def test_gene_fusion_1(self):
-        self.maxDiff = None
-        statement = 'g(fus(HGNC:TMPRSS2, c.1_79, HGNC:ERG, c.312_5034))'
+    def _help_test_gene_fusion_1(self, statement):
         result = self.parser.gene.parseString(statement)
         expected_dict = {
             FUNCTION: GENE,
@@ -322,12 +320,15 @@ class TestGene(TestTokenParserBase):
         self.assertEqual(expected_dict, self.parser.graph.node[expected_node])
 
         canonical_bel = self.graph.node_to_bel(expected_node)
-        expected_canonical_bel = statement
-        self.assertEqual(expected_canonical_bel, canonical_bel)
+        self.assertEqual('g(fus(HGNC:TMPRSS2, "c.1_79", HGNC:ERG, "c.312_5034"))', canonical_bel)
 
-    def test_gene_fusion_2(self):
-        self.maxDiff = None
-        statement = 'g(fus(HGNC:TMPRSS2, c.1_?, HGNC:ERG, c.312_5034))'
+    def test_gene_fusion_1(self):
+        # no quotes
+        self._help_test_gene_fusion_1('g(fus(HGNC:TMPRSS2, c.1_79, HGNC:ERG, c.312_5034))')
+        # quotes
+        self._help_test_gene_fusion_1('g(fus(HGNC:TMPRSS2, "c.1_79", HGNC:ERG, "c.312_5034"))')
+
+    def _help_test_gene_fusion_2(self, statement):
         result = self.parser.gene.parseString(statement)
         expected_dict = {
             FUNCTION: GENE,
@@ -356,12 +357,15 @@ class TestGene(TestTokenParserBase):
         self.assertEqual(expected_dict, self.parser.graph.node[expected_node])
 
         canonical_bel = self.graph.node_to_bel(expected_node)
-        expected_canonical_bel = statement
-        self.assertEqual(expected_canonical_bel, canonical_bel)
+        self.assertEqual('g(fus(HGNC:TMPRSS2, "c.1_?", HGNC:ERG, "c.312_5034"))', canonical_bel)
 
-    def test_gene_fusion_3(self):
-        self.maxDiff = None
-        statement = 'g(fus(HGNC:TMPRSS2, ?, HGNC:ERG, c.312_5034))'
+    def test_gene_fusion_2(self):
+        # no quotes
+        self._help_test_gene_fusion_2('g(fus(HGNC:TMPRSS2, c.1_?, HGNC:ERG, c.312_5034))')
+        # correct
+        self._help_test_gene_fusion_2('g(fus(HGNC:TMPRSS2, "c.1_?", HGNC:ERG, "c.312_5034"))')
+
+    def _help_test_gene_fusion_3(self, statement):
         result = self.parser.gene.parseString(statement)
         expected_dict = {
             FUNCTION: GENE,
@@ -387,12 +391,15 @@ class TestGene(TestTokenParserBase):
         self.assertEqual(expected_dict, self.parser.graph.node[expected_node])
 
         canonical_bel = self.graph.node_to_bel(expected_node)
-        expected_canonical_bel = statement
-        self.assertEqual(expected_canonical_bel, canonical_bel)
+        self.assertEqual('g(fus(HGNC:TMPRSS2, "?", HGNC:ERG, "c.312_5034"))', canonical_bel)
 
-    def test_gene_fusion_legacy_1(self):
-        self.maxDiff = None
-        statement = 'g(HGNC:BCR, fus(HGNC:JAK2, 1875, 2626))'
+    def test_gene_fusion_3(self):
+        # no quotes
+        self._help_test_gene_fusion_3('g(fus(HGNC:TMPRSS2, ?, HGNC:ERG, c.312_5034))')
+        # correct
+        self._help_test_gene_fusion_3('g(fus(HGNC:TMPRSS2, "?", HGNC:ERG, "c.312_5034"))')
+
+    def _help_test_gene_fusion_legacy_1(self, statement):
         result = self.parser.gene.parseString(statement)
 
         expected_dict = {
@@ -425,11 +432,18 @@ class TestGene(TestTokenParserBase):
         self.assertEqual(expected_dict, self.parser.graph.node[expected_node])
 
         canonical_bel = self.graph.node_to_bel(expected_node)
-        expected_canonical_bel = 'g(fus(HGNC:BCR, c.?_1875, HGNC:JAK2, c.2626_?))'
+        expected_canonical_bel = 'g(fus(HGNC:BCR, "c.?_1875", HGNC:JAK2, "c.2626_?"))'
         self.assertEqual(expected_canonical_bel, canonical_bel)
 
-    def test_gene_fusion_legacy_2(self):
-        statement = 'g(HGNC:CHCHD4, fusion(HGNC:AIFM1))'
+    def test_gene_fusion_legacy_1(self):
+        # legacy
+        self._help_test_gene_fusion_legacy_1('g(HGNC:BCR, fus(HGNC:JAK2, 1875, 2626))')
+        # no quotes
+        self._help_test_gene_fusion_legacy_1('g(fus(HGNC:BCR, c.?_1875, HGNC:JAK2, c.2626_?))')
+        # correct
+        self._help_test_gene_fusion_legacy_1('g(fus(HGNC:BCR, "c.?_1875", HGNC:JAK2, "c.2626_?"))')
+
+    def _help_test_gene_fusion_legacy_2(self, statement):
         result = self.parser.gene.parseString(statement)
 
         expected_dict = {
@@ -452,8 +466,15 @@ class TestGene(TestTokenParserBase):
         self.assertEqual(expected_dict, self.parser.graph.node[expected_node])
 
         canonical_bel = self.graph.node_to_bel(expected_node)
-        expected_canonical_bel = 'g(fus(HGNC:CHCHD4, ?, HGNC:AIFM1, ?))'
-        self.assertEqual(expected_canonical_bel, canonical_bel)
+        self.assertEqual('g(fus(HGNC:CHCHD4, "?", HGNC:AIFM1, "?"))', canonical_bel)
+
+    def test_gene_fusion_legacy_2(self):
+        # legacy
+        self._help_test_gene_fusion_legacy_2('g(HGNC:CHCHD4, fusion(HGNC:AIFM1))')
+        # no quotes
+        self._help_test_gene_fusion_legacy_2('g(fus(HGNC:CHCHD4, ?, HGNC:AIFM1, ?))')
+        # correct
+        self._help_test_gene_fusion_legacy_2('g(fus(HGNC:CHCHD4, "?", HGNC:AIFM1, "?"))')
 
     def test_gene_variant_snp(self):
         """2.2.2 SNP"""
@@ -732,8 +753,7 @@ class TestProtein(TestTokenParserBase):
         self.assert_has_node(parent, **{FUNCTION: PROTEIN, NAMESPACE: 'HGNC', NAME: 'AKT1'})
         self.assert_has_edge(parent, node, relation=HAS_VARIANT)
 
-    def test_protein_fusion_1(self):
-        statement = 'p(fus(HGNC:TMPRSS2, p.1_79, HGNC:ERG, p.312_5034))'
+    def _help_test_protein_fusion_1(self, statement):
         result = self.parser.protein.parseString(statement)
         expected_dict = {
             FUNCTION: PROTEIN,
@@ -761,12 +781,15 @@ class TestProtein(TestTokenParserBase):
         self.assert_has_node(expected_node)
 
         canonical_bel = self.graph.node_to_bel(expected_node)
-        expected_canonical_bel = statement
-        self.assertEqual(expected_canonical_bel, canonical_bel)
+        self.assertEqual('p(fus(HGNC:TMPRSS2, "p.1_79", HGNC:ERG, "p.312_5034"))', canonical_bel)
 
-    def test_protein_fusion_legacy_1(self):
-        self.maxDiff = None
-        statement = 'p(HGNC:BCR, fus(HGNC:JAK2, 1875, 2626))'
+    def test_protein_fusion_1(self):
+        # no quotes
+        self._help_test_protein_fusion_1('p(fus(HGNC:TMPRSS2, p.1_79, HGNC:ERG, p.312_5034))')
+        # quotes
+        self._help_test_protein_fusion_1('p(fus(HGNC:TMPRSS2, "p.1_79", HGNC:ERG, "p.312_5034"))')
+
+    def _help_test_protein_fusion_legacy_1(self, statement):
         result = self.parser.protein.parseString(statement)
 
         expected_dict = {
@@ -794,11 +817,17 @@ class TestProtein(TestTokenParserBase):
         self.assert_has_node(expected_node)
 
         canonical_bel = self.graph.node_to_bel(expected_node)
-        expected_canonical_bel = 'p(fus(HGNC:BCR, p.?_1875, HGNC:JAK2, p.2626_?))'
-        self.assertEqual(expected_canonical_bel, canonical_bel)
+        self.assertEqual('p(fus(HGNC:BCR, "p.?_1875", HGNC:JAK2, "p.2626_?"))', canonical_bel)
 
-    def test_protein_fusion_legacy_2(self):
-        statement = 'p(HGNC:CHCHD4, fusion(HGNC:AIFM1))'
+    def test_protein_fusion_legacy_1(self):
+        # legacy (BEL 1.0)
+        self._help_test_protein_fusion_legacy_1('p(HGNC:BCR, fus(HGNC:JAK2, 1875, 2626))')
+        # missing quotes
+        self._help_test_protein_fusion_legacy_1('p(fus(HGNC:BCR, p.?_1875, HGNC:JAK2, p.2626_?))')
+        # correct
+        self._help_test_protein_fusion_legacy_1('p(fus(HGNC:BCR, "p.?_1875", HGNC:JAK2, "p.2626_?"))')
+
+    def _help_test_protein_legacy_fusion_2(self, statement):
         result = self.parser.protein.parseString(statement)
 
         expected_dict = {
@@ -817,11 +846,19 @@ class TestProtein(TestTokenParserBase):
         self.assert_has_node(expected_node)
 
         canonical_bel = self.graph.node_to_bel(expected_node)
-        expected_canonical_bel = 'p(fus(HGNC:CHCHD4, ?, HGNC:AIFM1, ?))'
-        self.assertEqual(expected_canonical_bel, canonical_bel)
+        self.assertEqual('p(fus(HGNC:CHCHD4, "?", HGNC:AIFM1, "?"))', canonical_bel)
 
-    def test_protein_trunc_1(self):
-        statement = 'p(HGNC:AKT1, trunc(40))'
+    def test_protein_fusion_legacy_2(self):
+        # legacy (BEL 1.0)
+        self._help_test_protein_legacy_fusion_2('proteinAbundance(HGNC:CHCHD4, fusion(HGNC:AIFM1))')
+        # legacy shorthand (BEL 1.0)
+        self._help_test_protein_legacy_fusion_2('p(HGNC:CHCHD4, fus(HGNC:AIFM1))')
+        # missing quotes
+        self._help_test_protein_legacy_fusion_2('p(fus(HGNC:CHCHD4, ?, HGNC:AIFM1, ?))')
+        # correct
+        self._help_test_protein_legacy_fusion_2('p(fus(HGNC:CHCHD4, "?", HGNC:AIFM1, "?"))')
+
+    def _help_test_protein_trunc_1(self, statement):
         result = self.parser.protein.parseString(statement)
 
         expected_node = PROTEIN, 'HGNC', 'AKT1', (HGVS, 'p.40*')
@@ -836,6 +873,14 @@ class TestProtein(TestTokenParserBase):
 
         self.assert_has_edge(protein_node, expected_node, relation=HAS_VARIANT)
 
+    def test_protein_trunc_1(self):
+        # legacy
+        self._help_test_protein_trunc_1('p(HGNC:AKT1, trunc(40))')
+        # missing quotes
+        self._help_test_protein_trunc_1('p(HGNC:AKT1, var(p.40*))')
+        # correct
+        self._help_test_protein_trunc_1('p(HGNC:AKT1, var("p.40*"))')
+
     def test_protein_trunc_2(self):
         statement = 'p(HGNC:AKT1, var(p.Cys40*))'
         result = self.parser.protein.parseString(statement)
@@ -1181,9 +1226,7 @@ class TestRna(TestTokenParserBase):
 
         self.assert_has_edge(parent, expected_node, relation=HAS_VARIANT)
 
-    def test_rna_fusion_1(self):
-        """2.6.1 RNA abundance of fusion with known breakpoints"""
-        statement = 'r(fus(HGNC:TMPRSS2, r.1_79, HGNC:ERG, r.312_5034))'
+    def _help_test_rna_fusion_1(self, statement):
         result = self.parser.rna.parseString(statement)
 
         expected_dict = {
@@ -1210,12 +1253,19 @@ class TestRna(TestTokenParserBase):
         self.assert_has_node(expected_node)
 
         canonical_bel = self.graph.node_to_bel(expected_node)
-        expected_canonical_bel = statement
-        self.assertEqual(expected_canonical_bel, canonical_bel)
-
-    def test_rna_fusion_2(self):
-        """2.6.1 RNA abundance of fusion with unspecified breakpoints"""
-        statement = 'r(fus(HGNC:TMPRSS2, ?, HGNC:ERG, ?))'
+        # TODO remove all of these: expected_canonical_bel = statement
+        self.assertEqual('r(fus(HGNC:TMPRSS2, "r.1_79", HGNC:ERG, "r.312_5034"))', canonical_bel)
+
+    def test_rna_fusion_known_breakpoints(self):
+        """Test RNA fusions (2.6.1) with known breakpoints (2.6.1)."""
+        # missing quotes
+        self._help_test_rna_fusion_1('r(fus(HGNC:TMPRSS2, r.1_79, HGNC:ERG, r.312_5034))')
+        # correct (short form)
+        self._help_test_rna_fusion_1('r(fus(HGNC:TMPRSS2, "r.1_79", HGNC:ERG, "r.312_5034"))')
+        # correct (long form)
+        self._help_test_rna_fusion_1('rnaAbundance(fusion(HGNC:TMPRSS2, "r.1_79", HGNC:ERG, "r.312_5034"))')
+
+    def _help_test_rna_fusion_unspecified_breakpoints(self, statement):
         result = self.parser.rna.parseString(statement)
 
         expected_dict = {
@@ -1236,11 +1286,18 @@ class TestRna(TestTokenParserBase):
         expected_node = node_to_tuple(result)
 
         canonical_bel = self.graph.node_to_bel(expected_node)
-        expected_canonical_bel = statement
-        self.assertEqual(expected_canonical_bel, canonical_bel)
-
-    def test_rna_fusion_legacy_1(self):
-        statement = 'r(HGNC:BCR, fus(HGNC:JAK2, 1875, 2626))'
+        self.assertEqual('r(fus(HGNC:TMPRSS2, "?", HGNC:ERG, "?"))', canonical_bel)
+
+    def test_rna_fusion_unspecified_breakpoints(self):
+        """Test RNA fusions (2.6.1) with unspecified breakpoints."""
+        # missing quotes
+        self._help_test_rna_fusion_unspecified_breakpoints('r(fus(HGNC:TMPRSS2, ?, HGNC:ERG, ?))')
+        # correct (short form)
+        self._help_test_rna_fusion_unspecified_breakpoints('r(fus(HGNC:TMPRSS2, "?", HGNC:ERG, "?"))')
+        # correct (long form)
+        self._help_test_rna_fusion_unspecified_breakpoints('rnaAbundance(fusion(HGNC:TMPRSS2, "?", HGNC:ERG, "?"))')
+
+    def _help_test_rna_fusion_legacy_1(self, statement):
         result = self.parser.rna.parseString(statement)
 
         expected_dict = {
@@ -1268,11 +1325,17 @@ class TestRna(TestTokenParserBase):
         self.assert_has_node(expected_node)
 
         canonical_bel = self.graph.node_to_bel(expected_node)
-        expected_canonical_bel = 'r(fus(HGNC:BCR, r.?_1875, HGNC:JAK2, r.2626_?))'
-        self.assertEqual(expected_canonical_bel, canonical_bel)
+        self.assertEqual('r(fus(HGNC:BCR, "r.?_1875", HGNC:JAK2, "r.2626_?"))', canonical_bel)
 
-    def test_rna_fusion_legacy_2(self):
-        statement = 'r(HGNC:CHCHD4, fusion(HGNC:AIFM1))'
+    def test_rna_fusion_legacy_1(self):
+        # legacy
+        self._help_test_rna_fusion_legacy_1('r(HGNC:BCR, fus(HGNC:JAK2, 1875, 2626))')
+        # no quotes
+        self._help_test_rna_fusion_legacy_1('r(fus(HGNC:BCR, r.?_1875, HGNC:JAK2, r.2626_?))')
+        # correct
+        self._help_test_rna_fusion_legacy_1('r(fus(HGNC:BCR, "r.?_1875", HGNC:JAK2, "r.2626_?"))')
+
+    def _help_test_rna_fusion_legacy_2(self, statement):
         result = self.parser.rna.parseString(statement)
 
         expected_dict = {
@@ -1290,8 +1353,15 @@ class TestRna(TestTokenParserBase):
         self.assert_has_node(expected_node)
 
         canonical_bel = self.graph.node_to_bel(expected_node)
-        expected_canonical_bel = 'r(fus(HGNC:CHCHD4, ?, HGNC:AIFM1, ?))'
-        self.assertEqual(expected_canonical_bel, canonical_bel)
+        self.assertEqual('r(fus(HGNC:CHCHD4, "?", HGNC:AIFM1, "?"))', canonical_bel)
+
+    def test_rna_fusion_legacy_2(self):
+        # legacy
+        self._help_test_rna_fusion_legacy_2('r(HGNC:CHCHD4, fusion(HGNC:AIFM1))')
+        # no quotes
+        self._help_test_rna_fusion_legacy_2('r(fus(HGNC:CHCHD4, ?, HGNC:AIFM1, ?))')
+        # correct
+        self._help_test_rna_fusion_legacy_2('r(fus(HGNC:CHCHD4, "?", HGNC:AIFM1, "?"))')
 
     def test_rna_variant_codingReference(self):
         """2.2.2 RNA coding reference sequence"""
diff --git a/tests/test_parse/test_parse_bel_relations.py b/tests/test_parse/test_parse_bel_relations.py
index fc5755c9..0b1fda98 100644
--- a/tests/test_parse/test_parse_bel_relations.py
+++ b/tests/test_parse/test_parse_bel_relations.py
@@ -971,37 +971,3 @@ class TestCustom(unittest.TestCase):
 
         with self.assertRaises(MissingNamespaceNameWarning):
             self.parser.protein.parseString(s)
-
-
-class TestWrite(TestTokenParserBase):
-    def test_1(self):
-        cases = [
-            ('abundance(CHEBI:"superoxide")', 'a(CHEBI:superoxide)'),
-            ('g(HGNC:AKT1,var(p.Phe508del))', 'g(HGNC:AKT1, var("p.Phe508del"))'),
-            ('geneAbundance(HGNC:AKT1, variant(p.Phe508del), sub(G,308,A), var(c.1521_1523delCTT))',
-             'g(HGNC:AKT1, var("c.1521_1523delCTT"), var("c.308G>A"), var("p.Phe508del"))'),
-            ('p(HGNC:MAPT,proteinModification(P))', 'p(HGNC:MAPT, pmod(Ph))'),
-            ('proteinAbundance(HGNC:SFN)', 'p(HGNC:SFN)'),
-            ('complex(proteinAbundance(HGNC:SFN), p(HGNC:YWHAB))', 'complex(p(HGNC:SFN), p(HGNC:YWHAB))'),
-            ('composite(proteinAbundance(HGNC:SFN), p(HGNC:YWHAB))', 'composite(p(HGNC:SFN), p(HGNC:YWHAB))'),
-            ('reaction(reactants(a(CHEBI:superoxide)),products(a(CHEBI:"oxygen"),a(CHEBI:"hydrogen peroxide")))',
-             'rxn(reactants(a(CHEBI:superoxide)), products(a(CHEBI:"hydrogen peroxide"), a(CHEBI:oxygen)))'),
-            ('rxn(reactants(a(CHEBI:superoxide)),products(a(CHEBI:"hydrogen peroxide"), a(CHEBI:"oxygen")))',
-             'rxn(reactants(a(CHEBI:superoxide)), products(a(CHEBI:"hydrogen peroxide"), a(CHEBI:oxygen)))'),
-            ('g(HGNC:AKT1, geneModification(M))', 'g(HGNC:AKT1, gmod(Me))'),
-            'g(fus(HGNC:TMPRSS2, p.1_79, HGNC:ERG, p.312_5034))',
-            'g(fus(HGNC:TMPRSS2, r.1_?, HGNC:ERG, r.312_5034))',
-            'g(fus(HGNC:TMPRSS2, r.1_79, HGNC:ERG, r.?_5034))',
-            ('g(HGNC:CHCHD4, fusion(HGNC:AIFM1))', 'g(fus(HGNC:CHCHD4, ?, HGNC:AIFM1, ?))'),
-            ('g(HGNC:CHCHD4, fusion(HGNC:AIFM1, ?, ?))', 'g(fus(HGNC:CHCHD4, ?, HGNC:AIFM1, ?))'),
-            'g(fus(HGNC:TMPRSS2, ?, HGNC:ERG, ?))',
-        ]
-
-        self.parser.bel_term.addParseAction(self.parser.handle_term)
-
-        for case in cases:
-            source_bel, expected_bel = case if 2 == len(case) else (case, case)
-
-            result = self.parser.bel_term.parseString(source_bel)
-            bel = self.graph.node_to_bel(node_to_tuple(result))
-            self.assertEqual(expected_bel, bel)