def test_03_minimal(self): """ MINIMAL_METADATA """ gnats.metadata_level = gnats.MINIMAL_METADATA db = Database(self.server, 'testdb', self.conn) self.assertEqual(db.name, 'testdb') self.assertEqual(db.description, '') self.assertEqual(db.builtin('number'), 'number') self.assertEqual(len(db.list_fields()), 8) self.assertEqual(len(db.initial_entry_fields), 0) self.assertEqual(db.fields['synopsis'].lcname, 'synopsis') self.assertEqual(db.fields['synopsis'].description, '') self.assertEqual(len(db.fields['enum-fld'].values), 0)
def test_02_no_enum(self): """ NO_ENUM_METADATA """ gnats.metadata_level = gnats.NO_ENUM_METADATA db = Database(self.server, 'testdb', self.conn) self.assertEqual(db.name, 'testdb') self.assertEqual(db.description, 'Fake Database') self.assertEqual(len(db.list_fields()), 8) self.assertEqual(db.builtin('number'), 'number') self.assertRaises(GnatsException, db._validate, 1, 1) self.assertRaises(GnatsException, db.validate_field, 1, 1) self.assertEqual(len(db.initial_entry_fields), 4) self.assertEqual(db.fields['synopsis'].lcname, 'synopsis') self.assertEqual(db.fields['synopsis'].name, 'Synopsis') self.assertEqual(db.fields['synopsis'].description, 'Synopsis field') self.assertEqual(len(db.fields['enum-fld'].values), 0) self.assertRaises(GnatsException, db.fields['enum-fld'].list_values)
class T04_DatabaseMethods(unittest.TestCase): """ Test Database utility methods """ def setUp(self): self.server = gnats.Server('somehost') self.conn = FakeServerConnectionForDB(self.server) self.db = Database(self.server, 'testdb', self.conn) def test_01_get_handle(self): """ get_handle returns DatabaseHandle """ self.assertTrue( isinstance(self.db.get_handle('user', 'pass', self.conn), DatabaseHandle)) def test_02_get_handle_doesnt_refresh(self): """ get_handle uses cached metadata when not expired """ # Change the dbdesc method to return something we can check self.conn.dbdesc = lambda __: 'changed database' self.db.get_handle('user', 'pass', self.conn) self.assertNotEquals(self.db.description, 'changed database') def test_03_get_handle_refreshes(self): """ get_handle refreshes metadata when expired """ # "Refresh" the conn, so that metadata can run again conn = FakeServerConnectionForDB(self.server) # change the config time returned by conn, which should trigger refresh conn.cfgt = lambda: u'10000' # Change the dbdesc method to return something we can check conn.dbdesc = lambda __: 'changed database' self.assertEquals(self.db.last_config_time, u'1000') self.db.get_handle('user', passwd='pass', conn=conn) self.assertEquals(self.db.description, 'changed database') self.assertEquals(self.db.last_config_time, u'10000') def test_03a_no_cfgt(self): """ get_handle refresh works when CFGT not implemented """ # "Refresh" the conn, so that metadata can run again conn = FakeServerConnectionForDB(self.server) # change the config time returned by conn, which should trigger refresh def fake_cfgt(): raise gnats.GnatsException('boo') conn.cfgt = fake_cfgt # Change the dbdesc method to return something we can check conn.dbdesc = lambda __: 'changed database' self.db.get_handle('user', passwd='pass', conn=conn) self.assertEquals(self.db.description, 'changed database') def test_04_list_fields(self): """ list_fields """ flist = self.db.list_fields() self.assertEqual(len(flist), 8) self.assertEqual(flist[1].name, 'Synopsis') self.assertEqual(flist[2].ftype, 'enum') def test_05_build_format(self): """ build_format """ self.assertEqual(self.db.build_format(['synopsis', 'enum-fld']), '"%s\037%s\036" synopsis enum-fld') def test_06_build_format_builtin(self): """ build_format with builtinfield:number """ self.assertEqual( self.db.build_format(['builtinfield:number', 'enum-fld']), '"%s\037%s\036" builtinfield:number enum-fld') def test_07_build_format_date(self): """ build_format with date """ self.assertEqual( self.db.build_format(['synopsis', 'last-modified']), '"%s\037%{%Y-%m-%d %H:%M:%S %Z}D\036" synopsis last-modified') def test_08_build_format_unknown(self): """ build_format with unknown field """ self.assertEqual( self.db.build_format(['synopsis', 'enum-fld', 'fred']), '"%s\037%s\037%s\036" synopsis enum-fld fred') def test_09_build_format_table_field(self): """ build_format with bare table field colnames""" self.assertEqual( self.db.build_format(['username', 'datetime'], table_field='change-log'), '"%s\034%{%Y-%m-%d %H:%M:%S %Z}D\035" username datetime') def test_09a_build_format_table_columns(self): """ build_format with qualified table field colnames """ self.assertEqual( self.db.build_format( ['change-log.username', 'change-log.datetime'], table_field='change-log'), '"%s\034%{%Y-%m-%d %H:%M:%S %Z}D\035" ' 'change-log.username change-log.datetime') def test_10_unparse_pr_no_from(self): """ unparse_pr raises if from: header missing """ #text = {'state':'open', 'synopsis':'This is a test',} #expected = ">Synopsis: This is a test\n>State: open" #self.assertEquals(self.conn.unparse_pr(text), expected) pr = {"foo": "bar"} self.assertRaises(gnats.GnatsException, self.db.unparse_pr, pr) def test_11_unparse_pr_no_number(self): """ unparse_pr without Number field """ # Number Synopsis Enum-fld Scoped-Enum-fld MultiEnum-fld Multitext-fld Last-Modified pr = { 'enum-fld': 'gnats', 'synopsis': 'This is a test', 'multienum-fld': '', 'multitext-fld': 'some long\ntext.', 'last-modified': '2008-01-01', 'identifier': [ ('1', { 'scoped-enum-fld': 'open' }), ], 'from:': 'me', } expected = """From: me >Synopsis: This is a test >Enum-fld: gnats >MultiEnum-fld: \n>Multitext-fld: some long text. >Scoped-Enum-fld{1}: open""" self.assertEquals(self.db.unparse_pr(pr), expected) def test_12_unparse_pr_number(self): """ unparse_pr with Number field """ # Number Synopsis Enum-fld Scoped-Enum-fld MultiEnum-fld Multitext-fld Last-Modified pr = { 'number': '100', 'enum-fld': 'gnats', 'synopsis': 'This is a test', 'multitext-fld': 'some long\ntext.', 'last-modified': '2008-01-01', 'identifier': [ ('1', { 'scoped-enum-fld': 'open' }), ], 'from:': 'me', } expected = """From: me >Number: 100 >Synopsis: This is a test >Enum-fld: gnats >Multitext-fld: some long text. >Scoped-Enum-fld{1}: open""" self.assertEquals(self.db.unparse_pr(pr), expected) def test_13_unparse_pr_empty_multi(self): """ unparse_pr with empty multitext field """ # Number Synopsis Enum-fld Scoped-Enum-fld MultiEnum-fld Multitext-fld Last-Modified pr = { 'number': '100', 'enum-fld': 'gnats', 'synopsis': 'This is a test', 'last-modified': '2008-01-01', 'multitext-fld': '', 'identifier': [ ('1', { 'scoped-enum-fld': 'open' }), ], 'from:': 'me', } expected = """From: me >Number: 100 >Synopsis: This is a test >Enum-fld: gnats >Multitext-fld: >Scoped-Enum-fld{1}: open""" self.assertEquals(self.db.unparse_pr(pr), expected) def test_14_unparse_pr_change_reason(self): """ unparse_pr with change-reason """ # Number Synopsis Enum-fld Scoped-Enum-fld MultiEnum-fld Multitext-fld Last-Modified pr = { 'number': '100', 'enum-fld': 'gnats', 'enum-fld-changed-why': 'boo', 'synopsis': 'This is a test', 'last-modified': '2008-01-01', 'identifier': [ ('1', { 'scoped-enum-fld': 'open' }), ], 'from:': 'me', } expected = """From: me >Number: 100 >Synopsis: This is a test >Enum-fld: gnats >Enum-fld-Changed-Why: boo >Scoped-Enum-fld{1}: open""" self.assertEquals(self.db.unparse_pr(pr), expected) def test_15_unparse_pr_scoped_change_reason(self): """ unparse_pr with scoped change-reason """ # Number Synopsis Enum-fld Scoped-Enum-fld MultiEnum-fld Multitext-fld Last-Modified pr = { 'number': '100', 'enum-fld': 'gnats', 'multienum-fld': '', 'synopsis': 'This is a test', 'last-modified': '2008-01-01', 'identifier': [ ('1', { 'scoped-enum-fld': 'open', 'scoped-enum-fld-changed-why': 'boo', }), ], 'from:': 'me', } expected = """From: me >Number: 100 >Synopsis: This is a test >Enum-fld: gnats >MultiEnum-fld: \n>Scoped-Enum-fld{1}: open >Scoped-Enum-fld-Changed-Why{1}: boo""" self.assertEquals(self.db.unparse_pr(pr), expected) def test_16_unparse_pr_bogus_scope(self): """ unparse_pr with bogus scope """ # Number Synopsis Enum-fld Scoped-Enum-fld MultiEnum-fld Multitext-fld Last-Modified pr = { 'number': '100', 'enum-fld': 'gnats', 'multienum-fld': 'foo:bar', 'synopsis': 'This is a test', 'last-modified': '2008-01-01', 'identifier': [ ('', { 'scoped-enum-fld': 'open', }), ], 'from:': 'me', } expected = """From: me >Number: 100 >Synopsis: This is a test >Enum-fld: gnats >MultiEnum-fld: foo:bar""" self.assertEquals(self.db.unparse_pr(pr), expected) def test_17_unparse_pr_fake_read_only(self): """ unparse_pr with fake read-only field """ # Number Synopsis Enum-fld Scoped-Enum-fld MultiEnum-fld Multitext-fld Last-Modified pr = { 'number': '100', 'enum-fld': 'gnats', 'synopsis': 'This is a test', 'multitext-fld': 'some long\ntext.', 'last-modified': '2008-01-01', 'identifier': [ ('1', { 'scoped-enum-fld': 'open' }), ], 'from:': 'me', } expected = """From: me >Number: 100 >Synopsis: This is a test >Enum-fld: gnats >Multitext-fld: some long text. >Scoped-Enum-fld{1}: open""" self.db.fields['synopsis'].read_only = True self.assertEquals(self.db.unparse_pr(pr), expected) def test_18_unparse_pr_table_field(self): """ unparse_pr skips table field even if not read_only """ # Number Synopsis Enum-fld Scoped-Enum-fld MultiEnum-fld Multitext-fld Last-Modified pr = { 'number': '100', 'enum-fld': 'gnats', 'synopsis': 'This is a test', 'multitext-fld': 'some long\ntext.', 'last-modified': '2008-01-01', 'identifier': [ ('1', { 'scoped-enum-fld': 'open' }), ], 'from:': 'me', 'change-log': 'foo', } expected = """From: me >Number: 100 >Synopsis: This is a test >Enum-fld: gnats >Multitext-fld: some long text. >Scoped-Enum-fld{1}: open""" self.db.fields['change-log'].read_only = False self.assertEquals(self.db.unparse_pr(pr), expected) def test_19_unparse_pr_multienum_as_list(self): """ unparse_pr handles multienums given as lists """ pr = { 'number': '100', 'enum-fld': 'gnats', 'synopsis': 'This is a test', 'multienum-fld': ['foo', 'bar'], 'last-modified': '2008-01-01', 'identifier': [ ('1', { 'scoped-enum-fld': 'open' }), ], 'from:': 'me', 'change-log': 'foo', } expected = """From: me >Number: 100 >Synopsis: This is a test >Enum-fld: gnats >MultiEnum-fld: foo:bar >Scoped-Enum-fld{1}: open""" self.assertEquals(self.db.unparse_pr(pr), expected) def test_20_unparse_pr_trailing_whitespace_multi(self): """ unparse_pr, multitext field with trailing whitespace """ # Number Synopsis Enum-fld Scoped-Enum-fld MultiEnum-fld Multitext-fld Last-Modified pr = { 'number': '100', 'enum-fld': 'gnats', 'synopsis': 'This is a test', 'last-modified': '2008-01-01', 'multitext-fld': 'a\ntest\n\n', 'identifier': [ ('1', { 'scoped-enum-fld': 'open' }), ], 'from:': 'me', } expected = """From: me >Number: 100 >Synopsis: This is a test >Enum-fld: gnats >Multitext-fld: a test >Scoped-Enum-fld{1}: open""" self.assertEquals(self.db.unparse_pr(pr), expected) def test_21_builtin(self): """ Check that builtin() works for last-modified """ self.assertEqual(self.db.builtin('last-modified'), 'last-modified') def test_22_builtin_non_existent(self): """ Check that builtin() returns empty string for non-existent field """ self.assertEqual(self.db.builtin('fred'), '') def test_23_add_space_beggining_multiline(self): """ unparse_pr, add a space in the beginning of multitext field, if it contains data in the format '>fld: fld-value'. """ # Number Synopsis Enum-fld Scoped-Enum-fld MultiEnum-fld Multitext-fld Last-Modified pr = { 'number': '100', 'enum-fld': 'gnats', 'synopsis': 'This is a test', 'last-modified': '2008-01-01', 'multitext-fld': 'some text\n>from: fld-value\n>fld: fld-value', 'identifier': [ ('1', { 'scoped-enum-fld': 'open' }), ], 'from:': 'me', } expected = """From: me >Number: 100 >Synopsis: This is a test >Enum-fld: gnats >Multitext-fld: some text >from: fld-value >fld: fld-value >Scoped-Enum-fld{1}: open""" self.assertEquals(self.db.unparse_pr(pr), expected)
class T04_DatabaseMethods(unittest.TestCase): """ Test Database utility methods """ def setUp(self): self.server = gnats.Server('somehost') self.conn = FakeServerConnectionForDB(self.server) self.db = Database(self.server, 'testdb', self.conn) def test_01_get_handle(self): """ get_handle returns DatabaseHandle """ self.assertTrue(isinstance(self.db.get_handle('user', 'pass', self.conn), DatabaseHandle)) def test_02_get_handle_doesnt_refresh(self): """ get_handle uses cached metadata when not expired """ # Change the dbdesc method to return something we can check self.conn.dbdesc = lambda __: 'changed database' self.db.get_handle('user', 'pass', self.conn) self.assertNotEquals(self.db.description, 'changed database') def test_03_get_handle_refreshes(self): """ get_handle refreshes metadata when expired """ # "Refresh" the conn, so that metadata can run again conn = FakeServerConnectionForDB(self.server) # change the config time returned by conn, which should trigger refresh conn.cfgt = lambda: u'10000' # Change the dbdesc method to return something we can check conn.dbdesc = lambda __: 'changed database' self.assertEquals(self.db.last_config_time, u'1000') self.db.get_handle('user', passwd='pass', conn=conn) self.assertEquals(self.db.description, 'changed database') self.assertEquals(self.db.last_config_time, u'10000') def test_03a_no_cfgt(self): """ get_handle refresh works when CFGT not implemented """ # "Refresh" the conn, so that metadata can run again conn = FakeServerConnectionForDB(self.server) # change the config time returned by conn, which should trigger refresh def fake_cfgt(): raise gnats.GnatsException('boo') conn.cfgt = fake_cfgt # Change the dbdesc method to return something we can check conn.dbdesc = lambda __: 'changed database' self.db.get_handle('user', passwd='pass', conn=conn) self.assertEquals(self.db.description, 'changed database') def test_04_list_fields(self): """ list_fields """ flist = self.db.list_fields() self.assertEqual(len(flist), 8) self.assertEqual(flist[1].name, 'Synopsis') self.assertEqual(flist[2].ftype, 'enum') def test_05_build_format(self): """ build_format """ self.assertEqual(self.db.build_format(['synopsis', 'enum-fld']), '"%s\037%s\036" synopsis enum-fld') def test_06_build_format_builtin(self): """ build_format with builtinfield:number """ self.assertEqual(self.db.build_format(['builtinfield:number', 'enum-fld']), '"%s\037%s\036" builtinfield:number enum-fld') def test_07_build_format_date(self): """ build_format with date """ self.assertEqual(self.db.build_format(['synopsis', 'last-modified']), '"%s\037%{%Y-%m-%d %H:%M:%S %Z}D\036" synopsis last-modified') def test_08_build_format_unknown(self): """ build_format with unknown field """ self.assertEqual(self.db.build_format(['synopsis', 'enum-fld', 'fred']), '"%s\037%s\037%s\036" synopsis enum-fld fred') def test_09_build_format_table_field(self): """ build_format with bare table field colnames""" self.assertEqual(self.db.build_format(['username', 'datetime'], table_field='change-log'), '"%s\034%{%Y-%m-%d %H:%M:%S %Z}D\035" username datetime') def test_09a_build_format_table_columns(self): """ build_format with qualified table field colnames """ self.assertEqual( self.db.build_format(['change-log.username', 'change-log.datetime'], table_field='change-log'), '"%s\034%{%Y-%m-%d %H:%M:%S %Z}D\035" ' 'change-log.username change-log.datetime') def test_10_unparse_pr_no_from(self): """ unparse_pr raises if from: header missing """ #text = {'state':'open', 'synopsis':'This is a test',} #expected = ">Synopsis: This is a test\n>State: open" #self.assertEquals(self.conn.unparse_pr(text), expected) pr = {"foo":"bar"} self.assertRaises(gnats.GnatsException, self.db.unparse_pr, pr) def test_11_unparse_pr_no_number(self): """ unparse_pr without Number field """ # Number Synopsis Enum-fld Scoped-Enum-fld MultiEnum-fld Multitext-fld Last-Modified pr = {'enum-fld':'gnats', 'synopsis':'This is a test', 'multienum-fld':'', 'multitext-fld':'some long\ntext.', 'last-modified':'2008-01-01', 'identifier':[('1', {'scoped-enum-fld':'open'}),], 'from:':'me',} expected = """From: me >Synopsis: This is a test >Enum-fld: gnats >MultiEnum-fld: \n>Multitext-fld: some long text. >Scoped-Enum-fld{1}: open""" self.assertEquals(self.db.unparse_pr(pr), expected) def test_12_unparse_pr_number(self): """ unparse_pr with Number field """ # Number Synopsis Enum-fld Scoped-Enum-fld MultiEnum-fld Multitext-fld Last-Modified pr = {'number':'100', 'enum-fld':'gnats', 'synopsis':'This is a test', 'multitext-fld':'some long\ntext.', 'last-modified':'2008-01-01', 'identifier':[('1', {'scoped-enum-fld':'open'}),], 'from:':'me',} expected = """From: me >Number: 100 >Synopsis: This is a test >Enum-fld: gnats >Multitext-fld: some long text. >Scoped-Enum-fld{1}: open""" self.assertEquals(self.db.unparse_pr(pr), expected) def test_13_unparse_pr_empty_multi(self): """ unparse_pr with empty multitext field """ # Number Synopsis Enum-fld Scoped-Enum-fld MultiEnum-fld Multitext-fld Last-Modified pr = {'number':'100', 'enum-fld':'gnats', 'synopsis':'This is a test', 'last-modified':'2008-01-01', 'multitext-fld':'', 'identifier':[('1', {'scoped-enum-fld':'open'}),], 'from:':'me',} expected = """From: me >Number: 100 >Synopsis: This is a test >Enum-fld: gnats >Multitext-fld: >Scoped-Enum-fld{1}: open""" self.assertEquals(self.db.unparse_pr(pr), expected) def test_14_unparse_pr_change_reason(self): """ unparse_pr with change-reason """ # Number Synopsis Enum-fld Scoped-Enum-fld MultiEnum-fld Multitext-fld Last-Modified pr = {'number':'100', 'enum-fld':'gnats', 'enum-fld-changed-why':'boo', 'synopsis':'This is a test', 'last-modified':'2008-01-01', 'identifier':[('1', {'scoped-enum-fld':'open'}),], 'from:':'me',} expected = """From: me >Number: 100 >Synopsis: This is a test >Enum-fld: gnats >Enum-fld-Changed-Why: boo >Scoped-Enum-fld{1}: open""" self.assertEquals(self.db.unparse_pr(pr), expected) def test_15_unparse_pr_scoped_change_reason(self): """ unparse_pr with scoped change-reason """ # Number Synopsis Enum-fld Scoped-Enum-fld MultiEnum-fld Multitext-fld Last-Modified pr = {'number':'100', 'enum-fld':'gnats', 'multienum-fld':'', 'synopsis':'This is a test', 'last-modified':'2008-01-01', 'identifier':[('1', {'scoped-enum-fld':'open', 'scoped-enum-fld-changed-why':'boo',}),], 'from:':'me',} expected = """From: me >Number: 100 >Synopsis: This is a test >Enum-fld: gnats >MultiEnum-fld: \n>Scoped-Enum-fld{1}: open >Scoped-Enum-fld-Changed-Why{1}: boo""" self.assertEquals(self.db.unparse_pr(pr), expected) def test_16_unparse_pr_bogus_scope(self): """ unparse_pr with bogus scope """ # Number Synopsis Enum-fld Scoped-Enum-fld MultiEnum-fld Multitext-fld Last-Modified pr = {'number':'100', 'enum-fld':'gnats', 'multienum-fld':'foo:bar', 'synopsis':'This is a test', 'last-modified':'2008-01-01', 'identifier':[('', {'scoped-enum-fld':'open',}),], 'from:':'me',} expected = """From: me >Number: 100 >Synopsis: This is a test >Enum-fld: gnats >MultiEnum-fld: foo:bar""" self.assertEquals(self.db.unparse_pr(pr), expected) def test_17_unparse_pr_fake_read_only(self): """ unparse_pr with fake read-only field """ # Number Synopsis Enum-fld Scoped-Enum-fld MultiEnum-fld Multitext-fld Last-Modified pr = {'number':'100', 'enum-fld':'gnats', 'synopsis':'This is a test', 'multitext-fld':'some long\ntext.', 'last-modified':'2008-01-01', 'identifier':[('1', {'scoped-enum-fld':'open'}),], 'from:':'me',} expected = """From: me >Number: 100 >Synopsis: This is a test >Enum-fld: gnats >Multitext-fld: some long text. >Scoped-Enum-fld{1}: open""" self.db.fields['synopsis'].read_only = True self.assertEquals(self.db.unparse_pr(pr), expected) def test_18_unparse_pr_table_field(self): """ unparse_pr skips table field even if not read_only """ # Number Synopsis Enum-fld Scoped-Enum-fld MultiEnum-fld Multitext-fld Last-Modified pr = {'number':'100', 'enum-fld':'gnats', 'synopsis':'This is a test', 'multitext-fld':'some long\ntext.', 'last-modified':'2008-01-01', 'identifier':[('1', {'scoped-enum-fld':'open'}),], 'from:':'me', 'change-log':'foo',} expected = """From: me >Number: 100 >Synopsis: This is a test >Enum-fld: gnats >Multitext-fld: some long text. >Scoped-Enum-fld{1}: open""" self.db.fields['change-log'].read_only = False self.assertEquals(self.db.unparse_pr(pr), expected) def test_19_unparse_pr_multienum_as_list(self): """ unparse_pr handles multienums given as lists """ pr = {'number':'100', 'enum-fld':'gnats', 'synopsis':'This is a test', 'multienum-fld':['foo', 'bar'], 'last-modified':'2008-01-01', 'identifier':[('1', {'scoped-enum-fld':'open'}),], 'from:':'me', 'change-log':'foo',} expected = """From: me >Number: 100 >Synopsis: This is a test >Enum-fld: gnats >MultiEnum-fld: foo:bar >Scoped-Enum-fld{1}: open""" self.assertEquals(self.db.unparse_pr(pr), expected) def test_20_unparse_pr_trailing_whitespace_multi(self): """ unparse_pr, multitext field with trailing whitespace """ # Number Synopsis Enum-fld Scoped-Enum-fld MultiEnum-fld Multitext-fld Last-Modified pr = {'number':'100', 'enum-fld':'gnats', 'synopsis':'This is a test', 'last-modified':'2008-01-01', 'multitext-fld':'a\ntest\n\n', 'identifier':[('1', {'scoped-enum-fld':'open'}),], 'from:':'me',} expected = """From: me >Number: 100 >Synopsis: This is a test >Enum-fld: gnats >Multitext-fld: a test >Scoped-Enum-fld{1}: open""" self.assertEquals(self.db.unparse_pr(pr), expected) def test_21_builtin(self): """ Check that builtin() works for last-modified """ self.assertEqual(self.db.builtin('last-modified'), 'last-modified') def test_22_builtin_non_existent(self): """ Check that builtin() returns empty string for non-existent field """ self.assertEqual(self.db.builtin('fred'), '') def test_23_add_space_beggining_multiline(self): """ unparse_pr, add a space in the beginning of multitext field, if it contains data in the format '>fld: fld-value'. """ # Number Synopsis Enum-fld Scoped-Enum-fld MultiEnum-fld Multitext-fld Last-Modified pr = {'number':'100', 'enum-fld':'gnats', 'synopsis':'This is a test', 'last-modified':'2008-01-01', 'multitext-fld':'some text\n>from: fld-value\n>fld: fld-value', 'identifier':[('1', {'scoped-enum-fld':'open'}),], 'from:':'me',} expected = """From: me >Number: 100 >Synopsis: This is a test >Enum-fld: gnats >Multitext-fld: some text >from: fld-value >fld: fld-value >Scoped-Enum-fld{1}: open""" self.assertEquals(self.db.unparse_pr(pr), expected)