Index: geniuswebcore/test/geniusweb/actions/AcceptTest.py
===================================================================
--- geniuswebcore/test/geniusweb/actions/AcceptTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/actions/AcceptTest.py	(revision 67)
@@ -23,5 +23,5 @@
 	def testSerialize(self):
 		print(str(self.pyson.toJson(self.accept)))
-		self.assertEqual({'Accept': {'actor': 'myid', 'bid': {'issuevalues':{'fte': 3, 'leasecar': 'yes'}}}},\
+		self.assertEqual({'Accept': {'actor': 'myid', 'bid': {'issuevalues':{'fte': 3, 'leasecar': "yes"}}}},\
 						self.pyson.toJson(self.accept))
 		
@@ -31,5 +31,5 @@
 	
 	def testRepr(self):
-		self.assertEqual("Accept[myid,Bid{'fte': 3, 'leasecar': yes}]", repr(self.accept))
+		self.assertEqual('Accept[myid,Bid{fte=3, leasecar="yes"}]', repr(self.accept))
 		
 	def testEqual(self):
Index: geniuswebcore/test/geniusweb/actions/OfferTest.py
===================================================================
--- geniuswebcore/test/geniusweb/actions/OfferTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/actions/OfferTest.py	(revision 67)
@@ -24,5 +24,5 @@
 	def testSerialize(self):
 		print(str(self.pyson.toJson(self.offer)))
-		self.assertEqual({'Offer': {'actor': 'myid', 'bid': {'issuevalues':{'fte': 3, 'leasecar': 'yes'}}}},\
+		self.assertEqual({'Offer': {'actor': 'myid', 'bid': {'issuevalues':{'fte': 3, 'leasecar': "yes"}}}},\
 						self.pyson.toJson(self.offer))
 		
@@ -32,5 +32,5 @@
 		
 	def testRepr(self):
-		self.assertEqual("Offer[myid,Bid{'fte': 3, 'leasecar': yes}]", repr(self.offer))
+		self.assertEqual('Offer[myid,Bid{fte=3, leasecar="yes"}]', repr(self.offer))
 		
 	def testEqual(self):
Index: geniuswebcore/test/geniusweb/actions/VoteTest.py
===================================================================
--- geniuswebcore/test/geniusweb/actions/VoteTest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/actions/VoteTest.py	(revision 67)
@@ -0,0 +1,65 @@
+from decimal import Decimal
+import json
+from typing import Dict
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.actions.Action import Action
+from geniusweb.actions.Offer import Offer
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.actions.Vote import Vote
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+from geniusweb.issuevalue.NumberValue import NumberValue
+from geniusweb.issuevalue.Value import Value
+
+
+class VoteTest (unittest.TestCase) :
+    jackson = ObjectMapper()
+
+    id1 = PartyId("party1")
+    id2 = PartyId("party2")
+    issuevalues: Dict[str, Value]={}
+    issuevaluesb: Dict[str, Value] = {}
+    bid:Bid 
+    bidb:Bid 
+    ISSUE1 = "issue1";
+    VALUE1 = DiscreteValue("value1");
+    ISSUE2 = "issue2";
+    VALUE2 = NumberValue(Decimal(10));
+    # issue 2 is NUMBER and thus serializes with leading '='
+    votestring = "{\"Vote\":{\"actor\":\"party1\",\"bid\":{\"issuevalues\":{\"issue1\":\"value1\",\"issue2\":10}},\"minPower\":1,\"maxPower\":2}}";
+
+    issuevalues[ISSUE1]=VALUE1
+    issuevalues[ISSUE2]=VALUE2
+    bid = Bid(issuevalues)
+    vote1 = Vote(id1, bid, 1, 2)
+    vote1a = Vote(id1, bid, 1, 2)
+
+    vote2 = Vote(id2, bid, 1, 2)
+
+    # values swapped, so different issuevalues.
+    issuevaluesb[ISSUE1]= VALUE2
+    issuevaluesb[ISSUE2]= VALUE2
+    bidb = Bid(issuevaluesb)
+    vote3 = Vote(id1, bidb, 1, 2)
+
+    vote4 = Vote(id1, bid, 2, 2)
+    vote5 =  Vote(id1, bid, 1, 3)
+
+
+    def testSerialize(self):
+        print(self.jackson.toJson(self.vote1))
+        self.assertEqual(self.votestring, json.dumps(self.jackson.toJson(self.vote1)).replace(" ",""))
+
+
+    def testDeserialize(self):
+        act = self.jackson.parse(json.loads(self.votestring), Action)
+        self.assertEqual(self.vote1, act)
+
+    def testEqual(self):
+        self.assertEqual(self.vote1, self.vote1a)
+        self.assertNotEqual(self.vote1, self.vote2)
+        self.assertEqual(hash(self.vote1), hash(self.vote1a))
+        self.assertNotEqual(hash(self.vote1), hash(self.vote2))
Index: geniuswebcore/test/geniusweb/actions/VotesTest.py
===================================================================
--- geniuswebcore/test/geniusweb/actions/VotesTest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/actions/VotesTest.py	(revision 67)
@@ -0,0 +1,111 @@
+import json
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.actions.Action import Action
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.actions.Vote import Vote
+from geniusweb.actions.Votes import Votes
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+
+
+class VotesTest (unittest.TestCase) :
+    maxDiff = None
+
+    jackson = ObjectMapper();
+    votestring = "{\"Votes\":{\"actor\":\"partyA\",\"votes\":[{\"Vote\":{\"actor\":\"partyA\",\"bid\":{\"issuevalues\":{\"is1\":\"val1\"}},\"minPower\":2,\"maxPower\":9}},{\"Vote\":{\"actor\":\"partyA\",\"bid\":{\"issuevalues\":{\"is1\":\"val2\"}},\"minPower\":2,\"maxPower\":9}}]}}";
+
+    partyA = PartyId("partyA");
+    partyB = PartyId("partyB");
+    bid1 = Bid({"is1": DiscreteValue("val1")})
+    bid2 = Bid({"is1": DiscreteValue("val2")})
+    voteA1 = Vote(partyA, bid1, 2, 9)
+    voteA2 = Vote(partyA, bid2, 2, 9)
+    voteB1 = Vote(partyB, bid1, 2, 9)
+    voteB2 = Vote(partyB, bid2, 2, 9)
+
+    votes1 = Votes(partyA,set([voteA1, voteA2]))
+    votes1a=Votes(partyA,set([voteA1, voteA2]))
+    votes2 = Votes(partyB,set([voteB1, voteB2]))
+
+    def testRejectVoteWithIncorrectParty(self):
+        self.assertRaises(ValueError, lambda: Votes(self.partyA, set([self.voteB1])) )
+ 
+
+    def tes1Serialize(self) :
+        # FIXME why is this failing half of the time?
+        print(str(self.jackson.toJson(self.votes1)));
+        self.assertEqual(json.loads(self.votestring), self.jackson.toJson(self.votes1))
+
+    def testDeserialize(self) :
+        act = self.jackson.parse(json.loads(self.votestring), Action)
+        self.assertEqual(self.votes1, act)
+
+    def testWrongPartyName(self):
+        self.assertRaises(ValueError, lambda:\
+            Votes(self.partyA, set([self.voteB1, self.voteB2])))
+
+    def testIsExtendingTestExtraVote(self):
+        otherVotes = Votes(self.partyA, set([self.voteA1]))
+        self.assertTrue(self.votes1.isExtending(otherVotes))
+
+    def testIsExtendingTestNoVotes(self) :
+        otherVotes = Votes(self.partyA, set())
+        self.assertTrue(self.votes1.isExtending(otherVotes))
+
+    def testIsExtendingTestMissing(self):
+        otherVotes = Votes(self.partyA, set([self.voteA1]))
+        self.assertFalse(otherVotes.isExtending(self.votes1))
+
+    def testIsExtendingIdenticalVotes(self):
+        self.assertTrue(self.votes1.isExtending(self.votes1))
+
+#     @Test
+#     public void isReallyExtendingVotesMin() {
+#         Vote powervoteA1 = new Vote(partyA, bid1, 1, 9);
+#         Votes powerVotes = new Votes(partyA,
+#                 new HashSet<>(Arrays.asList(powervoteA1, voteA2)));
+#         assertTrue(powerVotes.isExtending(votes1));
+#     }
+# 
+#     @Test
+#     public void isReallyExtendingVotesMax() {
+#         Vote powervoteA1 = new Vote(partyA, bid1, 2, 10);
+#         Votes powerVotes = new Votes(partyA,
+#                 new HashSet<>(Arrays.asList(powervoteA1, voteA2)));
+#         assertTrue(powerVotes.isExtending(votes1));
+#     }
+# 
+#     @Test
+#     public void IsNarrowingOneVoteRemoved() {
+#         Votes narrowVotes = new Votes(partyA, Collections.singleton(voteA2));
+#         assertFalse(narrowVotes.isExtending(votes1));
+#     }
+# 
+#     @Test
+#     public void isNarrowingTestMoreMinPower() {
+#         Vote narrowVoteA1 = new Vote(partyA, bid1, 3, 9);
+#         Votes narrowVotes = new Votes(partyA,
+#                 new HashSet<>(Arrays.asList(narrowVoteA1, voteA2)));
+#         assertFalse(narrowVotes.isExtending(votes1));
+#         assertTrue(votes1.isExtending(narrowVotes));
+#     }
+# 
+#     @Test
+#     public void isNarrowingLessMaxPower() {
+#         Vote powervoteA1 = new Vote(partyA, bid1, 2, 8);
+#         Votes narrowVotes = new Votes(partyA,
+#                 new HashSet<>(Arrays.asList(powervoteA1, voteA2)));
+#         assertFalse(narrowVotes.isExtending(votes1));
+#         assertTrue(votes1.isExtending(narrowVotes));
+#     }
+# 
+#     @Test(expected = IllegalArgumentException.class)
+#     public void testDuplicateBidsNotAllowed() {
+#         Vote voteA1similar = new Vote(partyA, bid1, 3, 9);
+#         Votes votes = new Votes(partyA,
+#                 new HashSet<>(Arrays.asList(voteA1, voteA1similar)));
+#     }
+# }
Index: geniuswebcore/test/geniusweb/bidspace/AllBidsListTest.py
===================================================================
--- geniuswebcore/test/geniusweb/bidspace/AllBidsListTest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/bidspace/AllBidsListTest.py	(revision 67)
@@ -0,0 +1,53 @@
+from decimal import Decimal
+from typing import Collection, List, Dict
+import unittest
+
+from tudelft.utilities.immutablelist.Range import Range
+
+from geniusweb.bidspace.AllBidsList import AllBidsList
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+from geniusweb.issuevalue.DiscreteValueSet import DiscreteValueSet
+from geniusweb.issuevalue.Domain import Domain
+from geniusweb.issuevalue.NumberValue import NumberValue
+from geniusweb.issuevalue.NumberValueSet import NumberValueSet
+from geniusweb.issuevalue.ValueSet import ValueSet
+
+
+class AllBidsListTest(unittest.TestCase):
+    I1V2 = DiscreteValue("i1v2")
+    I1V1 = DiscreteValue("i1v1")
+    I2V1 = NumberValue(Decimal("2.00"))
+    I2V2 = NumberValue(Decimal("2.45"))
+    
+    I2V3 = NumberValue(Decimal("2.90"))
+    DOMAINNAME = "testdomain"
+    
+    ISSUE1 = "issue1"
+    ISSUE2 = "issue2"
+    issues:Dict[str, ValueSet]  = {}
+     
+     
+    discretevalues1:List[DiscreteValue]  = [I1V1, I1V2]
+    values1 = DiscreteValueSet(discretevalues1)
+    
+    issues[ISSUE1]= values1
+    
+    values2 = NumberValueSet(Range(Decimal("2"), Decimal("3"), Decimal("0.45")))
+    issues[ISSUE2]=values2
+    
+    domain = Domain(DOMAINNAME, issues)
+    allBidsList = AllBidsList(domain)
+
+    print(str(allBidsList))
+
+    def testListElements(self):
+        self.assertEqual(6, self.allBidsList.size())
+        allbids = [bid for bid in self.allBidsList]
+        self.assertTrue(Bid({self.ISSUE1:self.I1V1, self.ISSUE2:self.I2V1}) in allbids)
+        self.assertTrue(Bid({self.ISSUE1:self.I1V1, self.ISSUE2:self.I2V2}) in allbids)
+        self.assertTrue(Bid({self.ISSUE1:self.I1V1, self.ISSUE2:self.I2V3}) in allbids)
+        self.assertTrue(Bid({self.ISSUE1:self.I1V2, self.ISSUE2:self.I2V1}) in allbids)
+        self.assertTrue(Bid({self.ISSUE1:self.I1V2, self.ISSUE2:self.I2V1}) in allbids)
+        self.assertTrue(Bid({self.ISSUE1:self.I1V2, self.ISSUE2:self.I2V3}) in allbids)
+
Index: geniuswebcore/test/geniusweb/inform/ActionDoneTest.py
===================================================================
--- geniuswebcore/test/geniusweb/inform/ActionDoneTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/inform/ActionDoneTest.py	(revision 67)
@@ -8,4 +8,6 @@
 from geniusweb.inform.Inform import Inform
 from geniusweb.inform.YourTurn import YourTurn
+from geniusweb.inform.Voting import Voting
+from unitpy.GeneralTests import GeneralTests
 
 
Index: geniuswebcore/test/geniusweb/inform/AgreementsTest.py
===================================================================
--- geniuswebcore/test/geniusweb/inform/AgreementsTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/inform/AgreementsTest.py	(revision 67)
@@ -46,5 +46,5 @@
         
     def testRepr(self):
-        self.assertEqual("Agreements{party1: Bid{'issue1': value1}}", repr(self.agrees))
+        self.assertEqual('Agreements{party1=Bid{issue1="value1"}}', repr(self.agrees))
         
     def testEqual(self):
Index: geniuswebcore/test/geniusweb/inform/FinishedTest.py
===================================================================
--- geniuswebcore/test/geniusweb/inform/FinishedTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/inform/FinishedTest.py	(revision 67)
@@ -38,5 +38,5 @@
     
     def testRepr(self):
-        self.assertEqual("Finished[Agreements{party1: Bid{}, party2: Bid{}}]", repr(self.finished))
+        self.assertEqual("Finished[Agreements{party1=Bid{}, party2=Bid{}}]", repr(self.finished))
         
     def testEqual(self):
Index: geniuswebcore/test/geniusweb/inform/OptInTest.py
===================================================================
--- geniuswebcore/test/geniusweb/inform/OptInTest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/inform/OptInTest.py	(revision 67)
@@ -0,0 +1,55 @@
+from datetime import datetime
+import json
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+from uri.uri import URI 
+
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.actions.Vote import Vote
+from geniusweb.actions.Votes import Votes
+from geniusweb.inform.Inform import Inform
+from geniusweb.inform.OptIn import OptIn
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+
+
+class OptInTest(unittest.TestCase):
+    maxDiff = None
+        
+    bid1 =  Bid({"iss":  DiscreteValue("val1")})
+    bid2 =  Bid({"iss":  DiscreteValue("val2")})
+    partyA =  PartyId("partyA")
+    partyB =  PartyId("partyB")
+    voteA1 = Vote(partyA, bid1, 2, 9)
+    voteB1 =  Vote(partyB, bid1, 2, 9)
+    voteB2 =  Vote(partyB, bid2, 2, 9)
+
+    votesA = Votes(partyA, set([voteA1]))
+    votesB = Votes(partyB,set([voteB1, voteB2]))
+
+    optIn1 = OptIn([votesA, votesB])
+    optIn1a = OptIn([votesA, votesB])
+    optIn2 = OptIn([votesA])
+    jackson = ObjectMapper();
+
+    asJson="{\"OptIn\":{\"votes\":[{\"Votes\":{\"actor\":\"partyA\",\"votes\":[{\"Vote\":{\"actor\":\"partyA\",\"bid\":{\"issuevalues\":{\"iss\":\"val1\"}},\"minPower\":2,\"maxPower\":9}}]}},{\"Votes\":{\"actor\":\"partyB\",\"votes\":[{\"Vote\":{\"actor\":\"partyB\",\"bid\":{\"issuevalues\":{\"iss\":\"val2\"}},\"minPower\":2,\"maxPower\":9}},{\"Vote\":{\"actor\":\"partyB\",\"bid\":{\"issuevalues\":{\"iss\":\"val1\"}},\"minPower\":2,\"maxPower\":9}}]}}]}}"
+
+    def testGeneralTestData(self) :
+        self.assertEqual(self.optIn1, self.optIn1a)
+        self.assertNotEqual(self.optIn1, self.optIn2)
+        self.assertEqual(hash(self.optIn1), hash(self.optIn1a))
+        self.assertNotEqual(hash(self.optIn1), hash(self.optIn2))
+
+
+    def testSerialize(self):
+        jsonstruct = self.jackson.toJson(self.optIn1)
+        print(jsonstruct)
+        # test does not work, python keeps changing the field order...
+#         self.assertEqual(self.bla, jsonstruct)
+
+    def testDeserialize(self):
+        p = self.jackson.parse(json.loads(self.asJson), Inform)
+        print(p);
+        self.assertEqual(self.optIn1, p)
+
Index: geniuswebcore/test/geniusweb/inform/SettingsTest.py
===================================================================
--- geniuswebcore/test/geniusweb/inform/SettingsTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/inform/SettingsTest.py	(revision 67)
@@ -1,62 +1,84 @@
+from typing import Dict, Any, List
+from geniusweb.inform.Settings import Settings
+from unitpy.GeneralTests import GeneralTests
+from geniusweb.actions.PartyId import PartyId
 from datetime import datetime
+from pyson.ObjectMapper import ObjectMapper
+from geniusweb.references.ProfileRef import ProfileRef
+from uri.uri import URI
+from geniusweb.references.ProtocolRef import ProtocolRef
+from geniusweb.progress.ProgressRounds import ProgressRounds
+from geniusweb.references.Parameters import Parameters
+from geniusweb.references.PartyRef import PartyRef
+import json
+from geniusweb.inform.Inform import Inform
 import unittest
 
-from pyson.ObjectMapper import ObjectMapper
-from uri import URI #type:ignore
+class SettingsTest(unittest.TestCase, GeneralTests[Settings]):
 
-from geniusweb.actions.PartyId import PartyId
-from geniusweb.inform.Inform import Inform
-from geniusweb.inform.Settings import Settings
-from geniusweb.progress.ProgressTime import ProgressTime
-from geniusweb.references.Parameters import Parameters
-from geniusweb.references.ProfileRef import ProfileRef
-from geniusweb.references.ProtocolRef import ProtocolRef
+    id = PartyId("party1")
+    id2 = PartyId("party2")
 
+    asJson = "{\"Settings\":"\
+            + "{\"id\":\"party1\",\"profile\":\"ws:profile1\","\
+            + "\"protocol\":\"ws:localhost/protocol1\","\
+            + "\"progress\":{\"ProgressRounds\":{\"duration\":10,\"currentRound\":0,\"endtime\":999}},"\
+            + "\"parameters\":{}}}"
 
-class SettingsTest(unittest.TestCase):
-    id1 = PartyId("party1")
-    id2 = PartyId("party2")
-    profileref1 = ProfileRef(URI("profile:1"))
-    profileref2 = ProfileRef(URI("profile:2"))
-    protocolref1 = ProtocolRef(URI("protocol:1"))
-    protocolref2 = ProtocolRef(URI("protocol:2"))
-    progress1=ProgressTime(1000, datetime.fromtimestamp(12345678))
-    progress2=ProgressTime(2000, datetime.fromtimestamp(12345678))
-    parameters1=Parameters()
-    parameters2=Parameters({'a':1})
+    date = datetime.fromtimestamp(999/1000.)
+    jackson = ObjectMapper()
 
-    settings  = Settings(id1, profileref1, protocolref1, progress1, parameters1 )
-    settings1  = Settings(id1, profileref1, protocolref1, progress1, parameters1 )
-    settings2  = Settings(id2, profileref1, protocolref1, progress1, parameters1 )
-    settings3  = Settings(id1, profileref2, protocolref1, progress1, parameters1 )
-    settings4  = Settings(id1, profileref1, protocolref2, progress1, parameters1 )
-    settings5  = Settings(id1, profileref1, protocolref1, progress2, parameters1 )
+    # we can't mock because we want to test the serializer
+    profile1 = ProfileRef( URI("ws:profile1"))
+    # jackson.readValue(serialized, Profile.class);
 
-    settings1json={'Settings': {'id': 'party1', 'profile': 'profile:1', 'protocol': 'protocol:1', 'progress': {'ProgressTime': {'duration': 1000, 'start': 12345678000}}, 'parameters': {}}}
+    protocol1 = ProtocolRef(URI("ws:localhost/protocol1"))
+    protocol1a = ProtocolRef(URI("ws:localhost/protocol1"))
+    protocol2 = ProtocolRef(URI("ws:localhost/protocol2"))
+    progress1 = ProgressRounds(10, 0, date)
+    progress1a = ProgressRounds(10, 0, date)
+    progress2 = ProgressRounds(12, 0, date)
 
+    settings1 = Parameters()
+    settings2 = Parameters().With("a", 1)
 
+    negoinfo1 = Settings(id, profile1, protocol1, progress1, settings1)
+    negoinfo1a = Settings(id, profile1, protocol1a, progress1a,settings1)
+    negoinfo2 = Settings(id, profile1, protocol2, progress1, settings1)
+    negoinfo3 = Settings(id, profile1, protocol1, progress2, settings1)
+    negoinfo4 = Settings(id2, profile1, protocol1, progress2, settings1)
+    negoinfo5 = Settings(id, profile1, protocol1, progress1, settings2)
+
+    def getGeneralTestData(self)-> List[List[Settings]] :
+        return [[self.negoinfo1, self.negoinfo1a],
+                [self.negoinfo2], [self.negoinfo3],
+                [self.negoinfo4], [self.negoinfo5]]
+
+    def getGeneralTestStrings(self) -> List[str]:
+        return [
+                "Settings.*party1.*ProtocolRef.*protocol1.*ProgressRounds.*0.*10.*",
+                "Settings.*party1.*ProtocolRef.*protocol2.*ProgressRounds.*0.*10.*",
+                "Settings.*party1.*ProtocolRef.*protocol1.*ProgressRounds.*0.*12.*",
+                "Settings.*party2.*ProtocolRef.*protocol1.*ProgressRounds.*0.*12.*",
+                "Settings.*party1.*ProtocolRef.*protocol1.*ProgressRounds.*0.*10.*"]
+
+    def testSmoke(self): 
+        PartyRef(URI("ws:localhost"))
+    
+    def testNull(self):
+        self.assertRaises(ValueError,lambda: PartyRef(None))
+  
     def testSerialize(self):
-        pyson=ObjectMapper()
-        print(str(pyson.toJson(self.settings1)))
-        self.assertEqual(self.settings1json, pyson.toJson(self.settings1))
+        json1 = self.jackson.toJson(self.negoinfo1)
+        print(json1)
+        self.assertEqual(json.loads(self.asJson), json1)
         
     def testDeserialize(self):
-        pyson=ObjectMapper()
-        self.assertEqual(self.settings1, pyson.parse(self.settings1json, Inform))
-        
-    def testRepr(self):
-        self.assertEqual("Settings[party1,ProfileRef[profile:1],ProtocolRef[protocol:1],ProgressTime[12345678.0 , 1000ms],{}]", repr(self.settings))
-        
-    def testEqual(self):
-        self.assertEqual(self.settings, self.settings1)
-        self.assertNotEqual(self.settings, self.settings2)
-        self.assertNotEqual(self.settings, self.settings3)
-        self.assertNotEqual(self.settings, self.settings4)
-        self.assertNotEqual(self.settings, self.settings5)
-        self.assertEqual(hash(self.settings), hash(self.settings1))
-        self.assertNotEqual(hash(self.settings), hash(self.settings2))
-        self.assertNotEqual(hash(self.settings), hash(self.settings3))
-        self.assertNotEqual(hash(self.settings), hash(self.settings4))
-        self.assertNotEqual(hash(self.settings), hash(self.settings5))
-        
-        
+        p = self.jackson.parse(json.loads(self.asJson), Inform)
+        print(p)
+        self.assertEqual(self.negoinfo1, p)
+
+    def testGeneralDict(self):
+        val = self.jackson.parse(json.loads("{\"a\":0.3,\"b\":{\"x\":3},\"c\":[1,2,3]}"), 
+                                 Dict[str,Any])
+        print(val)
Index: geniuswebcore/test/geniusweb/inform/VotingTest.py
===================================================================
--- geniuswebcore/test/geniusweb/inform/VotingTest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/inform/VotingTest.py	(revision 67)
@@ -0,0 +1,61 @@
+import json
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.actions.Offer import Offer
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.inform.Inform import Inform
+from geniusweb.inform.Voting import Voting
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+from typing import List
+from unitpy.GeneralTests import GeneralTests
+
+
+class VotingTest(unittest.TestCase,GeneralTests[Voting]):
+    maxDiff = None
+
+    jackson = ObjectMapper();
+    party1 = PartyId("party1");
+    bid1 = Bid({"iss": DiscreteValue("val1")})
+    bid2 =  Bid({"iss": DiscreteValue("val2")})
+    
+    powers1 = {party1: 2}
+    powers2 = {party1: 3}
+    
+    voting1 = Voting([Offer(party1, bid1)], powers1)
+    voting1a = Voting([Offer(party1, bid1)], powers1)
+    voting2 = Voting([ Offer(party1, bid2)], powers1)
+    voting3 = Voting([ Offer(party1, bid1)], powers2);
+    
+    asJson="{\"Voting\":{\"offers\":[{\"Offer\":{\"actor\":\"party1\",\"bid\":{\"issuevalues\":{\"iss\":\"val1\"}}}}],\"powers\":{\"party1\":2}}}"
+
+    def getGeneralTestData(self)-> List[List[Voting]]:
+        return [[self.voting1, self.voting1a], [self.voting2], [self.voting3]]
+    
+
+    def getGeneralTestStrings(self)-> List[str]:
+        return ["Voting.*Bid.*iss.*val1.*party1.*2.*",
+                "Voting.*Bid.*iss.*val2.*party1.*2.*",
+                "Voting.*Bid.*iss.*val1.*party1.*3.*"]
+    
+
+    
+    def testGeneralTestData(self):
+        self.assertEqual(self.voting1, self.voting1a)
+        self.assertEqual(hash(self.voting1), hash(self.voting1a))
+        self.assertNotEqual(self.voting1, self.voting2)
+        self.assertNotEqual(hash(self.voting1), hash(self.voting2))
+
+    
+    def testSerialize(self) :
+        jsondict = self.jackson.toJson(self.voting1);
+        print(jsondict);
+        self.assertEqual(json.loads(self.asJson), jsondict);
+    
+    def testDeserialize(self):
+        p = self.jackson.parse(json.loads(self.asJson), Inform)
+        print(p)
+        self.assertEqual(self.voting1, p)
+    
Index: geniuswebcore/test/geniusweb/inform/YourTurnTest.py
===================================================================
--- geniuswebcore/test/geniusweb/inform/YourTurnTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/inform/YourTurnTest.py	(revision 67)
@@ -11,5 +11,5 @@
     pyson=ObjectMapper()
     yourturn  = YourTurn()
-    yourturnjson= {'YourTurn': {}}
+    yourturnjson:dict= {'YourTurn': {}}
     
 
Index: geniuswebcore/test/geniusweb/issuevalue/BidTest.py
===================================================================
--- geniuswebcore/test/geniusweb/issuevalue/BidTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/issuevalue/BidTest.py	(revision 67)
@@ -1,45 +1,126 @@
+from collections import OrderedDict
 from decimal import Decimal
+import json
+from typing import List, Dict
 import unittest
+from unittest.mock import Mock
 
 from pyson.ObjectMapper import ObjectMapper
+from unitpy.GeneralTests import GeneralTests
 
-from geniusweb.actions.PartyId import PartyId
 from geniusweb.issuevalue.Bid import Bid
 from geniusweb.issuevalue.DiscreteValue import DiscreteValue
 from geniusweb.issuevalue.NumberValue import NumberValue
+from geniusweb.issuevalue.Value import Value
 
 
-class BidTest (unittest.TestCase) :
-    pyson=ObjectMapper()
-    actor=PartyId("myid")
-    bid=Bid({'fte':NumberValue(Decimal(3)), 'leasecar':DiscreteValue('yes')})
-    bid2=Bid({'fte':NumberValue(Decimal(3)), 'leasecar':DiscreteValue('no')})
-    bid3=Bid({'fte':NumberValue(Decimal(-3)), 'leasecar':DiscreteValue('yes')})
-    
-    def testBadValue(self):
-        self.assertRaises(ValueError, lambda:Bid({"iss":3}))
+class BidTest (unittest.TestCase,  GeneralTests[Bid]):
 
-    def testSerialize(self):
-        print(str(self.pyson.toJson(self.bid)))
-        self.assertEqual({'issuevalues':{'fte': 3, 'leasecar': 'yes'}},\
-                self.pyson.toJson(self.bid))
-        self.assertEqual({'issuevalues':{'fte': -3, 'leasecar': 'yes'}},\
-                self.pyson.toJson(self.bid3))
-        
-    def testDeserialize(self):
-        jsonoffer= self.pyson.toJson(self.bid)
-        self.assertEqual(self.bid, self.pyson.parse(jsonoffer, Bid))
-    
-    def testNonValue(self):
-        self.assertRaises(ValueError, lambda: Bid({"a":12}))
-        
-    
-    def testRepr(self):
-        self.assertEqual("Bid{'fte': 3, 'leasecar': yes}", repr(self.bid))
-        
-    def testEqual(self): 
-        self.assertEqual(self.bid, self.bid)
-        self.assertNotEqual(self.bid, self.bid2)
-        self.assertEqual(hash(self.bid), hash(self.bid))        
-        self.assertNotEqual(hash(self.bid), hash(self.bid2))
-        
+    asString1 = "{\"issuevalues\":{\"issue3\":901234567.89,\"issue2\":1,\"issue1\":\"b\"}}"
+    ISSUE1 = "issue1"
+    VALUE1:Value = DiscreteValue("value1")
+    ISSUE2 = "issue2"
+    VALUE2:Value  =  NumberValue(Decimal("10"))
+    list:List[List[Bid]] = []
+
+    issuevalues:Dict[str, Value]  = OrderedDict()
+    issuevalues["issue3"]=  NumberValue(Decimal("901234567.89"))
+    issuevalues[ISSUE2]= NumberValue(Decimal(1))
+    issuevalues[ISSUE1]= DiscreteValue("b")
+    bid = Bid(issuevalues);
+
+    # different order but that shouldn't matter
+    issuevalues1:Dict[str, Value]  = OrderedDict()
+    issuevalues1["issue3"]= NumberValue(Decimal("901234567.89"))
+    issuevalues1[ISSUE2]= NumberValue(Decimal(1))
+    issuevalues1[ISSUE1]= DiscreteValue("b")
+    bid1 = Bid(issuevalues1)
+
+    issuevaluesb:Dict[str, Value] = OrderedDict()
+    issuevaluesb[ISSUE2]= NumberValue(Decimal(1))
+    issuevaluesb[ISSUE1]= DiscreteValue("b")
+    bidb = Bid(issuevaluesb)
+
+    # bidc and bidd have values swapped, so different issuevalues.
+    issuevaluesc:Dict[str, Value]  = OrderedDict()
+    issuevaluesc[ISSUE2]=VALUE2
+    issuevaluesc[ISSUE1]= VALUE1
+    bidc =  Bid(issuevaluesc)
+
+    list.append([bid, bid1])
+    list.append([bidb])
+    list.append([bidc])
+   
+    def getGeneralTestData(self)->List[List[Bid]] :
+        return self.list
+
+    def getGeneralTestStrings(self)->List[str] : 
+        return [
+                "Bid\\{issue3=901234567.89, issue2=1, issue1=\"b\"\\}",
+                "Bid\\{issue2=1, issue1=\"b\"\\}",
+                "Bid\\{issue2=10, issue1=\"value1\"\\}"]
+
+
+    def testNullConstructor(self):
+        issuevalues = None;
+        self.assertRaises(ValueError, lambda: Bid(issuevalues))
+
+    def testBidSimple(self):
+        issuevalues = {}
+        Bid(issuevalues) # no issues, no values should be ok
+
+    def testBidNull(self):
+        issuevalues = {}
+        issuevalues[self.ISSUE1]=None
+        self.assertRaises(ValueError, lambda:Bid(issuevalues)) # smokes
+
+    def testbidOkIssueValue(self):
+        issuevalues = OrderedDict()
+        issuevalues[self.ISSUE1]=Mock(Value)
+        Bid(issuevalues) # shouldn't smoke
+
+    def testSerialize(self) :
+        jackson = ObjectMapper()
+        print(str(jackson.toJson(self.bid)))
+        self.assertEqual(json.loads(self.asString1), jackson.toJson(self.bid))
+
+    def testDeserialize(self) :
+        jackson = ObjectMapper()
+        read = jackson.parse(json.loads(self.asString1), Bid)
+        self.assertEquals(self.bid, read)
+
+    def testMerge(self):
+        issuevalues = {}
+        issuevalues[self.ISSUE1]= self.VALUE1
+        partial1 = Bid(issuevalues)
+
+        issuevalues = {}
+        issuevalues[self.ISSUE2]= self.VALUE2
+        partial2 = Bid(issuevalues)
+
+        mergedbid = partial1.merge(partial2)
+        self.assertEqual(self.bidc, mergedbid)
+
+    def testMergeIssuesOverlap(self):
+        issuevalues = {}
+        issuevalues[self.ISSUE1]=self.VALUE1
+        partial1 = Bid(issuevalues)
+
+        issuevalues = {}
+        issuevalues[self.ISSUE1]=self.VALUE2
+        partial2 =  Bid(issuevalues)
+
+        self.assertRaises(ValueError, lambda:partial1.merge(partial2))
+
+    def testSmokeConstructor2(self):
+        Bid({"issue": DiscreteValue("dss")})
+
+    def testSmokeConstructor2b(self):
+        Bid({"issue": NumberValue(Decimal("0.5"))})
+
+    def testSmokeConstructor2Null1(self):
+        self.assertRaises(ValueError, lambda:Bid({None: DiscreteValue("dss")}))
+
+    def testSmokeConstructor2Null2(self):
+        self.assertRaises(ValueError, lambda: Bid({"issue": None}))
+
Index: geniuswebcore/test/geniusweb/issuevalue/DiscreteValueSetTest.py
===================================================================
--- geniuswebcore/test/geniusweb/issuevalue/DiscreteValueSetTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/issuevalue/DiscreteValueSetTest.py	(revision 67)
@@ -1,2 +1,4 @@
+import json
+from typing import List
 import unittest
 
@@ -9,29 +11,76 @@
 
 class DiscreteValueSetTest(unittest.TestCase):
-    pyson=ObjectMapper()
-    val = DiscreteValueSet([DiscreteValue("a"), DiscreteValue("b")])
-    val1 = DiscreteValueSet([DiscreteValue("a"), DiscreteValue("b")])
-    val2 = DiscreteValueSet([DiscreteValue("a"), DiscreteValue("c")])
-    valjson = {"values":["a","b"]}
 
-    def testSerialize(self):
-        print(str(self.pyson.toJson(self.val)))
-        self.assertEqual(self.valjson, self.pyson.toJson(self.val))
-        
+    A = DiscreteValue("a");
+    A1 = DiscreteValue("a");
+    B = DiscreteValue("b");
+    values:List[DiscreteValue]  = [A, B]
+    valuesReverse = [B, A]
+
+    set1 = DiscreteValueSet(values)
+    setReverse = DiscreteValueSet(valuesReverse)
+    asString = "{\"values\":[\"a\",\"b\"]}"
+    # String asString = "{\"DiscreteValueSet
+    # \":[\"a\",\"b\"]}";
+    asString1 = "[\"a\",\"b\"]"
+
+    # SPECIAL. note that the "numbers" have no leading "=" and therefore are
+    # parsed as discrete values.
+    discretenumbers = "{\"values\":[\"0\",\"1\"]}"
+    jackson = ObjectMapper()
+
+    def  testSerialize(self) :
+        jsonobj = self.jackson.toJson(self.set1)
+        print(jsonobj)
+        self.assertEqual(json.loads(self.asString), jsonobj)
+
     def testDeserialize(self):
-        self.assertEqual(self.val, self.pyson.parse(self.valjson, ValueSet))
-        
-        
-    def testIterator(self):
-        self.assertTrue(DiscreteValue("a") in self.val)
-        
-            
-    def testRepr(self):
-        self.assertEqual("DiscreteValueSet[a, b]", repr(self.val))
-        
-    def testEqual(self):
-        self.assertEqual(self.val, self.val1)
-        self.assertNotEqual(self.val, self.val2)
-        self.assertEqual(hash(self.val), hash(self.val1))
-        self.assertNotEqual(hash(self.val), hash(self.val2))
-        
+        p = self.jackson.parse(json.loads(self.asString), DiscreteValueSet)
+        print(p)
+        self.assertEqual(self.set1, p)
+
+    def testDeserializeNumberValues(self):
+        p = self.jackson.parse(json.loads(self.discretenumbers), DiscreteValueSet)
+        print(p)
+        self.assertEqual("0", p.get(0).getValue()) # not the number but string
+        self.assertEquals("1", p.get(1).getValue())
+
+    def testEquality(self):
+        self.assertEqual(self.set1, self.setReverse)
+
+    def testHashCodeInsensitiveToOrder(self):
+        self.assertEqual(hash(self.set1), hash(self.setReverse))
+
+    def testSize(self):
+        self.assertEqual(2, self.set1.size())
+
+    def testGet(self): 
+        self.assertEqual(self.A, self.set1.get(0))
+        self.assertEqual(self.B, self.set1.get(1))
+        self.assertEqual(self.A, self.set1.get(0))
+        self.assertEqual(self.B, self.set1.get(1))
+
+    def testgetValues(self):
+        self.assertEqual(self.values, self.set1.getValues())
+
+    def testConstructor2(self):
+        self.assertEqual(self.set1, DiscreteValueSet([self.A, self.B]))
+
+    def testContains(self):
+        self.assertTrue(self.set1.contains(self.A))
+        self.assertTrue(self.set1.contains(self.A1))
+        self.assertTrue(self.set1.contains(self.B))
+        self.assertFalse(self.set1.contains(DiscreteValue("c")))
+        self.assertFalse(self.set1.contains(None))
+
+    def testiterator1(self) :
+        it = iter(self.set1);
+        self.assertEqual(self.A, next(it));
+        self.assertEqual(self.B, next(it));
+        self.assertRaises(StopIteration, lambda: next(it))
+
+    def testiterator2(self): 
+        it = iter(self.setReverse)
+        self.assertEqual(self.B, next(it))
+        self.assertEqual(self.A, next(it));
+        self.assertRaises(StopIteration,lambda: next(it))
Index: geniuswebcore/test/geniusweb/issuevalue/DiscreteValueTest.py
===================================================================
--- geniuswebcore/test/geniusweb/issuevalue/DiscreteValueTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/issuevalue/DiscreteValueTest.py	(revision 67)
@@ -1,5 +1,9 @@
+import json
+from pickle import NONE
+from typing import List
 import unittest
 
 from pyson.ObjectMapper import ObjectMapper
+from unitpy.GeneralTests import GeneralTests
 
 from geniusweb.issuevalue.DiscreteValue import DiscreteValue
@@ -7,25 +11,26 @@
 
 
-class DiscreteValueTest(unittest.TestCase):
-    pyson=ObjectMapper()
-    val = DiscreteValue('val')
-    val1 = DiscreteValue('val')
-    val2 = DiscreteValue('val2')
-    valjson = 'val'
+class DiscreteValueTest(unittest.TestCase, GeneralTests[DiscreteValue]):
+    value =  DiscreteValue("a");
+    value1 =  DiscreteValue("a");
+    valueb =  DiscreteValue("b");
+    serialized = "\"a\"";
+    jackson =  ObjectMapper();
 
     def testSerialize(self):
-        print(str(self.pyson.toJson(self.val)))
-        self.assertEqual(self.valjson, self.pyson.toJson(self.val))
+        print(str(self.jackson.toJson(self.value)))
+        self.assertEqual(json.loads(self.serialized), self.jackson.toJson(self.value))
         
-    def testDeserialize(self):
-        self.assertEqual(self.val, self.pyson.parse(self.valjson, Value))
+    def testDeserialize(self) :
+        self.assertEqual(self.value, self.jackson.parse(json.loads(self.serialized), DiscreteValue))
+ 
+    def testDeserializeFromValue(self):
+        self.assertEqual(self.value, self.jackson.parse(json.loads(self.serialized), Value))
         
-    def testRepr(self):
-        self.assertEqual('val', repr(self.val))
-        
-    def testEqual(self):
-        self.assertEqual(self.val, self.val1)
-        self.assertNotEqual(self.val, self.val2)
-        self.assertEqual(hash(self.val), hash(self.val1))
-        self.assertNotEqual(hash(self.val), hash(self.val2))
-        
+
+    def getGeneralTestData(self)->List[List[DiscreteValue]] :
+        return [ [self.value, self.value1], [self.valueb]]
+
+    def getGeneralTestStrings(self)->List[str]:
+        return [self.serialized, "\"b\""]
+    
Index: geniuswebcore/test/geniusweb/issuevalue/DomainTest.py
===================================================================
--- geniuswebcore/test/geniusweb/issuevalue/DomainTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/issuevalue/DomainTest.py	(revision 67)
@@ -1,2 +1,3 @@
+from decimal import Decimal
 import json
 import unittest
@@ -12,4 +13,5 @@
 
 class DomainTest (unittest.TestCase) :
+    maxDiff=None
 
     pyson=ObjectMapper()
@@ -55,11 +57,41 @@
         self.assertEqual(self.jobs, self.pyson.parse(self.jobsjson, Domain))
     
+    def testInit(self):
+        self.assertRaises(ValueError, lambda: Domain("name",None))
+        self.assertRaises(ValueError, lambda: Domain(None,{"issue"}))
+        self.assertRaises(ValueError, lambda: Domain("_invalid_name",{"issue"}))
+        self.assertRaises(ValueError, lambda: Domain("name",{}))
+        
     def testIsFitting(self):
         self.assertEqual(None,self.jobs.isFitting(Bid({"lease car":DiscreteValue("no")})))
-        self.assertTrue("illegal value maybe" in self.jobs.isFitting(Bid({"lease car":DiscreteValue("maybe")})))
+        self.assertTrue("refers to non-domain issue" in self.jobs.isFitting(Bid({"no such issue":DiscreteValue("no")})))
+        self.assertNotEqual(None,self.jobs.isFitting(Bid({"lease car":NumberValue(Decimal(1))})))
+        self.assertTrue('illegal value' in self.jobs.isFitting(Bid({"lease car":DiscreteValue("maybe")})))
+     
+    def testIsComplete(self):
+        self.assertEqual(None,self.jobs.isComplete(Bid({"lease car":DiscreteValue("no"),
+                                                        "permanent contract":DiscreteValue("yes"),
+                                                        "career development opportunities":DiscreteValue("high"),
+                                                        "fte":DiscreteValue("1.0"),
+                                                        "salary":DiscreteValue("3000"),
+                                                        "work from home":DiscreteValue("1")
+                                                        })))
+        self.assertTrue("do not match issues in domain" in self.jobs.isComplete(Bid({"lease car":DiscreteValue("no"),
+                                                        "permanent contract":DiscreteValue("yes"),
+                                                        "work from home":DiscreteValue("1")
+                                                        })))
         
+    def testGetValues(self):
+        self.assertEqual(self.jobs.getValues("lease car"),self.yesno)
+        
+    def testgetIssues(self):
+        self.assertEqual(self.jobs.getIssues(),{'permanent contract', 'fte', 'lease car', 'career development opportunities', 'salary', 'work from home'})
+               
+    def testgetName(self):
+        self.assertEqual(self.jobs.getName(),"jobs")
+
     def testRepr(self):
-        self.assertEqual("Domain[jobs,{'lease car': DiscreteValueSet[yes, no], 'permanent contract': DiscreteValueSet[yes, no], 'career development opportunities': DiscreteValueSet[low, medium, high], 'fte': DiscreteValueSet[0.6, 0.8, 1.0], 'salary': DiscreteValueSet[2000, 2500, 3000, 3500, 4000], 'work from home': DiscreteValueSet[0, 1, 2]}]", \
-                            repr(self.jobs))
+        self.assertEqual("Domain[jobs,{'lease car': DiscreteValueSet[\"yes\", \"no\"], 'permanent contract': DiscreteValueSet[\"yes\", \"no\"], 'career development opportunities': DiscreteValueSet[\"low\", \"medium\", \"high\"], 'fte': DiscreteValueSet[\"0.6\", \"0.8\", \"1.0\"], 'salary': DiscreteValueSet[\"2000\", \"2500\", \"3000\", \"3500\", \"4000\"], 'work from home': DiscreteValueSet[\"0\", \"1\", \"2\"]}]"\
+                         ,repr(self.jobs))
         
     def testEqual(self):
Index: geniuswebcore/test/geniusweb/issuevalue/NumberValueSetTest.py
===================================================================
--- geniuswebcore/test/geniusweb/issuevalue/NumberValueSetTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/issuevalue/NumberValueSetTest.py	(revision 67)
@@ -17,4 +17,7 @@
     valjson = {"range":{"low":12.2,"high":12.6,"step":0.3}}
 
+    def testInit(self):
+        self.assertRaises(ValueError, lambda: NumberValueSet(range(1,5)))
+        
     def testSerialize(self):
         print(str(self.pyson.toJson(self.val)))
Index: geniuswebcore/test/geniusweb/issuevalue/NumberValueTest.py
===================================================================
--- geniuswebcore/test/geniusweb/issuevalue/NumberValueTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/issuevalue/NumberValueTest.py	(revision 67)
@@ -1,6 +1,9 @@
 from _decimal import Decimal
+import json
+from typing import List, Any
 import unittest
 
 from pyson.ObjectMapper import ObjectMapper
+from unitpy.GeneralTests import GeneralTests
 
 from geniusweb.issuevalue.NumberValue import NumberValue
@@ -8,28 +11,71 @@
 
 
-class NumberValueTest(unittest.TestCase):
-    pyson=ObjectMapper()
-    val = NumberValue(Decimal('0.1237'))
-    val2 = NumberValue(Decimal('2'))
-    valjson = 0.1237
+class NumberValueTest(unittest.TestCase, GeneralTests[NumberValue]):
+    value =  NumberValue(Decimal("12.31"))
+    value1 =  NumberValue(Decimal("12.31"))
+    value1c = NumberValue(Decimal("1.231E+1"))
+    valueb =  NumberValue(Decimal("12.310000000000000001"))
+    serialized = "12.31"
 
-    def testSerialize(self):
-        print(str(self.pyson.toJson(self.val)))
-        self.assertEqual(self.valjson, self.pyson.toJson(self.val))
+    bigserialized = "8748703924870439218876954320948372058"
+    bigvalue = NumberValue(Decimal(bigserialized))
+    jackson = ObjectMapper()
+
+    def getGeneralTestData(self)->List[List[NumberValue]]:
+        return [ [self.value, self.value1, self.value1c], [self.valueb]]
+    
+    def  getGeneralTestStrings(self)->List[str]:
+        return ["12.31", "12.310000000000000001"]
+
+    def testSerialize(self) :
+        self.assertEquals(json.loads(self.serialized), self.jackson.toJson(self.value))
+
+    def testDeserialize(self):
+        self.assertEqual(self.value, self.jackson.parse(json.loads(self.serialized), NumberValue))
+
+    def  testDeserializeFromValue(self):
+        self.assertEqual(self.value, self.jackson.parse(json.loads(self.serialized), Value))
+
+    def testBigSerialize(self):
+        self.assertEqual(json.loads(self.bigserialized), self.jackson.toJson(self.bigvalue))
+
+    def testBigDeserialize(self):
+        self.assertEquals(self.bigvalue,
+                self.jackson.parse(json.loads(self.bigserialized), NumberValue))
+
+    def testSerializeShorts(self):
+        print(self.jackson.toJson([ 1, 2, 3, 4, 5 ]))
+
+    def testDeserializeShorts(self) :
+        list = self.jackson.parse(json.loads("[1,2,3,4,5]"),List[Any])
+        print(list)
+        print(list[0].__class__)
+
+    def testDeserializeMix(self):
+        list = self.jackson.parse(json.loads('["short",1,2,3,4,5]'), List[Any])
+        print(list)
+        print(list[0].__class__)
+
+    def testDeserializePlainNumber(self):
+        '''
+        Showing that we CAN deserialize big numbers without double quotes
+        correctly, if we tell jackson upfront that it's a BigDecimal.
+        '''
+        valstr = "483958743698732691487326987569213874694328974329874328947320984372498327493827432987231874681273648127";
+        val = self.jackson.parse(valstr, Decimal)
+        self.assertEqual(valstr, str(val))
+
+
+    def testNumberBlab(self):
+        # jackson.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
+        self.assertEqual(self.value, self.jackson.parse(json.loads("12.31"), Value))
         
-    def testDeserialize(self):
-        self.assertEqual(self.val, self.pyson.parse(self.valjson, Value))
-        
-    def testSerialize2(self):
-        print(str(self.pyson.toJson(NumberValue(Decimal("900")))))
-        
-    def testNonDecimal(self):
-        self.assertRaises(ValueError, lambda:NumberValue(1200))
+    def testNonNumber(self):
+        self.assertRaises(ValueError, lambda: NumberValue("abc"))
 
-    def testHash(self):
-        self.assertEqual(self.val,self.val)
-        self.assertNotEqual(self.val,self.val2)
-        self.assertNotEqual(hash(self.val), hash(self.val2))
-        
-    def testRepr(self):
-        self.assertEqual('2', repr(self.val2))
+    def testDeserializeScientificNotation(self):
+        valstr = "9E+2";
+        val = self.jackson.parse(json.loads(valstr), Value)
+        # self.assertEqual(valstr, str(val)) FIXME? 
+        self.assertEqual(val, NumberValue(Decimal("900")))
+    
Index: geniuswebcore/test/geniusweb/issuevalue/ValueDeserializerTest.py
===================================================================
--- geniuswebcore/test/geniusweb/issuevalue/ValueDeserializerTest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/issuevalue/ValueDeserializerTest.py	(revision 67)
@@ -0,0 +1,16 @@
+import unittest
+from decimal import Decimal
+
+from geniusweb.issuevalue.ValueDeserializer import ValueDeserializer
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+from geniusweb.issuevalue.NumberValue import NumberValue
+
+class ValueDeserializerTest(unittest.TestCase):
+       
+    val = ValueDeserializer()
+    
+    def testValueDeserializer(self):
+        #self.assertEqual(DiscreteValue(123),self.val.deserialize(123,1))
+        self.assertEqual(DiscreteValue("123"),self.val.deserialize("123",1))
+        self.assertEqual(NumberValue(Decimal(123)), self.val.deserialize(123,1))
+        self.assertRaises(ValueError,lambda: self.val.deserialize([1,2,3],1))
Index: geniuswebcore/test/geniusweb/party/DefaultPartyTest.py
===================================================================
--- geniuswebcore/test/geniusweb/party/DefaultPartyTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/party/DefaultPartyTest.py	(revision 67)
@@ -2,4 +2,5 @@
 from geniusweb.party.DefaultParty import DefaultParty       
 from geniusweb.party.Capabilities import Capabilities
+from geniusweb.inform.Inform import Inform
       
 class MyParty(DefaultParty):
@@ -10,5 +11,5 @@
         return "blabla"
     
-    def notifyChange(self, data:int):
+    def notifyChange(self, data:Inform):
         # you'd think the type checker would complain here. data is NOT int
         return 
Index: geniuswebcore/test/geniusweb/partystdio/EmptyParty.py
===================================================================
--- geniuswebcore/test/geniusweb/partystdio/EmptyParty.py	(revision 66)
+++ geniuswebcore/test/geniusweb/partystdio/EmptyParty.py	(revision 67)
@@ -2,5 +2,5 @@
 import logging
 import sys
-from typing import cast
+from typing import cast, Optional, List
 
 from tudelft_utilities_logging.Reporter import Reporter 
@@ -21,6 +21,6 @@
         super().__init__(reporter)
         self.getReporter().log(logging.INFO,"party is initialized")
-        self._lastReceivedBid:Bid = None
-        self.received=[]
+        self._lastReceivedBid:Optional[Bid] = None
+        self.received:List[Inform]=[]
 
     # Override
Index: geniuswebcore/test/geniusweb/profile/utilityspace/DiscreteValSetUtilsTest.py
===================================================================
--- geniuswebcore/test/geniusweb/profile/utilityspace/DiscreteValSetUtilsTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/profile/utilityspace/DiscreteValSetUtilsTest.py	(revision 67)
@@ -37,5 +37,5 @@
 
     def testRepr(self):
-        self.assertEqual("DiscreteValueSetUtilities{high: Decimal('1'), low: Decimal('0'), medium: Decimal('0.5')}", repr(self.carreerutils))
+        self.assertEqual('DiscreteValueSetUtilities{"high"=1, "low"=0, "medium"=0.5}', repr(self.carreerutils))
         
     def testEqual(self):
Index: geniuswebcore/test/geniusweb/profile/utilityspace/LinearAdditiveTest.py
===================================================================
--- geniuswebcore/test/geniusweb/profile/utilityspace/LinearAdditiveTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/profile/utilityspace/LinearAdditiveTest.py	(revision 67)
@@ -1,9 +1,12 @@
+from collections import OrderedDict
 from decimal import Decimal
 import json
 from pathlib import Path
-from typing import Dict
+from typing import List, Dict
 import unittest
+from unittest.mock import Mock
 
 from pyson.ObjectMapper import ObjectMapper
+from unitpy.GeneralTests import GeneralTests
 
 from geniusweb.issuevalue.Bid import Bid
@@ -12,111 +15,226 @@
 from geniusweb.issuevalue.Domain import Domain
 from geniusweb.issuevalue.NumberValue import NumberValue
+from geniusweb.issuevalue.Value import Value
 from geniusweb.issuevalue.ValueSet import ValueSet
 from geniusweb.profile.Profile import Profile
 from geniusweb.profile.utilityspace.DiscreteValueSetUtilities import DiscreteValueSetUtilities
 from geniusweb.profile.utilityspace.LinearAdditiveUtilitySpace import LinearAdditiveUtilitySpace
+from geniusweb.profile.utilityspace.UtilitySpace import UtilitySpace
 from geniusweb.profile.utilityspace.ValueSetUtilities import ValueSetUtilities
 
 
-class LinearAdditiveTest(unittest.TestCase):
-    pyson=ObjectMapper()
-
-    yesno = DiscreteValueSet([DiscreteValue("yes"), DiscreteValue("no")])
-    leasecarvals = yesno
-    permcontractvals = yesno
-    carreervals=DiscreteValueSet([DiscreteValue("low"),DiscreteValue("medium"),DiscreteValue("high")])
-    ftevals=DiscreteValueSet([DiscreteValue("0.6"),DiscreteValue("0.8"),DiscreteValue("1.0")])
-    salaryvals=DiscreteValueSet([DiscreteValue("2000"),DiscreteValue("2500"),
-         DiscreteValue("3000"),DiscreteValue("3500"),DiscreteValue("4000")])
-    workfromhomevals=DiscreteValueSet([DiscreteValue("0"),DiscreteValue("1"),DiscreteValue("2")])
-     
-    jobsdomain = Domain("jobs", {"lease car":leasecarvals, "permanent contract":permcontractvals, \
-            "career development opportunities":carreervals, "fte":ftevals, \
-            "salary": salaryvals, "work from home": workfromhomevals  })
-    N0=Decimal("0")
-    N1=Decimal("1")
-    N025=Decimal("0.25")
-    N03=Decimal("0.3")
-    N05=Decimal("0.5")
-    N075=Decimal("0.75")
-
-    leasevarutils = DiscreteValueSetUtilities({DiscreteValue("no"):N0, DiscreteValue("yes"):N1})
-    leasevarutils2 = DiscreteValueSetUtilities({DiscreteValue("no"):N0, DiscreteValue("yes"):N075})
-    permcontrutils = DiscreteValueSetUtilities({DiscreteValue("no"):N0, DiscreteValue("yes"):N1})
-    carreerutils = DiscreteValueSetUtilities({DiscreteValue("high"):N1,DiscreteValue("low"):N0,DiscreteValue("medium"):N05})
-    fteutils = DiscreteValueSetUtilities({DiscreteValue("1.0"):N075,DiscreteValue("0.6"):N025,DiscreteValue("0.8"):N05})
-    salaryutils = DiscreteValueSetUtilities({DiscreteValue("4000"):N1,DiscreteValue("2500"):N025,DiscreteValue("3500"):N075,
-                     DiscreteValue("2000"):N0,DiscreteValue("3000"):N03})
-    fromhomeutils = DiscreteValueSetUtilities({DiscreteValue("1"):N05,DiscreteValue("2"):Decimal("0.666666666666"),
-                   DiscreteValue("0"):Decimal("0.333333333")})
-    utils:Dict[str, ValueSetUtilities] = {'lease car': leasevarutils, 'permanent contract':permcontrutils,  'career development opportunities': carreerutils,
-             'fte':fteutils, 'salary':salaryutils, 'work from home':fromhomeutils }
-    utils2:Dict[str, ValueSetUtilities] = {'lease car': leasevarutils2, 'permanent contract':permcontrutils,  'career development opportunities': carreerutils,
-             'fte':fteutils, 'salary':salaryutils, 'work from home':fromhomeutils }
-    weights:Dict[str, Decimal] = {"lease car":Decimal("0.06"),"permanent contract":Decimal("0.16"),
-            "career development opportunities":Decimal("0.04"),"fte":Decimal("0.32"),
-            "salary":Decimal("0.24"),"work from home":Decimal("0.18")}
-    weights2:Dict[str, Decimal] = {"lease car":Decimal("0.06"),"permanent contract":Decimal("0.16"),
-            "career development opportunities":Decimal("0.05"),"fte":Decimal("0.31"),
-            "salary":Decimal("0.24"),"work from home":Decimal("0.18")}
-
-    jobs=LinearAdditiveUtilitySpace(jobsdomain, "jobs1",utils, weights )
-    jobs1=LinearAdditiveUtilitySpace(jobsdomain, "jobs1",utils, weights )
-    jobs2=LinearAdditiveUtilitySpace(jobsdomain, "jobs2",utils, weights )
-    jobs3=LinearAdditiveUtilitySpace(jobsdomain, "jobs",utils2, weights )
-    jobs4=LinearAdditiveUtilitySpace(jobsdomain, "jobs",utils, weights2 )
-
-    # A real jobs profile from profiles server
-    # "reservationBid":null was added because toJson will also add this (even though it's the default value)
-    jobsstr = '{"LinearAdditiveUtilitySpace":{"issueUtilities":{'  \
-        + '"lease car":{"DiscreteValueSetUtilities":{"valueUtilities":{"no":0,"yes":1}}},' \
-        + '"permanent contract":{"DiscreteValueSetUtilities":{"valueUtilities":{"no":0,"yes":1}}},'  \
-        + '"career development opportunities":{"DiscreteValueSetUtilities":{"valueUtilities":{"high":1,"low":0,"medium":0.5}}},' \
-        + '"fte":{"DiscreteValueSetUtilities":{"valueUtilities":{"1.0":0.75,"0.6":0.25,"0.8":0.5}}},' \
-        + '"salary":{"DiscreteValueSetUtilities":{"valueUtilities":{"4000":1,"2500":0.25,"3500":0.75,"2000":0,"3000":0.3}}},' \
-        + '"work from home":{"DiscreteValueSetUtilities":{"valueUtilities":{"1":0.5,"2":0.666666666666,"0":0.333333333}}}},' \
-        + '"issueWeights":{"lease car":0.06,"permanent contract":0.16,' \
-        + '"career development opportunities":0.04,"fte":0.32,"salary":0.24,"work from home":0.18},' \
-        + '"domain":{"name":"jobs","issuesValues":{"lease car":{"values":["yes","no"]},"permanent contract":{"values":["yes","no"]},"career development opportunities":{"values":["low","medium","high"]},"fte":{"values":["0.6","0.8","1.0"]},"salary":{"values":["2000","2500","3000","3500","4000"]},"work from home":{"values":["0","1","2"]}}},'\
-        + '"name":"jobs1", "reservationBid":null}}'
-
-    jobsjson = json.loads(jobsstr)
-
-    partystr='{"LinearAdditiveUtilitySpace":{"domain":{"name":"party","issuesValues":{"Invitations":{"values":["Plain","Photo","Custom, Handmade","Custom, Printed"]},"Music":{"values":["MP3","DJ","Band"]},"Drinks":{"values":["Non-Alcoholic","Beer Only","Handmade Cocktails","Catering"]},"Cleanup":{"values":["Water and Soap","Specialized Materials","Special Equiment","Hired Help"]},"Food":{"values":["Chips and Nuts","Finger-Food","Handmade Food","Catering"]},"Location":{"values":["Party Tent","Your Dorm","Party Room","Ballroom"]}}},"name":"party1","issueUtilities":{"Invitations":{"DiscreteValueSetUtilities":{"valueUtilities":{"Plain":0.24,"Custom, Printed":0.48,"Photo":0.74,"Custom, Handmade":1}}},"Music":{"DiscreteValueSetUtilities":{"valueUtilities":{"DJ":0.99,"Band":0.35,"MP3":0.65}}},"Drinks":{"DiscreteValueSetUtilities":{"valueUtilities":{"Handmade Cocktails":0.672,"Non-Alcoholic":0.263,"Beer Only":0.98,"Catering":0.334}}},"Cleanup":{"DiscreteValueSetUtilities":{"valueUtilities":{"Special Equiment":0.32,"Water and Soap":0.65,"Specialized Materials":0.96,"Hired Help":0.31}}},"Food":{"DiscreteValueSetUtilities":{"valueUtilities":{"Finger-Food":0.52,"Handmade Food":0.51,"Catering":0.25,"Chips and Nuts":0.75}}},"Location":{"DiscreteValueSetUtilities":{"valueUtilities":{"Your Dorm":0.23,"Ballroom":0.77,"Party Room":0.99,"Party Tent":0.52}}}},"issueWeights":{"Invitations":0.05,"Music":0.19,"Drinks":0.28,"Cleanup":0.10,"Food":0.19,"Location":0.19},"reservationBid":null}}'
-    partyjson = json.loads(partystr)
+class LinearAdditiveTest(unittest.TestCase, GeneralTests[LinearAdditiveUtilitySpace]):
+    NAME = "test"
+    I1V1UTIL = Decimal("0.3")
+    I1V2UTIL = Decimal("0.2")
+    I2V1UTIL = Decimal("0.6")
+    I2V2UTIL = Decimal("0.8")
+    WEIGHT1 = Decimal("0.4")
+    WEIGHT2 = Decimal("0.6")
+    ISS1 = "issue1"
+    ISS2 = "issue2"
+    ISS3 = "issue3"
+    i1v1 = DiscreteValue("issue1value1")
+    i1v2 = DiscreteValue("issue1value2")
+    i2v1 = DiscreteValue("issue2value1")
+    i2v2 = DiscreteValue("issue2value2")
+    i3v1 = DiscreteValue("issue3value1")
+    i3v2 = DiscreteValue("issue3value2")
+
+    pyson = ObjectMapper()
+
+    WEIGHT1a = Decimal("0.3")
+    WEIGHT2a = Decimal("0.7")
+ 
+    values:Dict[str, ValueSet] =  OrderedDict()
+    values[ISS1]=DiscreteValueSet([i1v1, i1v2])
+    smalldomain = Domain(NAME, values)
+    values[ISS2]= DiscreteValueSet([i2v1, i2v2])
+    domain = Domain(NAME, values)
+
+    # build utilspace for string and equals testing.
+    utils:Dict[str, ValueSetUtilities] =  OrderedDict()
+    valueUtils =  OrderedDict()
+    valueUtils[i2v2]= I2V2UTIL
+    valueUtils[i2v1]= I2V1UTIL
+    value2Utils = DiscreteValueSetUtilities(valueUtils)
+    utils[ISS2]= value2Utils
+    valueUtils =  OrderedDict()
+    valueUtils[i1v1]= I1V1UTIL
+    valueUtils[i1v2]= I1V2UTIL
+    value1Utils = DiscreteValueSetUtilities(valueUtils)
+    utils[ISS1]= value1Utils
+
+    # build utilspaceb, mix up the utilities a bit.
+    utilsb:Dict[str, ValueSetUtilities] =  OrderedDict()
+    valueUtils =  OrderedDict()
+    valueUtils[i2v2] =I1V2UTIL
+    valueUtils[i2v1]= I1V1UTIL
+    value2UtilsB = DiscreteValueSetUtilities(valueUtils)
+    utilsb[ISS2]= value2UtilsB
+    valueUtils =  OrderedDict()
+    valueUtils[i1v1]= I2V1UTIL
+    valueUtils[i1v2]= I2V2UTIL
+    value1UtilsB = DiscreteValueSetUtilities(valueUtils)
+    utilsb[ISS1] =value1UtilsB
+
+    # weight map
+    weights =  OrderedDict()
+    weights[ISS2]= WEIGHT2
+    weights[ISS1]= WEIGHT1
+
+    # weight map 2
+    weightsb =  OrderedDict()
+    weightsb[ISS2]= WEIGHT2a
+    weightsb[ISS1]= WEIGHT1a
+
+    # bid with lowest utility
+    issuevalues:Dict[str, Value] = OrderedDict()
+    issuevalues[ISS2]= i2v1
+    issuevalues[ISS1]=i1v2
+    reservationBid = Bid(issuevalues)
+
+    issuevalues =  OrderedDict()
+    issuevalues[ISS2]= i2v1
+    issuevalues[ISS1]= i1v1
+    reservationBid2 = Bid(issuevalues)
+
+    # make the utilspaces
+    utilspace1 = LinearAdditiveUtilitySpace(domain, NAME, utils,
+            weights, reservationBid)
+    utilspace1a = LinearAdditiveUtilitySpace(domain, NAME, utils,
+            weights, reservationBid);
+
+    utilspace2a = LinearAdditiveUtilitySpace(domain, NAME, utils,
+            weightsb, reservationBid);
+    utilspace2b =  LinearAdditiveUtilitySpace(domain, NAME, utilsb,
+            weights, reservationBid);
+    utilspace3 = LinearAdditiveUtilitySpace(domain, NAME, utilsb,
+            weights, reservationBid2);
+
+
+    #Override
+    def getGeneralTestData(self)->List[List[LinearAdditiveUtilitySpace]] :
+        return [ [self.utilspace1, self.utilspace1a], [self.utilspace2a], 
+                [self.utilspace2b], [self.utilspace3]]
+
+    #Override
+    def getGeneralTestStrings(self)-> List[str] :
+        return [
+            "LinearAdditive\\[\\{issue2=DiscreteValueSetUtilities\\{\"issue2value2\"=0.8, \"issue2value1\"=0.6\\}, issue1=DiscreteValueSetUtilities\\{\"issue1value1\"=0.3, \"issue1value2\"=0.2\\}\\},\\{issue2=0.6, issue1=0.4\\},Bid\\{issue2=\"issue2value1\", issue1=\"issue1value2\"\\}\\]",
+            "LinearAdditive\\[\\{issue2=DiscreteValueSetUtilities\\{\"issue2value2\"=0.8, \"issue2value1\"=0.6\\}, issue1=DiscreteValueSetUtilities\\{\"issue1value1\"=0.3, \"issue1value2\"=0.2\\}\\},\\{issue2=0.7, issue1=0.3\\},Bid\\{issue2=\"issue2value1\", issue1=\"issue1value2\"\\}\\]",
+            "LinearAdditive\\[\\{issue2=DiscreteValueSetUtilities\\{\"issue2value2\"=0.2, \"issue2value1\"=0.3\\}, issue1=DiscreteValueSetUtilities\\{\"issue1value1\"=0.6, \"issue1value2\"=0.8\\}\\},\\{issue2=0.6, issue1=0.4\\},Bid\\{issue2=\"issue2value1\", issue1=\"issue1value2\"\\}\\]",
+            "LinearAdditive\\[\\{issue2=DiscreteValueSetUtilities\\{\"issue2value2\"=0.2, \"issue2value1\"=0.3\\}, issue1=DiscreteValueSetUtilities\\{\"issue1value1\"=0.6, \"issue1value2\"=0.8\\}\\},\\{issue2=0.6, issue1=0.4\\},Bid\\{issue2=\"issue2value1\", issue1=\"issue1value1\"\\}\\]"            
+        ]
+
+
+    def testConstructorNullIssues(self):
+        self.assertRaises(ValueError, lambda:LinearAdditiveUtilitySpace(self.domain, self.NAME, None, None,self.reservationBid))
+
+    def testConstructorNullDomain(self):
+        self.assertRaises(ValueError, lambda:LinearAdditiveUtilitySpace(None, self.NAME, self.utils, self.weights,
+                self.reservationBid))
+
+    # Empty profile is not allowed since the weights then don't sum up to 1
+    def testConstructorEmpty(self): 
+        self.assertRaises(ValueError, lambda:LinearAdditiveUtilitySpace(self.domain, self.NAME, {}, self.weights,
+                self.reservationBid))
+
+    def testConstructorOneIssue(self):
+        utilset = {}
+        weightset = {}
+        utilset[self.ISS1]= self.value1Utils
+        weightset[self.ISS1]=Decimal(1)
+        LinearAdditiveUtilitySpace(self.smalldomain, self.NAME, utilset, weightset,
+                None)
+
+    # Empty profile is not allowed since the weights then don't sum up to 1
+    def testConstructoroneIssueWrongWeight(self):
+        utilset = {}
+        weightset = {}
+        utilset[self.ISS1]= self.value1Utils
+        weightset[self.ISS1]=self.WEIGHT1
+        self.assertRaises(ValueError, lambda:LinearAdditiveUtilitySpace(self.smalldomain, self.NAME, utilset, weightset,
+                self.reservationBid))
+
+    # Try creating a domain and check isFitting
+    #@Test
+    def testCheckCoversDomain(self):
+        utilset = {}
+        weightset = {}
+
+        utilset[self.ISS1]=self.value1Utils
+        weightset[self.ISS1]=Decimal(1)
+        space = LinearAdditiveUtilitySpace(
+                self.smalldomain, self.NAME, utilset, weightset, None)
+
     
-    def testSerialize(self):
-        print(self.pyson.toJson(self.jobs))
-        self.maxDiff=None
-        self.assertEqual(self.jobsjson, self.pyson.toJson(self.jobs))
-        
-    def testDeserialize(self):
-        self.assertEqual(self.jobs, self.pyson.parse(self.jobsjson, Profile))
-        
-    def testDeserializeJapanTrip(self):
+    # Try creating a domain and check isFitting but there is more issues in our
+    # map than in the actual domain
+    def testCheckCoversWrongDomain(self):
+        utilset = {}
+        weightset = {}
+
+        utilset[self.ISS1]=self.value1Utils
+        weightset[self.ISS1]= Decimal(1)
+        self.assertRaises(ValueError,  lambda:LinearAdditiveUtilitySpace(
+                self.domain, self.NAME, utilset, weightset, self.reservationBid))
+
+    # Empty profile is not allowed since the weights then don't sum up to 1
+    def testUtility(self):
+        space = LinearAdditiveUtilitySpace(
+                self.domain, self.NAME, self.utils, self.weights, self.reservationBid)
+
+        bid = Mock()
+        issues = [self.ISS1]
+        bid.getIssues = Mock(return_value = issues)
+        bid.getValue = Mock(return_value = self.i1v1)
+        self.assertEqual(self.WEIGHT1 * self.I1V1UTIL, space.getUtility(bid))
+
+    def testPreferred(self) :
+        issuevalues = {}
+        issuevalues[self.ISS1]=self.i1v1
+        issuevalues[self.ISS2]=self.i2v2
+        bid1 = Bid(issuevalues)
+        issuevalues1 = {}
+        issuevalues1[self.ISS1]= self.i1v2
+        issuevalues1[self.ISS2]= self.i2v1
+        bid2 = Bid(issuevalues1)
+        self.assertTrue(self.utilspace1.isPreferredOrEqual(bid1, bid2))
+        self.assertTrue(self.utilspace1.isPreferredOrEqual(bid1, bid1))
+        self.assertTrue(self.utilspace1.isPreferredOrEqual(bid2, bid2))
+        self.assertFalse(self.utilspace1.isPreferredOrEqual(bid2, bid1))
+        self.assertFalse(self.utilspace1.isPreferredOrEqual(Bid({}), bid1))
+
+    def testPartialBidUtilityTest(self):
+        space = LinearAdditiveUtilitySpace(
+                self.domain, self.NAME, self.utils, self.weights, self.reservationBid)
+        issuevalues = {}
+        issuevalues[self.ISS1]= self.i1v1
+        bid = Bid(issuevalues)
+        self.assertEqual(self.WEIGHT1* self.I1V1UTIL, space.getUtility(bid))
+
+    def testLoadFullWithJson(self) :
+        serialized = Path("test/resources/party1.json").read_text("utf-8")
+        jsonobj=json.loads(serialized)
+        space = self.pyson.parse(jsonobj, Profile)
+
+    def testLoadFullWithJsonNumber(self) :
         serialized = Path("test/resources/japantrip1.json").read_text("utf-8")
         jsonobj=json.loads(serialized)
-        profile:Profile = self.pyson.parse(jsonobj, Profile);
-        self.assertEquals("japantrip1", profile.getName())
-        self.assertEquals("japantrip", profile.getDomain().getName())
-        
-    def testDeserializeParty1(self):
-        print( str(self.pyson.parse(self.partyjson, Profile)))
-
-    def testEquals(self):
-        self.assertEqual(self.jobs,self.jobs1)
-        self.assertEqual(hash(self.jobs),hash(self.jobs1))
-        self.assertNotEqual(self.jobs,self.jobs2)
-        self.assertNotEqual(hash(self.jobs),hash(self.jobs2))
-        self.assertNotEqual(self.jobs,self.jobs3)
-        self.assertNotEqual(hash(self.jobs),hash(self.jobs3))
-        self.assertNotEqual(self.jobs,self.jobs4)
-        self.assertNotEqual(hash(self.jobs),hash(self.jobs4))
-
-        
-    def testUtil(self):
-        bid=Bid({"lease car":DiscreteValue("yes"), "permanent contract":DiscreteValue("yes"), \
-                 "career development opportunities": DiscreteValue("low"), \
-                "fte": DiscreteValue("0.8"), "salary":DiscreteValue("2500"), "work from home": DiscreteValue("1") });
-        # 0.06 * 1 + 0.16 * 1 +  0.04 * 0 + 0.32 * 0.5 + 0.24 * 0.25 + 0.18 * 0.5 
-        self.assertEqual(Decimal("0.53"), self.jobs.getUtility(bid))
+        profile:Profile = self.pyson.parse(jsonobj, Profile)
+
+
+    def testResBidWithNonsenseIssue(self) :
+        resBid = Bid({"nonsense": self.i1v1})
+        self.assertRaises(ValueError, 
+            lambda:LinearAdditiveUtilitySpace(self.domain, self.NAME, self.utils, self.weights, resBid))
+
+    def testResBidWithWrongValueType(self):
+        resBid = Bid({self.ISS1: NumberValue(Decimal(0))})
+        self.assertRaises(ValueError, lambda:LinearAdditiveUtilitySpace(self.domain, self.NAME, self.utils, self.weights, resBid))
+
+    def testResBidWithNonsenseValue(self):
+        resBid =  Bid({self.ISS1: DiscreteValue("nonsense")})
+        self.assertRaises(ValueError, lambda:LinearAdditiveUtilitySpace(self.domain, self.NAME, self.utils, self.weights, resBid))
+
Index: geniuswebcore/test/geniusweb/profile/utilityspace/LinearAdditiveTest2.py
===================================================================
--- geniuswebcore/test/geniusweb/profile/utilityspace/LinearAdditiveTest2.py	(revision 67)
+++ geniuswebcore/test/geniusweb/profile/utilityspace/LinearAdditiveTest2.py	(revision 67)
@@ -0,0 +1,123 @@
+from decimal import Decimal
+import json
+from pathlib import Path
+from typing import Dict
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+# from unitpy.GeneralTests import GeneralTests
+
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+from geniusweb.issuevalue.DiscreteValueSet import DiscreteValueSet
+from geniusweb.issuevalue.Domain import Domain
+from geniusweb.issuevalue.NumberValue import NumberValue
+from geniusweb.issuevalue.ValueSet import ValueSet
+from geniusweb.profile.Profile import Profile
+from geniusweb.profile.utilityspace.DiscreteValueSetUtilities import DiscreteValueSetUtilities
+from geniusweb.profile.utilityspace.LinearAdditiveUtilitySpace import LinearAdditiveUtilitySpace
+from geniusweb.profile.utilityspace.ValueSetUtilities import ValueSetUtilities
+
+
+class LinearAdditiveTest(unittest.TestCase): #, GeneralTests[LinearAdditiveUtilitySpace]):
+    pyson=ObjectMapper()
+
+    yesno = DiscreteValueSet([DiscreteValue("yes"), DiscreteValue("no")])
+    leasecarvals = yesno
+    permcontractvals = yesno
+    carreervals=DiscreteValueSet([DiscreteValue("low"),DiscreteValue("medium"),DiscreteValue("high")])
+    ftevals=DiscreteValueSet([DiscreteValue("0.6"),DiscreteValue("0.8"),DiscreteValue("1.0")])
+    salaryvals=DiscreteValueSet([DiscreteValue("2000"),DiscreteValue("2500"),
+         DiscreteValue("3000"),DiscreteValue("3500"),DiscreteValue("4000")])
+    workfromhomevals=DiscreteValueSet([DiscreteValue("0"),DiscreteValue("1"),DiscreteValue("2")])
+     
+    jobsdomain = Domain("jobs", {"lease car":leasecarvals, "permanent contract":permcontractvals, \
+            "career development opportunities":carreervals, "fte":ftevals, \
+            "salary": salaryvals, "work from home": workfromhomevals  })
+    N0=Decimal("0")
+    N1=Decimal("1")
+    N025=Decimal("0.25")
+    N03=Decimal("0.3")
+    N05=Decimal("0.5")
+    N075=Decimal("0.75")
+
+    leasevarutils = DiscreteValueSetUtilities({DiscreteValue("no"):N0, DiscreteValue("yes"):N1})
+    leasevarutils2 = DiscreteValueSetUtilities({DiscreteValue("no"):N0, DiscreteValue("yes"):N075})
+    permcontrutils = DiscreteValueSetUtilities({DiscreteValue("no"):N0, DiscreteValue("yes"):N1})
+    carreerutils = DiscreteValueSetUtilities({DiscreteValue("high"):N1,DiscreteValue("low"):N0,DiscreteValue("medium"):N05})
+    fteutils = DiscreteValueSetUtilities({DiscreteValue("1.0"):N075,DiscreteValue("0.6"):N025,DiscreteValue("0.8"):N05})
+    salaryutils = DiscreteValueSetUtilities({DiscreteValue("4000"):N1,DiscreteValue("2500"):N025,DiscreteValue("3500"):N075,
+                     DiscreteValue("2000"):N0,DiscreteValue("3000"):N03})
+    fromhomeutils = DiscreteValueSetUtilities({DiscreteValue("1"):N05,DiscreteValue("2"):Decimal("0.666666666666"),
+                   DiscreteValue("0"):Decimal("0.333333333")})
+    utils:Dict[str, ValueSetUtilities] = {'lease car': leasevarutils, 'permanent contract':permcontrutils,  'career development opportunities': carreerutils,
+             'fte':fteutils, 'salary':salaryutils, 'work from home':fromhomeutils }
+    utils2:Dict[str, ValueSetUtilities] = {'lease car': leasevarutils2, 'permanent contract':permcontrutils,  'career development opportunities': carreerutils,
+             'fte':fteutils, 'salary':salaryutils, 'work from home':fromhomeutils }
+    weights:Dict[str, Decimal] = {"lease car":Decimal("0.06"),"permanent contract":Decimal("0.16"),
+            "career development opportunities":Decimal("0.04"),"fte":Decimal("0.32"),
+            "salary":Decimal("0.24"),"work from home":Decimal("0.18")}
+    weights2:Dict[str, Decimal] = {"lease car":Decimal("0.06"),"permanent contract":Decimal("0.16"),
+            "career development opportunities":Decimal("0.05"),"fte":Decimal("0.31"),
+            "salary":Decimal("0.24"),"work from home":Decimal("0.18")}
+
+    jobs=LinearAdditiveUtilitySpace(jobsdomain, "jobs1",utils, weights )
+    jobs1=LinearAdditiveUtilitySpace(jobsdomain, "jobs1",utils, weights )
+    jobs2=LinearAdditiveUtilitySpace(jobsdomain, "jobs2",utils, weights )
+    jobs3=LinearAdditiveUtilitySpace(jobsdomain, "jobs",utils2, weights )
+    jobs4=LinearAdditiveUtilitySpace(jobsdomain, "jobs",utils, weights2 )
+
+    # A real jobs profile from profiles server
+    # "reservationBid":null was added because toJson will also add this (even though it's the default value)
+    jobsstr = '{"LinearAdditiveUtilitySpace":{"issueUtilities":{'  \
+        + '"lease car":{"DiscreteValueSetUtilities":{"valueUtilities":{"no":0,"yes":1}}},' \
+        + '"permanent contract":{"DiscreteValueSetUtilities":{"valueUtilities":{"no":0,"yes":1}}},'  \
+        + '"career development opportunities":{"DiscreteValueSetUtilities":{"valueUtilities":{"high":1,"low":0,"medium":0.5}}},' \
+        + '"fte":{"DiscreteValueSetUtilities":{"valueUtilities":{"1.0":0.75,"0.6":0.25,"0.8":0.5}}},' \
+        + '"salary":{"DiscreteValueSetUtilities":{"valueUtilities":{"4000":1,"2500":0.25,"3500":0.75,"2000":0,"3000":0.3}}},' \
+        + '"work from home":{"DiscreteValueSetUtilities":{"valueUtilities":{"1":0.5,"2":0.666666666666,"0":0.333333333}}}},' \
+        + '"issueWeights":{"lease car":0.06,"permanent contract":0.16,' \
+        + '"career development opportunities":0.04,"fte":0.32,"salary":0.24,"work from home":0.18},' \
+        + '"domain":{"name":"jobs","issuesValues":{"lease car":{"values":["yes","no"]},"permanent contract":{"values":["yes","no"]},"career development opportunities":{"values":["low","medium","high"]},"fte":{"values":["0.6","0.8","1.0"]},"salary":{"values":["2000","2500","3000","3500","4000"]},"work from home":{"values":["0","1","2"]}}},'\
+        + '"name":"jobs1", "reservationBid":null}}'
+
+    jobsjson = json.loads(jobsstr)
+
+    partystr='{"LinearAdditiveUtilitySpace":{"domain":{"name":"party","issuesValues":{"Invitations":{"values":["Plain","Photo","Custom, Handmade","Custom, Printed"]},"Music":{"values":["MP3","DJ","Band"]},"Drinks":{"values":["Non-Alcoholic","Beer Only","Handmade Cocktails","Catering"]},"Cleanup":{"values":["Water and Soap","Specialized Materials","Special Equiment","Hired Help"]},"Food":{"values":["Chips and Nuts","Finger-Food","Handmade Food","Catering"]},"Location":{"values":["Party Tent","Your Dorm","Party Room","Ballroom"]}}},"name":"party1","issueUtilities":{"Invitations":{"DiscreteValueSetUtilities":{"valueUtilities":{"Plain":0.24,"Custom, Printed":0.48,"Photo":0.74,"Custom, Handmade":1}}},"Music":{"DiscreteValueSetUtilities":{"valueUtilities":{"DJ":0.99,"Band":0.35,"MP3":0.65}}},"Drinks":{"DiscreteValueSetUtilities":{"valueUtilities":{"Handmade Cocktails":0.672,"Non-Alcoholic":0.263,"Beer Only":0.98,"Catering":0.334}}},"Cleanup":{"DiscreteValueSetUtilities":{"valueUtilities":{"Special Equiment":0.32,"Water and Soap":0.65,"Specialized Materials":0.96,"Hired Help":0.31}}},"Food":{"DiscreteValueSetUtilities":{"valueUtilities":{"Finger-Food":0.52,"Handmade Food":0.51,"Catering":0.25,"Chips and Nuts":0.75}}},"Location":{"DiscreteValueSetUtilities":{"valueUtilities":{"Your Dorm":0.23,"Ballroom":0.77,"Party Room":0.99,"Party Tent":0.52}}}},"issueWeights":{"Invitations":0.05,"Music":0.19,"Drinks":0.28,"Cleanup":0.10,"Food":0.19,"Location":0.19},"reservationBid":null}}'
+    partyjson = json.loads(partystr)
+    
+    def testSerialize(self):
+        print(self.pyson.toJson(self.jobs))
+        self.maxDiff=None
+        self.assertEqual(self.jobsjson, self.pyson.toJson(self.jobs))
+        
+    def testDeserialize(self):
+        self.assertEqual(self.jobs, self.pyson.parse(self.jobsjson, Profile))
+        
+    def testDeserializeJapanTrip(self):
+        serialized = Path("test/resources/japantrip1.json").read_text("utf-8")
+        jsonobj=json.loads(serialized)
+        profile:Profile = self.pyson.parse(jsonobj, Profile);
+        self.assertEquals("japantrip1", profile.getName())
+        self.assertEquals("japantrip", profile.getDomain().getName())
+        
+    def testDeserializeParty1(self):
+        print( str(self.pyson.parse(self.partyjson, Profile)))
+
+    def testEquals(self):
+        self.assertEqual(self.jobs,self.jobs1)
+        self.assertEqual(hash(self.jobs),hash(self.jobs1))
+        self.assertNotEqual(self.jobs,self.jobs2)
+        self.assertNotEqual(hash(self.jobs),hash(self.jobs2))
+        self.assertNotEqual(self.jobs,self.jobs3)
+        self.assertNotEqual(hash(self.jobs),hash(self.jobs3))
+        self.assertNotEqual(self.jobs,self.jobs4)
+        self.assertNotEqual(hash(self.jobs),hash(self.jobs4))
+
+        
+    def testUtil(self):
+        bid=Bid({"lease car":DiscreteValue("yes"), "permanent contract":DiscreteValue("yes"), \
+                 "career development opportunities": DiscreteValue("low"), \
+                "fte": DiscreteValue("0.8"), "salary":DiscreteValue("2500"), "work from home": DiscreteValue("1") });
+        # 0.06 * 1 + 0.16 * 1 +  0.04 * 0 + 0.32 * 0.5 + 0.24 * 0.25 + 0.18 * 0.5 
+        self.assertEqual(Decimal("0.53"), self.jobs.getUtility(bid))
Index: geniuswebcore/test/geniusweb/profileconnection/ProfileConnectionFactoryTest.py
===================================================================
--- geniuswebcore/test/geniusweb/profileconnection/ProfileConnectionFactoryTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/profileconnection/ProfileConnectionFactoryTest.py	(revision 67)
@@ -1,6 +1,7 @@
+from builtins import BaseException
 import unittest
 
 from tudelft_utilities_logging.Reporter import Reporter
-from uri import URI
+from uri.uri import URI
 
 from geniusweb.profileconnection.ProfileConnectionFactory import ProfileConnectionFactory
@@ -8,5 +9,5 @@
 
 class DummyReporter(Reporter):
-    def log(self, level:int, msg:str, thrown:Exception=None):
+    def log(self, level:int, msg:str, thrown:BaseException=None):
         print(msg)
 
Index: geniuswebcore/test/geniusweb/profileconnection/WebSocketProfileConnectorTest.py
===================================================================
--- geniuswebcore/test/geniusweb/profileconnection/WebSocketProfileConnectorTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/profileconnection/WebSocketProfileConnectorTest.py	(revision 67)
@@ -3,9 +3,10 @@
 import threading
 from time import sleep
+from typing import Optional
 import unittest
 from unittest.mock import Mock
 
 from tudelft_utilities_logging.Reporter import Reporter
-from uri import URI
+from uri.uri import URI
 
 from geniusweb.profileconnection.FileProfileConnector import FileProfileConnector
@@ -17,5 +18,5 @@
 
 class DummyReporter(Reporter):
-    def log(self, level:int, msg:str, thrown:Exception=None):
+    def log(self, level:int, msg:str, thrown:BaseException=None):
         print(msg)
 
Index: geniuswebcore/test/geniusweb/protocol/CurrentNegoStateTest.py
===================================================================
--- geniuswebcore/test/geniusweb/protocol/CurrentNegoStateTest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/protocol/CurrentNegoStateTest.py	(revision 67)
@@ -0,0 +1,42 @@
+from typing import List
+import unittest
+from unittest.mock import Mock
+
+from unitpy.GeneralTests import GeneralTests
+
+from geniusweb.actions.Action import Action
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.progress.ProgressTime import ProgressTime
+from geniusweb.protocol.CurrentNegoState import CurrentNegoState
+from geniusweb.protocol.session.saop.SAOPSettings import SAOPSettings
+from geniusweb.protocol.session.saop.SAOPState import SAOPState
+
+
+class CurrentNegoStateTest (unittest.TestCase,GeneralTests[CurrentNegoState]):
+    party1 = PartyId("party1")
+    party2 = PartyId("party2")
+    actions1:List[Action] = []
+
+    connections = [party1]
+    connections2 = [party1, party2]
+
+    progresstime = Mock(ProgressTime)
+    settings = Mock(SAOPSettings)
+
+    saop1 = SAOPState(actions1, connections2, progresstime,
+            settings)
+    saop2 = SAOPState(actions1, connections, progresstime,
+            settings)
+
+    state1 = CurrentNegoState(saop1, 1234)
+    state1a = CurrentNegoState(saop1,1234)
+    state2 = CurrentNegoState(saop2,1234)
+
+    def getGeneralTestData(self)->List[List[CurrentNegoState]] :
+        return [[self.state1, self.state1a],[self.state2]]
+
+    def getGeneralTestStrings(self)->List[str] :
+        return [
+                "CurrentNegoState.*1234.*SAOPState.*party1.*party2.*ProgressTime.*SAOPSettings.*",
+                "CurrentNegoState.*1234.*SAOPState.*party1.*ProgressTime.*SAOPSettings.*"]
+
Index: geniuswebcore/test/geniusweb/protocol/ProtocolExceptionTest.py
===================================================================
--- geniuswebcore/test/geniusweb/protocol/ProtocolExceptionTest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/protocol/ProtocolExceptionTest.py	(revision 67)
@@ -0,0 +1,22 @@
+import traceback
+import unittest
+
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.protocol.ProtocolException import ProtocolException
+
+
+class ProtocolExceptionTest(unittest.TestCase):
+    def testConstructor(self):
+        party = PartyId("test")
+        try:
+            x=1/0
+        except Exception as e:
+            err = ProtocolException("div by zero", party,  e)
+            
+        print(err) # BUG ? FEATURE? this does NOT show the cause nor the stacktrace... 
+        self.assertTrue(err)
+        self.assertTrue(err.__cause__)
+
+    def testNullParty(self):
+        ProtocolException("test",None, None)
+        
Index: geniuswebcore/test/geniusweb/protocol/session/ConnectionWithPartiesTest.py
===================================================================
--- geniuswebcore/test/geniusweb/protocol/session/ConnectionWithPartiesTest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/protocol/session/ConnectionWithPartiesTest.py	(revision 67)
@@ -0,0 +1,80 @@
+from typing import List
+import unittest
+from unittest.mock import Mock
+
+from unitpy.GeneralTests import GeneralTests
+
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.protocol.partyconnection.ProtocolToPartyConn import ProtocolToPartyConn
+from geniusweb.protocol.partyconnection.ProtocolToPartyConnections import ProtocolToPartyConnections
+
+
+class ConnectionWithPartiesTest (unittest.TestCase, GeneralTests[ProtocolToPartyConnections]):
+	party1 = PartyId("party1")
+	party2 = PartyId("party2")
+	party3 = PartyId("party3")
+
+	con1 = Mock(ProtocolToPartyConn)
+	con2 = Mock(ProtocolToPartyConn)
+	con3 = Mock(ProtocolToPartyConn)
+
+	conns1 = ProtocolToPartyConnections([con1, con2])
+	conns1a = ProtocolToPartyConnections([con1, con2])
+	conns2 = ProtocolToPartyConnections([con1, con3])
+	conns3 = ProtocolToPartyConnections([con2, con1])
+	conns4 = ProtocolToPartyConnections([con2, con1, con3])
+	conns5 = ProtocolToPartyConnections([con2, con1, con3, con1])
+
+	con1.__repr__=Mock(return_value="con1") #type:ignore
+	con2.__repr__=Mock(return_value="con2") #type:ignore
+	con3.__repr__=Mock(return_value="con3")  #type:ignore
+	
+	con1.getParty=Mock(return_value=party1)
+	con2.getParty=Mock(return_value=party2)
+	con3.getParty=Mock(return_value=party3)
+
+
+	def getGeneralTestData(self)->List[List[ProtocolToPartyConnections]] :
+		return [[self.conns1, self.conns1a],[self.conns2], [self.conns3],
+				[self.conns4]]
+
+	def getGeneralTestStrings(self) -> List[str] : 
+		return ["ConnectionWithParties\\[con1, con2\\]",
+				"ConnectionWithParties\\[con1, con3\\]",
+				"ConnectionWithParties\\[con2, con1\\]",
+				"ConnectionWithParties\\[con2, con1, con3\\]"]
+
+	def testGet(self):
+		self.assertEqual(self.con1, self.conns1.getConn(0))
+		self.assertEqual(self.con3, self.conns4.getConn(2))
+
+	def testGetParty(self):
+		self.assertEquals(self.con1, self.conns1.get(self.party1))
+		self.assertEquals(self.con1, self.conns4.get(self.party1))
+		self.assertEquals(self.con2, self.conns1.get(self.party2))
+		self.assertEquals(self.con2, self.conns3.get(self.party2))
+		self.assertEquals(self.con2, self.conns4.get(self.party2))
+	
+	def testGetUnknownParty(self) :
+		self.assertEqual(None, self.conns2.get(self.party2))
+
+	def testSize(self):
+		self.assertEqual(2, self.conns1.size())
+		self.assertEquals(2, self.conns2.size())
+		self.assertEquals(2, self.conns3.size())
+		self.assertEquals(3, self.conns4.size())
+
+	def testWithParty(self) :
+		conns = self.conns3.With(self.con3)
+		self.assertEqual(2, self.conns3.size())
+		self.assertEqual(3, conns.size())
+		self.assertEqual(self.conns4, conns)
+
+	def testWithExistingParty(self):
+		self.conns4.With(self.con1);
+		# no throw, default this is ok, it should be the protocol testing this.
+	
+	def testAllUnique(self):
+		self.assertTrue(self.conns4.allunique());
+		self.assertFalse(self.conns5.allunique());
+
Index: geniuswebcore/test/geniusweb/protocol/session/SessionResultTest.py
===================================================================
--- geniuswebcore/test/geniusweb/protocol/session/SessionResultTest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/protocol/session/SessionResultTest.py	(revision 67)
@@ -0,0 +1,100 @@
+from collections import OrderedDict
+import json
+from typing import List, Dict
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+from unitpy.GeneralTests import GeneralTests
+from uri.uri import URI
+
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.inform.Agreements import Agreements
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+from geniusweb.issuevalue.Value import Value
+from geniusweb.protocol.session.SessionResult import SessionResult
+from geniusweb.references.Parameters import Parameters
+from geniusweb.references.PartyRef import PartyRef
+from geniusweb.references.PartyWithParameters import PartyWithParameters
+from geniusweb.references.PartyWithProfile import PartyWithProfile
+from geniusweb.references.ProfileRef import ProfileRef
+
+
+class SessionResultTest(unittest.TestCase,  GeneralTests[SessionResult]):
+	jackson = ObjectMapper()
+	error = ValueError("test");
+
+	ISSUE1 = "issue1";
+	jsonstring = "{\"participants\":{\"party2\":{\"party\":{\"partyref\":\"party2\",\"parameters\":{}},\"profile\":\"profile2\"},\"party1\":{\"party\":{\"partyref\":\"party1\",\"parameters\":{}},\"profile\":\"profile1\"}},\"agreements\":{\"party2\":{\"issuevalues\":{\"issue1\":\"a\"}},\"party1\":{\"issuevalues\":{\"issue1\":\"a\"}}},\"penalties\":{\"party2\":0.0,\"party1\":0.0},\"error\":null}";
+	nopenalties:Dict[PartyId, float] = {}
+	penalties:Dict[PartyId, float]  = {}
+
+	PARTY1 = PartyId("party1")
+	PARTY2 = PartyId("party2")
+	PARTY3 = PartyId("party3")
+
+	penalties[PARTY1]= 0.1
+	penalties[PARTY2]= 0.2
+	nopenalties[PARTY1]= 0.
+	nopenalties[PARTY2]= 0.
+
+	errorstring = "\"error\":{\"java.lang.RuntimeException\":"\
+			+ json.dumps(jackson.toJson(error)) + "}"
+	print(errorstring)
+
+	party1 = PartyWithParameters(PartyRef(URI("party1")), Parameters())
+	party2 = PartyWithParameters(PartyRef(URI("party2")), Parameters())
+
+	partyprofile1 = PartyWithProfile(party1, ProfileRef(URI("profile1")))
+	partyprofile2 = PartyWithProfile(party2, ProfileRef(URI("profile2")))
+
+	issuevalues1:Dict[str, Value] = {}
+	issuevalues1[ISSUE1]=DiscreteValue("a")
+	bid1 = Bid(issuevalues1)
+	agreement1 = Agreements().With(Agreements({PARTY1:bid1, PARTY2:bid1} ))
+
+	# different order but that shouldn't matter
+	issuevalues2:Dict[str, Value]  =OrderedDict()
+	issuevalues2[ISSUE1]= DiscreteValue("b")
+	bid2 = Bid(issuevalues2)
+	agreement2 = Agreements().With(Agreements({ PARTY1:bid2, PARTY3:bid2} ))
+
+	partiesmap:Dict[PartyId, PartyWithProfile] = OrderedDict()
+	partiesmap[PARTY2]= partyprofile2
+	partiesmap[PARTY1]= partyprofile1
+
+	partiesmap2:Dict[PartyId, PartyWithProfile] = OrderedDict()
+	partiesmap2[PARTY1]= partyprofile2
+	partiesmap2[PARTY3]= partyprofile1
+
+	result1 = SessionResult(partiesmap, agreement1, nopenalties, None) 
+	result1a = SessionResult(partiesmap, agreement1, nopenalties, None)
+	result2 = SessionResult(partiesmap, agreement2, nopenalties, None)
+	result3 = SessionResult(partiesmap2, agreement1, nopenalties, None)
+	result4 = SessionResult(partiesmap2, agreement1, penalties, None)
+
+	# IGNORE ERROR for now, it fails somewhere deep in maven suddenly.
+	# result4 = new SessionResult(Arrays.asList(partyprofile1,
+	# partyprofile2), bid1, error);
+
+	def getGeneralTestData(self) ->List[List[SessionResult]]: 
+		return [[self.result1, self.result1a],
+				[self.result2], [self.result3],
+				[self.result4]]
+
+	def  getGeneralTestStrings(self) -> List[str]:
+		return [
+				"SessionResult.*party2.*profile2.*,.*party1.*profile1.*Agreements.*Bid.*issue1=\"a\".*0\\.0.*0\\.0.*None.*",
+				"SessionResult.*party2.*profile2.*,.*party1.*profile1.*Agreements.*Bid.*issue1=\"b\".*0\\.0.*0\\.0.*None.*",
+				"SessionResult.*party1.*profile2.*,.*party3.*profile1.*Agreements.*Bid.*issue1=\"a\".*0\\.0.*0\\.0.*None.*",
+				"SessionResult.*party1.*profile2.*,.*party3.*profile1.*Agreements.*Bid.*issue1=\"a\".*party1.*0\\.1.*party2.*0\\.2.*None.*"
+				]
+
+	def serializeTest(self) :
+		print(self.jackson.toJson(self.result1))
+		self.assertEqual(json.loads(self.jsonstring), self.jackson.toJson(self.result1))
+
+	def deserializeTest(self):
+		act = self.jackson.parse(json.loads(self.jsonstring), SessionResult)
+		self.assertEqual(self.result1, act)
+
Index: geniuswebcore/test/geniusweb/protocol/session/TeamInfoTest.py
===================================================================
--- geniuswebcore/test/geniusweb/protocol/session/TeamInfoTest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/protocol/session/TeamInfoTest.py	(revision 67)
@@ -0,0 +1,46 @@
+import json
+from typing import List
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+from unitpy.GeneralTests import GeneralTests
+from uri.uri import URI
+
+from geniusweb.protocol.session.TeamInfo import TeamInfo
+from geniusweb.references.Parameters import Parameters
+from geniusweb.references.PartyRef import PartyRef
+from geniusweb.references.PartyWithParameters import PartyWithParameters
+from geniusweb.references.PartyWithProfile import PartyWithProfile
+from geniusweb.references.ProfileRef import ProfileRef
+
+
+class TeamInfoTest (unittest.TestCase, GeneralTests[TeamInfo]):
+
+	team1 = TeamInfo([])
+	team1a = TeamInfo([])
+	parameters = Parameters()
+	asString1 = "{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"party1\",\"parameters\":{}},\"profile\":\"profile1\"}]}}";
+
+	partyref = PartyRef(URI("party1"))
+	profileref =  ProfileRef(URI("profile1"))
+	team2 = TeamInfo( [PartyWithProfile(PartyWithParameters(partyref, parameters),profileref)])
+	team2a =  TeamInfo( [ PartyWithProfile(PartyWithParameters(partyref, parameters),
+			profileref)]);
+
+	def getGeneralTestData(self)->List[List[TeamInfo]] :
+		return [[self.team1, self.team1a],	[self.team2, self.team2a]]
+
+	def getGeneralTestStrings(self) -> List[str]:
+		return ["TeamInfo.\\[\\].",
+				"TeamInfo.\\[PartyWithProfile.*party1.*profile1.*\\].*"]
+
+	def testSerialize(self) :
+
+		jackson = ObjectMapper()
+		print(jackson.toJson(self.team2))
+		self.assertEqual(json.loads(self.asString1), jackson.toJson(self.team2))
+
+	def testDeserialize(self) :
+		jackson = ObjectMapper()
+		read = jackson.parse(json.loads(self.asString1), TeamInfo)
+		self.assertEqual(self.team2, read)
Index: geniuswebcore/test/geniusweb/protocol/session/saop/SAOPSettingsTest.py
===================================================================
--- geniuswebcore/test/geniusweb/protocol/session/saop/SAOPSettingsTest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/protocol/session/saop/SAOPSettingsTest.py	(revision 67)
@@ -0,0 +1,119 @@
+import json
+from typing import List
+from unittest import mock
+import unittest
+from unittest.mock import Mock
+
+from pyson.ObjectMapper import ObjectMapper
+from tudelft_utilities_logging.ReportToLogger import ReportToLogger
+from unitpy.GeneralTests import GeneralTests
+from uri.uri import URI
+
+from geniusweb.deadline.Deadline import Deadline
+from geniusweb.deadline.DeadlineRounds import DeadlineRounds
+from geniusweb.deadline.DeadlineTime import DeadlineTime
+from geniusweb.protocol.NegoSettings import NegoSettings
+from geniusweb.protocol.session.SessionSettings import SessionSettings
+from geniusweb.protocol.session.TeamInfo import TeamInfo
+from geniusweb.protocol.session.saop.SAOPSettings import SAOPSettings
+from geniusweb.references.Parameters import Parameters
+from geniusweb.references.PartyRef import PartyRef
+from geniusweb.references.PartyWithParameters import PartyWithParameters
+from geniusweb.references.PartyWithProfile import PartyWithProfile
+from geniusweb.references.ProfileRef import ProfileRef
+
+
+class SAOPSettingsTest (unittest.TestCase, GeneralTests[SAOPSettings]):
+
+	partyprof1 = Mock(TeamInfo)
+	partyprof2 = Mock(TeamInfo)
+	partyprof3 = Mock(TeamInfo)
+	participants2:List[TeamInfo] = [partyprof1,partyprof2]
+	participants3:List[TeamInfo] = [partyprof1, partyprof2, partyprof3]
+
+	deadline = Mock(DeadlineTime)
+	deadline2 = Mock(Deadline)
+	settings1 = SAOPSettings(participants2, deadline)
+	settings1a = SAOPSettings(participants2, deadline)
+	settings2 = SAOPSettings(participants3, deadline)
+	settings3 = SAOPSettings(participants2, deadline2)
+	jackson = ObjectMapper()
+
+	serialized = "{\"SAOPSettings\":{\"participants\":[{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"http://party1\",\"parameters\":{}},\"profile\":\"http://profile1\"}]}},{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"http://party2\",\"parameters\":{}},\"profile\":\"http://profile2\"}]}}],\"deadline\":{\"DeadlineTime\":{\"durationms\":100}}}}";
+
+	deadline.getDuration=Mock(return_value=1000)
+	#work around mypy bug
+	deadline.__repr__=Mock(return_value="deadline") #type:ignore
+	deadline2.__repr__=Mock(return_value="deadline2") #type:ignore
+
+	partyprof1.getSize=Mock(return_value=1)
+	partyprof2.getSize=Mock(return_value=1)
+	partyprof3.getSize=Mock(return_value=1)
+
+	partyprof1.__repr__=Mock(return_value="party and profile 1") #type:ignore
+	partyprof2.__repr__=Mock(return_value="party and profile 2") #type:ignore
+	partyprof3.__repr__=Mock(return_value="party and profile 3") #type:ignore
+
+	# SERIALIZABLE version with REAL objects. Still no workaround for
+	# this...
+	party1 = PartyWithParameters(PartyRef(URI("http://party1")), Parameters())
+	profile1 = ProfileRef(URI("http://profile1"))
+	party2 = PartyWithParameters(PartyRef(URI("http://party2")), Parameters())
+	profile2 = ProfileRef(URI("http://profile2"))
+	partywithprof1 = TeamInfo([PartyWithProfile(party1, profile1)])
+	partywithprof2 = TeamInfo([PartyWithProfile(party2, profile2)])
+	participants = [partywithprof1,	partywithprof2]
+
+	deadlinetime = DeadlineTime(100)
+	sersettings = SAOPSettings(participants, deadlinetime)
+
+
+	def getGeneralTestData(self) -> List[List[SAOPSettings]] :
+		return [[self.settings1, self.settings1a],
+			[self.settings2], [self.settings3]]
+
+	def getGeneralTestStrings(self)-> List[str]:
+		return [
+				"SAOPSettings.party and profile 1, party and profile 2.,deadline.",
+				"SAOPSettings.party and profile 1, party and profile 2, party and profile 3.,deadline.",
+				"SAOPSettings.party and profile 1, party and profile 2.,deadline2."]
+
+	def testGetProtocol(self):
+		self.assertEqual("SAOP", str(self.settings1.getProtocol(ReportToLogger("test"))
+				.getRef().getURI()))
+
+	def testConstructorNoDeadline(self):
+		self.assertRaises(ValueError, lambda:SAOPSettings(self.participants2, None))
+
+	def testMaxRuntime(self):
+		self.deadline.getDuration=Mock(return_value=234000) 
+		self.assertEqual(234, self.settings1.getMaxRunTime())
+
+	def testMaxRuntimeRounds(self):
+		deadline = Mock(DeadlineRounds)
+		deadline.getDuration=Mock(return_value=12000)
+
+		settings = SAOPSettings(self.participants2, deadline)
+
+		self.assertEqual(12, settings.getMaxRunTime())
+	
+	def testDeserialize(self) :
+		obj = self.jackson.parse(json.loads(self.serialized),SessionSettings);
+		print(obj)
+		self.assertEqual(self.sersettings, obj)
+
+	def testDeserializeAsNego(self) :
+		obj = self.jackson.parse(json.loads(self.serialized),NegoSettings);
+		print(obj)
+		self.assertEqual(self.sersettings, obj)
+
+
+	def testSerialize(self):
+		jsonobj = self.jackson.toJson(self.sersettings)
+		print(jsonobj)
+		self.assertEqual(json.loads(self.serialized), jsonobj)
+
+	def testWith(self):
+		saop = SAOPSettings(self.participants2, self.deadline)
+		saop2 = saop.With(self.partyprof3);
+		self.assertEqual(3, len(saop2.getTeams()))
Index: geniuswebcore/test/geniusweb/protocol/session/saop/SAOPStateTest.py
===================================================================
--- geniuswebcore/test/geniusweb/protocol/session/saop/SAOPStateTest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/protocol/session/saop/SAOPStateTest.py	(revision 67)
@@ -0,0 +1,272 @@
+from datetime import datetime
+import json
+import time
+from typing import List
+import unittest
+from unittest.mock import Mock
+
+from pyson.ObjectMapper import ObjectMapper
+from unitpy.GeneralTests import GeneralTests
+from uri.uri import URI
+
+from geniusweb.actions.Accept import Accept
+from geniusweb.actions.Action import Action
+from geniusweb.actions.EndNegotiation import EndNegotiation
+from geniusweb.actions.Offer import Offer
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.deadline.DeadlineTime import DeadlineTime
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.progress.ProgressRounds import ProgressRounds
+from geniusweb.progress.ProgressTime import ProgressTime
+from geniusweb.protocol.NegoState import NegoState
+from geniusweb.protocol.ProtocolException import ProtocolException
+from geniusweb.protocol.partyconnection.ProtocolToPartyConn import ProtocolToPartyConn
+from geniusweb.protocol.session.TeamInfo import TeamInfo
+from geniusweb.protocol.session.saop.SAOPSettings import SAOPSettings
+from geniusweb.protocol.session.saop.SAOPState import SAOPState
+from geniusweb.references.Parameters import Parameters
+from geniusweb.references.PartyRef import PartyRef
+from geniusweb.references.PartyWithParameters import PartyWithParameters
+from geniusweb.references.PartyWithProfile import PartyWithProfile
+from geniusweb.references.ProfileRef import ProfileRef
+
+
+class SAOPStateTest (unittest.TestCase, GeneralTests[SAOPState]):
+	maxDiff =None
+	
+	NOW = 1000
+	party1 = PartyId("party1") 
+	party2 = PartyId("party2")
+	party3 = PartyId("party3")
+	actions1:List[Action]  = []
+	action = Mock(Action)
+	actions2:List[Action] = [action]
+
+	party1conn = Mock(ProtocolToPartyConn)
+	party2conn = Mock(ProtocolToPartyConn)
+	party3conn = Mock(ProtocolToPartyConn)
+
+	connections = [party1, party2]
+	connections2 = [party1, party3]
+	connections3 = [party1, party2, party3]
+
+	progresstime = Mock(ProgressTime)
+	progressrounds = Mock(ProgressRounds)
+	progressrounds1 = Mock(ProgressRounds)
+	settings = Mock(SAOPSettings)
+
+	bid1 = Mock(Bid)
+	otherbid = Mock(Bid)
+	accept1 = Accept(party1, bid1)
+	accept2 = Accept(party2, bid1)
+	acceptother = Accept(party2, otherbid)
+	offer1 = Offer(party1, bid1)
+	endNegotiation1 = EndNegotiation(party1)
+
+	jackson = ObjectMapper()
+
+	serialized = "{\"SAOPState\":{\"actions\":[],\"connections\":[\"party1\",\"party2\"],\"progress\":{\"ProgressTime\":{\"duration\":1000000,\"start\":2000000}},\"settings\":{\"SAOPSettings\":{\"participants\":[{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"party1ref\",\"parameters\":{}},\"profile\":\"prof1\"}]}}],\"deadline\":{\"DeadlineTime\":{\"durationms\":123000}}}},\"partyprofiles\":{},\"error\":null}}"
+
+	party1conn.getParty=Mock(return_value=party1)
+	party2conn.getParty=Mock(return_value=party2)
+	party3conn.getParty=Mock(return_value=party3)
+	# workaround https://github.com/python/mypy/issues/2427
+	party1conn.__repr__= Mock(return_value="conn1") #type:ignore 
+	party2conn.__repr__=Mock(return_value="conn2") #type:ignore
+	party3conn.__repr__=Mock(return_value="conn3") #type:ignore
+	action.__repr__=Mock(return_value="act1") #type:ignore
+
+	progresstime.isPastDeadline=Mock(return_value=False) # is default in java
+	progressrounds.getTerminationTime=\
+		Mock(return_value=datetime.utcfromtimestamp(time.time() + 36000))
+	progressrounds1.getTerminationTime=\
+		Mock(return_value=datetime.utcfromtimestamp(time.time() + 36000))
+	progressrounds.advance=Mock(return_value=progressrounds1)
+
+	state1 = SAOPState(actions1, connections, progresstime, settings)
+	state1a = SAOPState(actions1, connections, progresstime, settings)
+	state2 = SAOPState(actions2, connections, progresstime, settings)
+	state3 = SAOPState(actions1, connections2, progresstime, settings)
+	state4 = SAOPState(actions1, connections, progressrounds, settings)
+
+	conns = [party1, party2]
+	pwithp = PartyWithParameters(PartyRef(URI("party1ref")), Parameters())
+	teams = [PartyWithProfile(pwithp, ProfileRef(URI("prof1")))]
+	participants = [ TeamInfo(teams)]
+	deadline = DeadlineTime(123000)
+	setts = SAOPSettings(participants, deadline)
+	state = SAOPState(actions1, conns,
+					ProgressTime(1000000, datetime.fromtimestamp(2000000/1000.)), setts)
+ 	
+	def getGeneralTestData(self)->List[List[SAOPState]] :
+		return [[self.state1, self.state1a],
+				[self.state2], [self.state3],
+				[self.state4]]
+
+	def getGeneralTestStrings(self)->List[str] :
+		return [
+				"SAOPState.*\\[\\].*party1.*party2.*ProgressTime.*",
+				"SAOPState.*\\[act1\\].*party1.*party2.*ProgressTime.*",
+				"SAOPState.*party1.*party3.*ProgressTime.*",
+				"SAOPState.*party1.*party2.*ProgressRounds.*"]
+
+	def constructWith0Connection(self) :
+		SAOPState(self.actions1, [], self.progresstime, self.settings, None,
+				None)
+
+	def constructWithNoneConnection(self):
+		state = SAOPState(self.actions1, None, self.progresstime, self.settings,
+				None, None)
+		self.assertEqual(0, len(state.getConnections()))
+
+	def testNextActor(self):
+		self.assertEqual(self.party1, self.state1._getNextActor())
+
+	def testNextActor2(self):
+		conns = [self.party2, self.party3]
+		state = SAOPState(self.actions1, conns, self.progresstime, self.settings,
+				None, None)
+		self.assertEqual(self.party2, state._getNextActor())
+
+	def testNextActorAfterAction(self):
+		actions = [self.action]
+		state = SAOPState(actions, self.connections, self.progresstime,
+				self.settings, None, None)
+		self.assertEqual(self.party2, state._getNextActor())
+
+	def testNextActorAfter2Actions(self):
+		actions = [self.action, self.action]
+		state = SAOPState(actions, self.connections, self.progresstime,
+				self.settings, None, None)
+		self.assertEqual(self.party1, state._getNextActor())
+
+	def testIsFinal(self):
+		self.assertFalse(self.state1.isFinal(self.NOW))
+		state = SAOPState(self.actions1, self.connections, self.progresstime,
+				self.settings, None, Mock(ProtocolException))
+		self.assertTrue(state.isFinal(self.NOW))
+
+	def testWithException(self):
+		finalstate1 = self.state1.WithException(Mock(ProtocolException))
+		self.assertTrue(finalstate1.isFinal(self.NOW))
+		finalstate2 = finalstate1.WithException(Mock(ProtocolException))
+		self.assertTrue(finalstate2.isFinal(self.NOW))
+
+	def testIsOfferAgreement1(self):
+		actions = [self.offer1]
+		state = SAOPState(actions, self.connections3, self.progressrounds,
+				self.settings, None, None)
+		self.assertEqual({}, state.getAgreements().getMap())
+
+	def testIsOfferAcceptAgreement(self):
+		actions = [self.offer1, self.accept2]
+		state = SAOPState(actions, self.connections3, self.progressrounds,
+				self.settings, None, None);
+		self.assertEqual({}, state.getAgreements().getMap())
+
+	def testIsOfferAcceptOfferAgreement(self):
+		actions = [self.offer1, self.accept1, self.offer1]
+		state = SAOPState(actions, self.connections3, self.progressrounds,
+				self.settings, None, None);
+		self.assertEqual({},state.getAgreements().getMap())
+
+	def testIsOfferAcceptAcceptAgreement(self) :
+		actions = [self.offer1, self.accept1, self.accept2]
+		state = SAOPState(actions, self.connections3, self.progressrounds,
+				self.settings, None, None);
+		self.assertEquals(self.bid1, state.getAgreements().getMap()[self.party1])
+		self.assertTrue(state.isFinal(self.NOW))
+
+	def testIsAcceptOfferAcceptAgreement(self):
+		actions = [self.accept1, self.offer1, self.accept2]
+		state = SAOPState(actions, self.connections3, self.progressrounds,
+				self.settings, None, None);
+		self.assertEqual({},state.getAgreements().getMap())
+
+	def testIsOfferAcceptAcceptotherAgreement(self) :
+		actions = [self.offer1, self.accept1, self.acceptother]
+		state = SAOPState(actions, self.connections3, self.progressrounds,
+				self.settings, None, None);
+		self.assertEqual({},state.getAgreements().getMap())
+
+
+	def testWithAction(self):
+		actn = self.action
+		actn.getActor=Mock(return_value=self.party1)
+		self.assertRaises(ValueError, lambda:self.state1.WithAction(self.party1, actn))
+
+	def testWithConnection(self):
+		state = self.state1.WithParty(self.party3, Mock(PartyWithProfile))
+		self.assertEqual(3, len(state.getConnections()))
+
+	def testIsEndNegotiationFinal(self):
+		actions = [self.endNegotiation1]
+		state = SAOPState(actions, self.connections3, self.progressrounds,
+				self.settings, None, None)
+		self.assertEqual({},state.getAgreements().getMap())
+		self.assertEqual(None,state.getError())
+		self.assertTrue(state.isFinal(self.NOW))
+
+	def testIsProtocolErrorFinal(self):
+		state = SAOPState(self.actions1, self.connections3, self.progressrounds,
+				self.settings, None,
+				ProtocolException("test error", PartyId("test")));
+		self.assertTrue(state.isFinal(self.NOW))
+		self.assertNotEqual(None, state.getError())
+
+	def testRefuseImmediateAccept(self):
+		Noneaccept = Accept(self.party1, None)
+		self.assertRaises(ValueError, lambda:self.state1.WithAction(self.party1, Noneaccept))
+
+	def testRefuseNotMyTurn(self):
+		actions = [self.offer1]
+		state = SAOPState(actions, self.connections3, self.progressrounds,
+				self.settings, None, None)
+		self.assertRaises(ValueError, lambda: state.WithAction(self.party1, self.offer1))
+
+	def testRefuseNoneAccept(self):
+		actions = [self.offer1]
+		state = SAOPState(actions, self.connections3, self.progressrounds,
+				self.settings, None, None);
+
+		Noneaccept = Accept(self.party2, None)
+		self.assertRaises(ValueError, lambda:state.WithAction(self.party2, Noneaccept))
+
+	def testRefuseNoneAction(self):
+		actions = [self.offer1]
+		state = SAOPState(actions, self.connections3, self.progressrounds,
+				self.settings, None, None)
+		self.assertRaises(ValueError,  lambda:state.WithAction(self.party2,  None))
+
+	def testCheckGoodAccept(self):
+		actions = [self.offer1, self.accept2]
+		state = SAOPState(actions, self.connections3, self.progressrounds,
+				self.settings, None, None)
+
+		accept = Accept(self.party3, self.bid1)
+		state = state.WithAction(self.party3, accept)
+		self.assertTrue(state.isFinal(self.NOW)) 
+		self.assertEqual(None,state.getError())
+		self.assertEqual(self.bid1, state.getAgreements().getMap().get(self.party1))
+
+	def testCheckGoodSecondAccept(self):
+		bid2 = Mock(Bid)
+		actions = [self.offer1, Offer(self.party2, bid2),
+				Accept(self.party3, bid2)]
+		state = SAOPState(actions, self.connections3, self.progressrounds,
+				self.settings, None, None)
+		accept = Accept(self.party1, bid2)
+		state = state.WithAction(self.party1, accept)
+		self.assertTrue(state.isFinal(self.NOW));
+		self.assertEqual(None,state.getError())
+		self.assertEqual(bid2, state.getAgreements().getMap().get(self.party1))
+
+	def testDeserialize(self):
+		obj:NegoState = self.jackson.parse(json.loads(self.serialized), NegoState)
+		print(obj)
+		self.assertEqual(self.state, obj)
+
+	def testSerialize(self):
+		jsob = self.jackson.toJson(self.state)
+		print(jsob)
+		self.assertEqual(json.loads(self.serialized), jsob)
Index: geniuswebcore/test/geniusweb/protocol/session/saop/SAOPTest.py
===================================================================
--- geniuswebcore/test/geniusweb/protocol/session/saop/SAOPTest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/protocol/session/saop/SAOPTest.py	(revision 67)
@@ -0,0 +1,411 @@
+from datetime import datetime, timedelta
+import time
+import unittest
+from unittest.mock import Mock
+
+from tudelft.utilities.listener.Listener import Listener
+from tudelft.utilities.repository.NoResourcesNowException import NoResourcesNowException
+from tudelft_utilities_logging.ReportToLogger import ReportToLogger
+from uri.uri import URI
+
+from geniusweb.actions.Accept import Accept
+from geniusweb.actions.Action import Action
+from geniusweb.actions.EndNegotiation import EndNegotiation
+from geniusweb.actions.Offer import Offer
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.deadline.Deadline import Deadline
+from geniusweb.deadline.DeadlineTime import DeadlineTime
+from geniusweb.events.CurrentState import CurrentState
+from geniusweb.inform.ActionDone import ActionDone
+from geniusweb.inform.Agreements import Agreements
+from geniusweb.inform.Finished import Finished
+from geniusweb.inform.Inform import Inform
+from geniusweb.inform.Settings import Settings
+from geniusweb.inform.YourTurn import YourTurn
+from geniusweb.progress.Progress import Progress
+from geniusweb.protocol.ProtocolException import ProtocolException
+from geniusweb.protocol.partyconnection.ProtocolToPartyConn import ProtocolToPartyConn
+from geniusweb.protocol.partyconnection.ProtocolToPartyConnFactory import ProtocolToPartyConnFactory
+from geniusweb.protocol.partyconnection.ProtocolToPartyConnections import ProtocolToPartyConnections
+from geniusweb.protocol.session.TeamInfo import TeamInfo
+from geniusweb.protocol.session.saop.SAOP import SAOP
+from geniusweb.protocol.session.saop.SAOPSettings import SAOPSettings
+from geniusweb.protocol.session.saop.SAOPState import SAOPState
+from geniusweb.references.Parameters import Parameters
+from geniusweb.references.PartyRef import PartyRef
+from geniusweb.references.PartyWithParameters import PartyWithParameters
+from geniusweb.references.PartyWithProfile import PartyWithProfile
+from geniusweb.references.ProfileRef import ProfileRef
+from geniusweb.references.ProtocolRef import ProtocolRef
+
+
+class SAOPTest(unittest.TestCase): 
+	'''
+	This test tests inner workings of the SAOP protocol, AKA 'white box' testing.
+	This adds brittleness to this test, because these internal workings may be
+	modified as long as the public API remains working. This approach was
+	necessary because fully testing the public API directly would lead to
+	excessive amounts of Mocking and unfocused tests. (with "focused test" we
+	mean that a junit test should test a small part of the code and thus aid in
+	locating the actual issue).
+	'''
+	PARTY2ID = PartyId("party2")
+	PARTY1ID = PartyId("party1")
+	parameters = Parameters()
+	SAOPPROTOCOL = ProtocolRef(URI("SAOP"))
+
+	NOW = 1000
+	DEADLINE =1000
+	party1ref = PartyRef(URI("party1"))
+	party2ref = PartyRef(URI("party2"))
+	partywithparam1 = PartyWithParameters(party1ref, parameters)
+	partywithparam2 = PartyWithParameters(party2ref, parameters)
+
+
+	def setUp(self):
+		self.state = Mock(SAOPState)
+		self.connectedstate = Mock(SAOPState)
+		self.failstate = Mock(SAOPState)
+		self.finalstate = Mock(SAOPState)
+	
+		self.settings = Mock(SAOPSettings)
+		self.team1 = Mock(TeamInfo)
+		self.team2 = Mock(TeamInfo)
+		self.conn1 = Mock(ProtocolToPartyConn)
+		self.conn2 = Mock(ProtocolToPartyConn);
+		self.progress = Mock(Progress)
+		self.testlistener = Mock(Listener)
+		
+		self.deadlinetime = Mock(DeadlineTime)
+
+
+		self.profile1 = Mock(ProfileRef)
+		self.profile2 = Mock(ProfileRef)
+
+		pwp1 = PartyWithProfile(self.partywithparam1, self.profile1);
+		pwp2 = PartyWithProfile(self.partywithparam2, self.profile2);
+		self.team1.getParties=Mock(return_value=[pwp1])
+		self.team2.getParties=Mock(return_value=[pwp2])
+
+		partyprofiles = {}
+		partyprofiles[self.PARTY1ID]= self.team1
+		partyprofiles[self.PARTY2ID]= self.team2
+		
+		
+		self.pwpmap = {}
+		self.pwpmap[self.PARTY1ID]=pwp1
+		self.pwpmap[self.PARTY2ID]=pwp2
+		self.deadlinetime.getDuration=Mock(return_value=self.DEADLINE)
+
+		teams = []
+		teams.append(self.team1);
+		teams.append(self.team2);
+		self.settings.getTeams=Mock(return_value=teams)
+		self.settings.getAllParties=Mock(return_value=[pwp1, pwp2])
+		self.settings.getDeadline=Mock(return_value=self.deadlinetime)
+
+		self.factory = Mock(ProtocolToPartyConnFactory)
+		# connections = Mock(List);
+		connections = [self.conn1, self.conn2]
+		self.factory.connectAll=Mock(return_value=connections)
+
+		# hack the state.with(connection) stuff simplistically
+
+		self.conn1.getParty=Mock(return_value=self.PARTY1ID)
+		self.conn2.getParty=Mock(return_value=self.PARTY2ID)
+
+		self.MockState(self.connectedstate, "connected state", True)
+		self.MockState(self.state, "running state",True);
+		
+		self.MockState(self.finalstate, "final state",True);
+		self.finalstate.isFinal=Mock(return_value=True)
+		self.MockState(self.failstate, "fail state", True);
+		self.failstate.isFinal=Mock(return_value=True)
+
+		self.connectionswithparties = ProtocolToPartyConnections([self.conn1, self.conn2])
+
+		# HACK thenReturn twice, so that 2nd call to iterator() returns new
+		# iterator instead of the old one
+		profilesmap = {}
+		profilesmap[self.PARTY1ID]= pwp1
+		profilesmap[self.PARTY2ID]= pwp2
+		self.state.getPartyProfiles=Mock(return_value=profilesmap)
+
+		self.saop = SAOP(self.state, ReportToLogger("test"),
+				self.connectionswithparties)
+		self.saop.addListener(self.testlistener)
+
+
+	def MockState(self, state:SAOPState , asText:str, isConnected:bool ) :
+		'''
+		All states are more or less the same, but are slightly modified
+		
+		@param state  the state to Mock (must be already
+		@param asText short name for the state, for debugging
+		@param bool true if parties are connected
+		'''
+		state.getSettings=Mock(return_value=self.settings) #type:ignore
+		if isConnected:
+			state.getConnections=Mock(return_value=[self.PARTY1ID, self.PARTY2ID])   #type:ignore
+		else:
+			state.getConnections=Mock(return_value=[]) #type:ignore
+		# when(state.getConnections()).thenReturn(connectionswithparties);
+
+		state.getProgress=Mock(return_value=self.progress)  #type:ignore
+		state.WithParty=Mock(return_value=self.connectedstate)  #type:ignore
+		state._getNextActor=Mock(return_value=self.PARTY1ID)  #type:ignore
+		state.WithAction= Mock(side_effect=lambda pid, act: #type:ignore
+				self.finalstate if isinstance(act, EndNegotiation) else\
+				state )
+					
+		state.WithProgress=Mock(return_value=state)  #type:ignore
+		state.__repr__=Mock(return_value=asText)  #type:ignore
+		state.WithException=Mock(return_value=self.failstate)  #type:ignore
+		state.getAgreements=Mock(return_value=Agreements())  #type:ignore
+
+		#explicit default. This differs from Java, where default bools to false
+		state.isFinal=Mock(return_value=False)	#type:ignore
+		state.getPartyProfiles = Mock(return_value=self.pwpmap) #type:ignore
+
+	def testConstructor(self):
+		state1 = Mock(SAOPState)
+		dl = Mock(Deadline)
+		set = Mock(SAOPSettings)
+		state1.getSettings=Mock(return_value=set)
+		set.getDeadline=Mock(return_value=dl)
+		dl.getDuration=Mock(return_value=1000)
+		SAOP(state1, ReportToLogger("test"), self.connectionswithparties)
+		self.assertEqual([], self.state.WithException.call_args_list)
+		self.assertEqual([], self. testlistener.notifyChange.call_args_list)
+
+
+	def testConnect(self):
+		self.saop._connect(self.factory)
+
+		self.assertEqual(self.connectedstate, self.saop.getState())
+		self.assertEqual([], self.state.WithException.call_args_list)
+		self.assertEqual([], self.testlistener.notifyChange.call_args_list)
+	
+
+	def testConnectFailingConnection(self):
+		# override the factory connect to throw IOException. This exception
+		# should boil up and cause the connect to fail.
+		def __raise(refs):
+			raise ConnectionError("Refusing connection")
+		self.factory.connectAll=Mock(side_effect=__raise)
+		this=self
+		self.assertRaises(ConnectionError, lambda:this.saop._connect(this.factory))
+		# following was never reached?
+		#verify(state, times(0)).with(any(ProtocolException));
+		#verify(testlistener, times(0)).notifyChange(any(CurrentState));
+
+	def testConnectRetry(self):
+		# override the factory connect to throw NoResourcesNowException and
+		# then succeed 2nd time.
+		this=self
+		
+		firsttime=True
+		def __throwsThenSucceeds():
+			if firsttime:
+				firsttime=False
+				raise NoResourcesNowException("Refusing connection",\
+						datetime.now() + timedelta(seconds=0.5))
+			return this.connections
+		self.factory.connect=Mock(side_effect=__throwsThenSucceeds)
+		
+		self.saop._connect(self.factory)
+
+		self.assertEqual(self.connectedstate, self.saop.getState())
+		#verify(state, times(0)).with(any(ProtocolException));
+		self.assertEqual([],  [e \
+			for e in self.state.WithException.call_args_list\
+			if not isinstance( e,ProtocolException)])
+		self.assertEqual([],self.testlistener.notifyChange.call_args_list) 
+
+	def  testSetup(self) :
+		self.saop._setupParties()
+
+		# were the connections attached properly?
+		self.assertEqual(1, len(self.conn1.addListener.call_args_list))
+		self.assertEqual(1, len(self.conn2.addListener.call_args_list))
+
+		# were the settings send to each party?
+		self.assertEqual(1, len([call for call in self.conn1.send.call_args_list \
+							if isinstance(call[0][0],Settings)]))
+		self.assertEqual(
+				Settings(self.PARTY1ID, self.profile1, self.SAOPPROTOCOL, self.progress, self.parameters),
+				self.conn1.send.call_args_list[0][0][0])
+
+		self.assertEqual(1, len([call for call in self.conn2.send.call_args_list \
+							if isinstance(call[0][0],Settings)]))
+		self.assertEqual(
+				Settings(self.PARTY2ID, self.profile2, self.SAOPPROTOCOL, self.progress, self.parameters),
+				self.conn2.send.call_args_list[0][0][0])
+				
+		self.assertEqual([], self.state.WithException.call_args_list)
+		self.assertEqual([], [v for v in self.testlistener.notifyChange.call_args_list if isinstance(v, CurrentState)])
+		
+
+	def testActionRequestWrongActor(self):
+		self.saop._actionRequest(self.conn2, Mock(EndNegotiation))
+		self.assertEqual(1, len([call for call in self.state.WithException.call_args_list\
+								 if isinstance(call[0][0], ProtocolException) ]))
+		self.assertEqual(1, len([call for call in self.testlistener.notifyChange.call_args_list
+							if isinstance(call[0][0], CurrentState)]))
+
+	def testActionRequest(self):
+		self.saop._nextTurn()
+
+		self.state._getNextActor=Mock(return_value=self.PARTY1ID)
+		self.state.withAction = Mock(return_value=self.finalstate)
+
+		self.saop._actionRequest(self.conn1, Mock(EndNegotiation))
+
+		self.assertEqual([] , [call for call in self.state.WithException.call_args_list
+						if isinstance(call[0][0], ProtocolException)])
+		#verify(state, times(1)).with(eq(PARTY1ID), any(EndNegotiation));
+		self.assertEqual(1, len([call for call in self.state.WithAction.call_args_list
+				if call[0][0]==self.PARTY1ID and\
+				isinstance(call[0][1], EndNegotiation)]))
+		# verify broadcast
+		self.assertEqual(1, len([call for call in self.conn1.send.call_args_list
+				if isinstance(call[0][0], ActionDone)]))
+		self.assertEqual(1, len([call for call in self.conn2.send.call_args_list
+				if isinstance(call[0][0], ActionDone)]))
+
+		# state.getNextActor() is frozen to PARTY1 by our Mocking
+		#verify(conn1, times(1)).send(isA(YourTurn));
+		self.assertEqual(1, len([call for call in self.conn1.send.call_args_list
+				if isinstance(call[0][0], YourTurn)]))
+		#verify(conn2, times(0)).send(isA(YourTurn));
+		self.assertEqual(0, len([call for call in self.conn2.send.call_args_list
+				if isinstance(call[0][0], YourTurn)]))
+		#listener should be informed about final state.
+		#verify(testlistener, times(0)).notifyChange(any(CurrentState));
+		self.assertEqual(1, len([call for call in self.testlistener.notifyChange.call_args_list
+				if isinstance(call[0][0], CurrentState)]))
+		
+	def  testFinalActionRequest(self):
+		this=self
+		#FIXME double mock??
+		#self.state.getNextActor=Mock(return_value=self.PARTY1ID)
+		def __nextstate(pid:PartyId, act:Action):
+			if isinstance(act, EndNegotiation):
+				return this.finalstate
+
+		self.state.withAction=Mock(side_effect=__nextstate)
+		# when(finalstate.getAgreements()).thenReturn(Mock(Agreements));
+
+		self.saop._nextTurn() # ensure we send YourTurn to party1.
+		self.saop._actionRequest(self.conn1, Mock(EndNegotiation))
+
+		#verify(state, times(0)).with(any(ProtocolException));
+		self.assertEqual([], [call for call in self.state.WithException.call_args_list
+					if isinstance(call[0][0], ProtocolException)	])
+		#verify(state, times(1)).with(eq(PARTY1ID), any(EndNegotiation));
+		self.assertEqual(1, len([call for call in self.state.WithAction.call_args_list\
+					if isinstance(call[0][1], EndNegotiation)]))
+		
+		# verify broadcast
+		#erify(conn1, times(1)).send(isA(Finished));
+		self.assertEqual(1, len([call for call in self.conn1.send.call_args_list
+			if isinstance(call[0][0], Finished)]))
+
+		#verify(conn2, times(1)).send(isA(Finished));
+		self.assertEqual(1, len([call for call in self.conn2.send.call_args_list
+			if isinstance(call[0][0], Finished)]))
+
+		#verify(testlistener, times(1)).notifyChange(any(CurrentState));
+		self.assertEqual(1, len([call for call in self.testlistener.notifyChange.call_args_list
+			if isinstance(call[0][0], CurrentState)]))
+
+		#verify(conn1, times(1)).send(isA(YourTurn));
+		self.assertEqual(1, len([call for call in self.conn1.send.call_args_list
+			if isinstance(call[0][0], YourTurn)]))
+		#verify(conn2, times(0)).send(isA(YourTurn));
+		self.assertEqual( [] , [call for call in self.conn2.send.call_args_list
+			if isinstance(call[0][0], YourTurn)])
+
+	def testStart(self):
+		self.saop.start(self.factory)
+
+	def testActionFailNextYourturn(self):
+		this=self
+		def __throwExc(inf:Inform):
+			if isinstance(inf, YourTurn):
+				raise ConnectionError("fail sending yourturn")
+		self.conn1.send=Mock(side_effect=__throwExc)
+		# CHECK is the state really final after an exception?
+		self.state.WithException=Mock(return_value=this.finalstate)
+		# when(state.getAgreements()).thenReturn(Mock(Agreements));
+		self.saop._actionRequest(self.conn1, Mock(Accept))
+
+		
+		#verify(state, times(1)).with(any(ProtocolException));
+		self.assertEqual( 1, len([call for call in self.state.WithException.call_args_list
+					if isinstance(call[0][0], ProtocolException)	]))
+		#verify(testlistener, times(1)).notifyChange(any(CurrentState));
+		self.assertEqual(1, len([call for call in self.testlistener.notifyChange.call_args_list
+				if isinstance(call[0][0], CurrentState)]))
+
+	def testActionNotTheTurn(self) :
+		def __throwExc(inf:Inform):
+			if isinstance(inf, YourTurn):
+				raise ConnectionError("fail sending yourturn")
+		self.conn1.send=Mock(side_effect=__throwExc)
+
+		# when(failstate.getAgreements()).thenReturn(Mock(Agreements));
+		# not turn of conn2.
+		self.saop._actionRequest(self.conn2, Mock(EndNegotiation))
+
+		#verify(state, times(1)).with(any(ProtocolException));
+		self.assertEqual( 1, len([call for call in self.state.WithException.call_args_list
+			if isinstance(call[0][0], ProtocolException)	]))
+		#verify(testlistener, times(1)).notifyChange(any(CurrentState));
+		self.assertEqual(1, len([call for call in self.testlistener.notifyChange.call_args_list
+				if isinstance(call[0][0], CurrentState)]))
+
+
+	def testActionInFinalState(self):
+		saop = SAOP(self.finalstate, ReportToLogger("test"),
+				self.connectionswithparties);
+		# when(finalstate.getAgreements()).thenReturn(Mock(Agreements));
+
+		saop._actionRequest(self.conn1, Mock(Offer))
+
+		#verify(finalstate, times(0)).with(any(ProtocolException));
+		self.assertEqual( [], [call for call in self.state.WithException.call_args_list
+			if isinstance(call[0][0], ProtocolException)	])
+		#verify(testlistener, times(0)).notifyChange(any());
+		self.assertEqual([], self.testlistener.notifyChange.call_args_list)
+
+	def testDescription(self):
+		self.assertNotEquals(None,self.saop.getDescription())
+
+	def testAddParticipant(self):
+		self.assertRaises(ValueError, lambda:self.saop.addParticipant(None))
+		
+	def testDeadlineTimer(self):
+		unconnectedstate = Mock(SAOPState)
+		self.MockState(unconnectedstate, "unconnected state", False)
+		saopempty = SAOP(unconnectedstate, ReportToLogger("test"),
+				ProtocolToPartyConnections([]))
+		saopempty.addListener(self.testlistener)
+
+		saopempty.start(self.factory);
+		# verify(testlistener, times(0)).notifyChange(any(CurrentState.class));
+		self.assertEqual([], [call for call in self.testlistener.notifyChange.call_args_list
+ 			if isinstance(call[0][0], CurrentState)	])
+
+		#verify(conn1, times(1)).send(isA(YourTurn.class));
+		self.assertEqual(1, len([call for call in self.conn1.send.call_args_list
+			if isinstance(call[0][0], YourTurn)]))
+		time.sleep( (self.DEADLINE + SAOP._TIME_MARGIN + 100)/1000.)
+		#verify(testlistener, times(1)).notifyChange(any(CurrentState.class));
+		self.assertEqual(1, len([call for call in self.testlistener.notifyChange.call_args_list
+			if isinstance(call[0][0], CurrentState)]))
+		#verify(conn1, times(1)).send(isA(Finished.class));
+		self.assertEqual(1, len([call for call in self.conn1.send.call_args_list
+			if isinstance(call[0][0], Finished)]))
+
+
Index: geniuswebcore/test/geniusweb/references/ParametersTest.py
===================================================================
--- geniuswebcore/test/geniusweb/references/ParametersTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/references/ParametersTest.py	(revision 67)
@@ -1,38 +1,92 @@
+import json
+from typing import List
 import unittest
 
 from pyson.ObjectMapper import ObjectMapper
+from unitpy.GeneralTests import GeneralTests
 
 from geniusweb.references.Parameters import Parameters
 
 
-class ParametersTest (unittest.TestCase) :
+class ParametersTest (unittest.TestCase, GeneralTests[Parameters]) :
 	pyson=ObjectMapper()
-	params=Parameters({'a':1})
-	params1=Parameters({'a':1})
-	params2=Parameters()
+	params1=Parameters()
+	params1a=Parameters()
+	params2=Parameters({'a':1, 'b':2.0})
 
-	paramsjson={'a':1}
+	paramsjson='{"a":1, "b":2.0}'
+
+	def getGeneralTestData(self)-> List[List[Parameters]] :
+		return [[ self.params1, self.params1a],[self.params2]]
+
+	def getGeneralTestStrings(self)->List[str]:
+		return ["\\{\\}", "\\{a=1, b=2.0\\}"]
+
 
 	def testGet(self):
-		self.assertEqual({'a':1}, self.params.getParameters())
+		self.assertEqual({'a':1, 'b':2.0}, self.params2.getParameters())
 		
+
+	def testGetWrongClass(self):
+		self.assertRaises(ValueError, lambda:self.params2.getType("a", str))
+
+	def testGetInteger(self) :
+		self.assertEqual(1, self.params2.getType("a", int))
+		self.assertEqual(2.0, self.params2.getType("b", float))
+
+	def testGetDouble(self):
+		self.assertEquals( 2., self.params2.getDouble("b", 7.0, None, None))
+		self.assertEquals(2., self.params2.getDouble("b", 7.0, 0., 4.))
+
+		# test outside range
+		self.assertEqual(7., self.params2.getDouble("b", 7., 4., 6.))
+		self.assertEqual(7., self.params2.getDouble("b", 7., -2., 1.))
+
+		# test fallback None
+		self.assertEqual(None, self.params2.getDouble("b", None, 4., 6.))
+
+	def testGetDoubleWithErrors(self):
+		# a does not contain a double
+		self.assertEqual(7., self.params2.getDouble("a", 7., 4., 6.));
+		# c does not exist
+		self.assertEqual(7., self.params2.getDouble("c", 7., 4., 6.));
+
+	def testWith(self):
+		p = self.params2.With("a", 2)
+		self.assertEqual(1, self.params2.get("a"))
+		self.assertEqual(2., self.params2.get("b"))
+		self.assertEqual(2, p.get("a"))
+		self.assertEqual(2., p.get("b"))
+
+	def testWithParams(self):
+		upd = Parameters().With("a", 2)
+		p = self.params2.WithParameters(upd)
+		self.assertEqual(1, self.params2.get("a"))
+		self.assertEqual(2., self.params2.get("b"))
+		self.assertEqual(2, p.get("a"))
+		self.assertEqual(2., p.get("b"))
+
+	def testIsEmpty(self) :
+		self.assertTrue(self.params1.isEmpty())
+		self.assertFalse(self.params2.isEmpty())
+
+	def testContainsKey(self):
+		self.assertTrue(self.params2.containsKey("a"))
+		self.assertFalse(self.params2.containsKey("c"))
+
+
 	def testSerialize(self):
-		print(str(self.pyson.toJson(self.params)))
-		self.assertEqual(self.paramsjson, self.pyson.toJson(self.params))
+		print(str(self.pyson.toJson(self.params2)))
+		self.assertEqual(json.loads(self.paramsjson), self.pyson.toJson(self.params2))
 		
 	def testDeserialize(self):
-		self.assertEqual(self.params, self.pyson.parse(self.paramsjson, Parameters))
+		self.assertEqual(self.params2, self.pyson.parse(json.loads(self.paramsjson), Parameters))
 	
-	def testRepr(self):
-		self.assertEqual("{'a': 1}", repr(self.params))
 		
-	def testEqual(self):
-		self.assertEqual(self.params, self.params1)
-		self.assertNotEqual(self.params, self.params2)
-		self.assertEqual(hash(self.params), hash(self.params1))
-		self.assertNotEqual(hash(self.params), hash(self.params2))
+	def testWith2(self):
+		p=Parameters({'a':1}).With('b', 2)
+		self.assertEqual({'a':1, 'b':2}, p.getParameters())
 		
+	def testParamNotAvailable(self):
+		self.assertEqual(None, self.params1.get("nosuchkey"))
 		
-	def testWith(self):
-		p=self.params.With('b', 2)
-		self.assertEqual({'a':1, 'b':2}, p.getParameters())
Index: geniuswebcore/test/geniusweb/references/PartiWithParamsTest.py
===================================================================
--- geniuswebcore/test/geniusweb/references/PartiWithParamsTest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/references/PartiWithParamsTest.py	(revision 67)
@@ -0,0 +1,43 @@
+import json
+from typing import List
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+from unitpy.GeneralTests import GeneralTests
+from uri.uri import URI
+
+from geniusweb.references.Parameters import Parameters
+from geniusweb.references.PartyRef import PartyRef
+from geniusweb.references.PartyWithParameters import PartyWithParameters
+
+
+class PartiWithParamsTest (unittest.TestCase, GeneralTests[PartyWithParameters]):
+	serialized = "{\"partyref\":\"ws:party1\",\"parameters\":{\"a\":1}}";
+
+	party1 =  PartyRef( URI("ws:party1"))
+	party2 =  PartyRef( URI("ws:party2"))
+	param1 =  Parameters()
+	param2 =  Parameters().With("a", 1)
+	party1param1 =  PartyWithParameters(party1, param1)
+	party1param1a = PartyWithParameters(party1, param1)
+	party1param2 =  PartyWithParameters(party1, param2)
+
+	def getGeneralTestData(self)->List[List[PartyWithParameters]]:
+		return [[	self.party1param1, self.party1param1a, self.party1param1a],
+				[self.party1param2]]
+
+	def getGeneralTestStrings(self) ->List[str] :
+		return ["PartyRef.ws:party1.","PartyRef.ws:party1.\\{a=1\\}"]
+
+	def testSerialize(self) :
+		jackson = ObjectMapper()
+
+		jsonobj = jackson.toJson(self.party1param2)
+		print(jsonobj)
+		self.assertEqual(json.loads(self.serialized), jsonobj)
+
+	def testDeserialize(self):
+		jackson = ObjectMapper()
+		p = jackson.parse(json.loads(self.serialized), PartyWithParameters)
+		print(p)
+		self.assertEqual(self.party1param2, p)
Index: geniuswebcore/test/geniusweb/references/PartyRefTest.py
===================================================================
--- geniuswebcore/test/geniusweb/references/PartyRefTest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/references/PartyRefTest.py	(revision 67)
@@ -0,0 +1,46 @@
+import json
+from typing import List
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+from unitpy.GeneralTests import GeneralTests
+from uri.uri import URI
+
+from geniusweb.references.PartyRef import PartyRef
+
+
+class PartyRefTest (unittest.TestCase, GeneralTests[PartyRef]):
+
+	asJson = "\"ws:localhost/party1\""
+
+	party1 =  PartyRef( URI("ws:localhost/party1"))
+	party1a = PartyRef( URI("ws:localhost/party1"))
+	party2 = PartyRef(URI("ws:localhost/party2"))
+	party3 = PartyRef( URI("http:localhost/party3"))
+
+
+	def getGeneralTestData(self) -> List[List[PartyRef]]:
+		return [ [self.party1, self.party1a],[self.party2], [self.party3]]
+
+	def getGeneralTestStrings(self) -> List[str] :
+		return ["PartyRef.*party1.", "PartyRef.*party2.",
+				"PartyRef.*party3."]
+
+	def testSmoke(self) :
+		PartyRef(URI("ws:localhost"))
+
+	def testNull(self):
+		self.assertRaises(ValueError, lambda:PartyRef(None))
+
+	def testSerialize(self) :
+		jackson = ObjectMapper()
+
+		jsonobj = jackson.toJson(self.party1)
+		print(json.dumps(jsonobj))
+		self.assertEqual(self.asJson, json.dumps(jsonobj))
+
+	def testDeserialize(self):
+		jackson = ObjectMapper()
+		p = jackson.parse(json.loads(self.asJson), PartyRef);
+		print(p)
+		self.assertEqual(self.party1, p)
Index: geniuswebcore/test/geniusweb/references/PartyWithProfileTest.py
===================================================================
--- geniuswebcore/test/geniusweb/references/PartyWithProfileTest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/references/PartyWithProfileTest.py	(revision 67)
@@ -0,0 +1,70 @@
+import json
+from typing import List
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+from unitpy.GeneralTests import GeneralTests
+from uri.uri import URI
+
+from geniusweb.references.Parameters import Parameters
+from geniusweb.references.PartyRef import PartyRef
+from geniusweb.references.PartyWithParameters import PartyWithParameters
+from geniusweb.references.PartyWithProfile import PartyWithProfile
+from geniusweb.references.ProfileRef import ProfileRef
+
+
+class PartyWithProfileTest (unittest.TestCase,  GeneralTests[PartyWithProfile]):
+
+	serialized = "{\"party\":{\"partyref\":\"ws:party1\",\"parameters\":{}},\"profile\":\"ws:profile1\"}"
+
+	party1 = PartyRef(URI("ws:party1"))
+	party1a =  PartyRef( URI("ws:party1"))
+	party2 =  PartyRef( URI("ws:party2"))
+	party3 = PartyRef( URI("http:party3"))
+
+	profile1 = ProfileRef(URI("ws:profile1"))
+	profile2 = ProfileRef( URI("ws:profile2"))
+
+	settings1 =  Parameters()
+	settings2 =  Parameters({"a": 1})
+
+	party1withparams1 =  PartyWithParameters(party1,settings1)
+	party1withparams2 =  PartyWithParameters(party1,settings2)
+	party2withparams1 =  PartyWithParameters(party2,settings1)
+
+	partyprof1 =  PartyWithProfile(party1withparams1, profile1)
+	partyprof1a =  PartyWithProfile(party1withparams1, profile1)
+	partyprof2 =  PartyWithProfile(party2withparams1, profile1)
+	partyprof3 =  PartyWithProfile(party1withparams1, profile2)
+	partyprof4 =  PartyWithProfile(party1withparams2, profile1)
+
+
+	def getGeneralTestData(self) -> List[List[PartyWithProfile]] :
+		return [[self.partyprof1, self.partyprof1a], [self.partyprof2],[self.partyprof3],
+				[self.partyprof4]]
+
+	def getGeneralTestStrings(self) -> List[str] :
+		return [
+				"PartyWithProfile.PartyRef.ws:party1.,ProfileRef.ws:profile1.*",
+				"PartyWithProfile.PartyRef.ws:party2.,ProfileRef.ws:profile1.*",
+				"PartyWithProfile.PartyRef.ws:party1.,ProfileRef.ws:profile2.*",
+				"PartyWithProfile.PartyRef.ws:party1.{a=1},ProfileRef.ws:profile1.*"]
+
+	def testSmoke(self) :
+		pass
+
+	def testNull(self) :
+		self.assertRaises(ValueError, lambda:PartyWithProfile(None, None))
+
+	def testSerialize(self) :
+		jackson = ObjectMapper()
+
+		jsonobj = jackson.toJson(self.partyprof1);
+		print(str(jsonobj))
+		self.assertEqual(json.loads(self.serialized), jsonobj);
+
+	def testDeserialize(self):
+		jackson = ObjectMapper()
+		p = jackson.parse(json.loads(self.serialized),	PartyWithProfile)
+		print(str(p))
+		self.assertEqual(self.partyprof1, p)
Index: geniuswebcore/test/geniusweb/references/ProfileRefTest.py
===================================================================
--- geniuswebcore/test/geniusweb/references/ProfileRefTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/references/ProfileRefTest.py	(revision 67)
@@ -2,5 +2,5 @@
 
 from pyson.ObjectMapper import ObjectMapper
-from uri import URI #type:ignore
+from uri.uri import URI #type:ignore
 from geniusweb.references.ProfileRef import ProfileRef
 
Index: geniuswebcore/test/geniusweb/references/ProtocolRefTest.py
===================================================================
--- geniuswebcore/test/geniusweb/references/ProtocolRefTest.py	(revision 66)
+++ geniuswebcore/test/geniusweb/references/ProtocolRefTest.py	(revision 67)
@@ -2,5 +2,5 @@
 
 from pyson.ObjectMapper import ObjectMapper
-from uri import URI #type:ignore
+from uri.uri import URI #type:ignore
 from geniusweb.references.ProtocolRef import ProtocolRef
 
Index: geniuswebcore/test/geniusweb/simplerunner/BlockingQueueTest.py
===================================================================
--- geniuswebcore/test/geniusweb/simplerunner/BlockingQueueTest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/simplerunner/BlockingQueueTest.py	(revision 67)
@@ -0,0 +1,37 @@
+from threading import Thread
+import time
+import unittest
+from geniusweb.simplerunner.BlockingQueue import BlockingQueue
+
+
+
+class BlockingQueueTest(unittest.TestCase):
+    def testSimple(self):
+        q=BlockingQueue[int](4)
+        self.assertEqual(0, q.size())
+        q.put(1)
+        q.put(2)
+        q.put(3)
+        self.assertEqual(3, q.size())
+        self.assertEqual(1,q.take())
+        self.assertEqual(2,q.take())
+        self.assertEqual(3,q.take())
+        self.assertEqual(0, q.size())
+        
+    def testThreading(self):
+        q=BlockingQueue[int](4)
+
+        def pushnumbers():
+            for n in range(4):
+                time.sleep(1)
+                q.put(n)
+        
+        t = Thread(target = pushnumbers)
+        t.start()
+        
+        for n in range(4):
+            val=q.take()
+            print(val)
+            self.assertEqual(n, val)
+        
+        
Index: geniuswebcore/test/geniusweb/simplerunner/ClassPathConnectionFactoryTest.py
===================================================================
--- geniuswebcore/test/geniusweb/simplerunner/ClassPathConnectionFactoryTest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/simplerunner/ClassPathConnectionFactoryTest.py	(revision 67)
@@ -0,0 +1,63 @@
+import time
+import unittest
+
+from uri.uri import URI
+
+from geniusweb.references.PartyRef import PartyRef
+from geniusweb.simplerunner.ClassPathConnectionFactory import ClassPathConnectionFactory, \
+	ConnectionPair
+
+
+class ClassPathConnectionFactoryTest(unittest.TestCase):
+	factory=ClassPathConnectionFactory()
+	PARTYREF = PartyRef(URI("pythonpath:test.testparty.TestParty.TestParty"))
+
+
+	def testWrongURI(self): 
+		self.assertRaises(ValueError, 
+				lambda:self.factory.connect(PartyRef(URI("http://blabla"))))
+
+
+	def testNullPath(self):
+		# bad because classpath should not start with //
+		self.assertRaises(ValueError, lambda:self.factory.connect(PartyRef(URI(
+			"pythonpath://some.class.path"))))
+
+	def testUnknownParty(self):
+		self.assertRaises(ValueError, lambda:self.factory.connect(PartyRef(URI(
+			"pythonpath:blabla.bla"))))
+
+	def testParty(self) :
+		conn = self.factory.connect(self.PARTYREF)
+		self.assertIsNotNone(conn)
+		conn.close()
+
+	def testConnectionPairProtocolClosesNormally(self):
+		cpair = ConnectionPair(self.PARTYREF);
+		self.assertEquals(2, len(cpair.getOpenConnections()))
+		cpair.getProtocolToPartyConn().close()
+		time.sleep(0.1)
+		self.assertEqual(0, len(cpair.getOpenConnections()))
+			
+	def testConnectionPairProtocolClosesWithExc(self):
+		cpair = ConnectionPair(self.PARTYREF)
+		self.assertEqual(2, len(cpair.getOpenConnections()))
+		cpair.getProtocolToPartyConn().setError(ValueError("test"))
+		time.sleep(0.1)
+		self.assertEqual(0, len(cpair.getOpenConnections()))
+
+	def testConnectionPairPartyClosesNormally(self):
+		cpair = ConnectionPair(self.PARTYREF)
+		self.assertEqual(2, len(cpair.getOpenConnections()))
+		cpair._getPartyToProtocolConn().close()
+		time.sleep(0.1)
+
+		self.assertEqual(0, len(cpair.getOpenConnections()))
+
+	def testConnectionPairPartyClosesWithExc(self):
+		cpair = ConnectionPair(self.PARTYREF)
+		self.assertEqual(2, len(cpair.getOpenConnections()))
+		cpair._getPartyToProtocolConn().setError(ValueError("test"))
+		time.sleep(0.1)
+		self.assertEqual(0, len(cpair.getOpenConnections()))
+			
Index: geniuswebcore/test/geniusweb/simplerunner/SessionRunnerE2ETest.py
===================================================================
--- geniuswebcore/test/geniusweb/simplerunner/SessionRunnerE2ETest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/simplerunner/SessionRunnerE2ETest.py	(revision 67)
@@ -0,0 +1,64 @@
+from fileinput import filename
+import json
+from pathlib import Path
+import unittest
+from unittest.mock import Mock
+
+from pyson.ObjectMapper import ObjectMapper
+from tudelft_utilities_logging.ReportToLogger import ReportToLogger
+
+from geniusweb.protocol.NegoSettings import NegoSettings
+from geniusweb.simplerunner.ClassPathConnectionFactory import ClassPathConnectionFactory
+from geniusweb.simplerunner.NegoRunner import NegoRunner
+
+
+class SessionRunnerE2ETest(unittest.TestCase):
+	'''
+	E2E test doing a full real run of a session with real agents and protocol.
+	NOTICE This will exercise a full system to errors here may point far outside
+	this module.
+	'''
+	TEST_RUNTIME = 5000
+	jackson = ObjectMapper()
+	logger = Mock(ReportToLogger("test"))
+	filename:str
+	runner:NegoRunner
+
+	params = ["test/resources/settings.json", 
+# 				 "test/resources/settings2.json" ,          PARTIALORDERING NOT YET
+ 				 "test/resources/settingsbadprofile.json"
+# 				 "test/resources/shaoptoursettings.json",   ALLPERMUTATIONSETTINGS NOT YET
+# 				 "test/resources/mopac.json" ,              MOPAC NOT YET
+# 				 "test/resources/tournament.json"			ALLPERMUTATIONSETTINGS NOT YET
+				 ]
+
+
+	
+	def testAll(self):
+		''' 
+		runs all the tests. Python has no standard 'parameterized' mechanism
+		so we have to do it ourselves.
+		'''
+		for filename in self.params:
+			self.filename=filename
+			with self.subTest("before "+self.filename):
+				self.before()
+			with self.subTest("runMain"+self.filename):
+				self.runTestMainFunction()
+
+	def before(self):
+		print("Running from file " + self.filename)
+		serialized = Path(self.filename).read_text("utf-8")
+		settings=self.jackson.parse(json.loads(serialized), NegoSettings)
+		self.runner = NegoRunner(settings, ClassPathConnectionFactory(),
+				self.logger,self.TEST_RUNTIME)
+			
+	def runTestMainFunction(self) :
+		NegoRunner.main([self.filename])
+
+	def runTest(self) :
+		self.runner.run()
+		print("Final state:\n" + str(self.runner.getProtocol().getState()))
+		self.assertTrue(self.runner.isProperlyStopped())
+		# check no warnings and more severe
+		self.asssertEqual([], self.logger.log.call_args_list)
Index: geniuswebcore/test/geniusweb/simplerunner/SessionRunnerTest.py1
===================================================================
--- geniuswebcore/test/geniusweb/simplerunner/SessionRunnerTest.py1	(revision 67)
+++ geniuswebcore/test/geniusweb/simplerunner/SessionRunnerTest.py1	(revision 67)
@@ -0,0 +1,58 @@
+import unittest
+from unittest.mock import Mock
+
+from tudelft_utilities_logging.ReportToLogger import ReportToLogger
+from tudelft_utilities_logging.Reporter import Reporter
+
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.protocol.CurrentNegoState import CurrentNegoState
+from geniusweb.protocol.ProtocolException import ProtocolException
+from geniusweb.protocol.session.SessionProtocol import SessionProtocol
+from geniusweb.protocol.session.SessionSettings import SessionSettings
+from geniusweb.protocol.session.SessionState import SessionState
+from geniusweb.simplerunner.ClassPathConnectionFactory import ClassPathConnectionFactory
+from geniusweb.simplerunner.NegoRunner import NegoRunner
+import unittest.mock as mock
+
+
+class SessionRunnerTest(unittest.TestCase):
+	PROTOCOL_EXC = ProtocolException(
+			"fake protocol exception", PartyId("test"))
+	NOW = 1000
+	logger = ReportToLogger("test")
+
+	def setUp(self):
+		self.finishedEvent = Mock(CurrentNegoState)
+		self.finishedstate = Mock(SessionState)
+		self.finishedstate.isFinal=Mock(return_value=True)
+		self.finishedEvent.getState=Mock(return_value=self.finishedstate)
+
+	def testSmoke(self):
+		NegoRunner(Mock(SessionSettings),
+				Mock(ClassPathConnectionFactory), self.logger, 5000)
+
+	def testRun(self):
+		settings = Mock(SessionSettings)
+		protocol = Mock(SessionProtocol)
+		settings.getProtocol=Mock(return_value=protocol)
+		factory = Mock(	ClassPathConnectionFactory)
+
+		runner=NegoRunner(settings, factory, self.logger, 5000)
+		
+		spyProtocolAddlistener = mock.patch.object(protocol, 'addListener',wraps=protocol.addListener)
+		spyProtocolStart = mock.patch.object(protocol, 'start',wraps=protocol.start)
+		spyRunnerStop:Mock = mock.patch.object(runner, 'stop', wraps=runner._stop)
+		
+		runner.run();
+		spyProtocolAddlistener.test_assert_called_once()
+		spyProtocolStart.test_assert_called_once()
+		spyRunnerStop.test_assert_not_called()
+
+
+	def testStopNormally(self):
+		# CAN NOT TEST: PYTHON HAS NO SPY WITH ARG CAPTURE
+		pass
+	
+	def testStopWithError(self) :
+		# CAN NOT TEST: PYTHON HAS NO SPY WITH ARG CAPTURE
+		pass
Index: geniuswebcore/test/geniusweb/simplerunner/TestParty.log
===================================================================
--- geniuswebcore/test/geniusweb/simplerunner/TestParty.log	(revision 67)
+++ geniuswebcore/test/geniusweb/simplerunner/TestParty.log	(revision 67)
@@ -0,0 +1,225 @@
+2021-09-13 13:13:07,814 - TestParty - INFO - party is initialized
+2021-09-13 13:13:07,816 - TestParty - INFO - party is initialized
+2021-09-13 13:13:07,816 - TestParty - INFO - party is initialized
+2021-09-13 13:13:07,818 - TestParty - INFO - party is initialized
+2021-09-13 13:13:07,818 - TestParty - INFO - party is initialized
+2021-09-13 13:13:07,818 - TestParty - INFO - party is initialized
+2021-09-13 13:13:07,820 - TestParty - INFO - party is initialized
+2021-09-13 13:13:07,820 - TestParty - INFO - party is initialized
+2021-09-13 13:13:07,820 - TestParty - INFO - party is initialized
+2021-09-13 13:13:07,820 - TestParty - INFO - party is initialized
+2021-09-13 13:22:30,227 - TestParty - INFO - party is initialized
+2021-09-13 13:22:30,229 - TestParty - INFO - party is initialized
+2021-09-13 13:22:30,229 - TestParty - INFO - party is initialized
+2021-09-13 13:22:30,231 - TestParty - INFO - party is initialized
+2021-09-13 13:22:30,231 - TestParty - INFO - party is initialized
+2021-09-13 13:22:30,231 - TestParty - INFO - party is initialized
+2021-09-13 13:22:30,232 - TestParty - INFO - party is initialized
+2021-09-13 13:22:30,232 - TestParty - INFO - party is initialized
+2021-09-13 13:22:30,232 - TestParty - INFO - party is initialized
+2021-09-13 13:22:30,232 - TestParty - INFO - party is initialized
+2021-09-13 13:22:50,994 - TestParty - INFO - party is initialized
+2021-09-13 13:24:55,678 - TestParty - INFO - party is initialized
+2021-09-13 13:24:55,786 - TestParty - INFO - party is initialized
+2021-09-13 13:24:55,786 - TestParty - INFO - party is initialized
+2021-09-13 13:24:55,896 - TestParty - INFO - party is initialized
+2021-09-13 13:24:55,896 - TestParty - INFO - party is initialized
+2021-09-13 13:24:55,896 - TestParty - INFO - party is initialized
+2021-09-13 13:24:56,004 - TestParty - INFO - party is initialized
+2021-09-13 13:24:56,004 - TestParty - INFO - party is initialized
+2021-09-13 13:24:56,004 - TestParty - INFO - party is initialized
+2021-09-13 13:24:56,004 - TestParty - INFO - party is initialized
+2021-09-13 13:24:56,114 - TestParty - INFO - party is initialized
+2021-09-13 13:24:56,114 - TestParty - INFO - party is initialized
+2021-09-13 13:24:56,114 - TestParty - INFO - party is initialized
+2021-09-13 13:24:56,114 - TestParty - INFO - party is initialized
+2021-09-13 13:24:56,114 - TestParty - INFO - party is initialized
+2021-09-13 13:25:19,319 - TestParty - INFO - party is initialized
+2021-09-13 13:25:19,423 - TestParty - INFO - party is initialized
+2021-09-13 13:25:19,423 - TestParty - INFO - party is initialized
+2021-09-13 13:25:19,529 - TestParty - INFO - party is initialized
+2021-09-13 13:25:19,529 - TestParty - INFO - party is initialized
+2021-09-13 13:25:19,529 - TestParty - INFO - party is initialized
+2021-09-13 13:25:19,635 - TestParty - INFO - party is initialized
+2021-09-13 13:25:19,635 - TestParty - INFO - party is initialized
+2021-09-13 13:25:19,635 - TestParty - INFO - party is initialized
+2021-09-13 13:25:19,635 - TestParty - INFO - party is initialized
+2021-09-13 13:25:19,744 - TestParty - INFO - party is initialized
+2021-09-13 13:25:19,744 - TestParty - INFO - party is initialized
+2021-09-13 13:25:19,744 - TestParty - INFO - party is initialized
+2021-09-13 13:25:19,744 - TestParty - INFO - party is initialized
+2021-09-13 13:25:19,744 - TestParty - INFO - party is initialized
+2021-09-13 13:26:39,309 - TestParty - INFO - party is initialized
+2021-09-13 13:26:39,421 - TestParty - INFO - party is initialized
+2021-09-13 13:26:39,421 - TestParty - INFO - party is initialized
+2021-09-13 13:26:44,907 - TestParty - INFO - party is initialized
+2021-09-13 13:26:44,907 - TestParty - INFO - party is initialized
+2021-09-13 13:26:44,907 - TestParty - INFO - party is initialized
+2021-09-13 13:26:54,727 - TestParty - INFO - party is initialized
+2021-09-13 13:26:54,727 - TestParty - INFO - party is initialized
+2021-09-13 13:26:54,727 - TestParty - INFO - party is initialized
+2021-09-13 13:26:54,727 - TestParty - INFO - party is initialized
+2021-09-13 13:26:54,838 - TestParty - INFO - party is initialized
+2021-09-13 13:26:54,838 - TestParty - INFO - party is initialized
+2021-09-13 13:26:54,838 - TestParty - INFO - party is initialized
+2021-09-13 13:26:54,838 - TestParty - INFO - party is initialized
+2021-09-13 13:26:54,838 - TestParty - INFO - party is initialized
+2021-09-13 13:27:36,289 - TestParty - INFO - party is initialized
+2021-09-13 13:27:39,646 - TestParty - INFO - party is initialized
+2021-09-13 13:27:39,646 - TestParty - INFO - party is initialized
+2021-09-13 13:27:39,756 - TestParty - INFO - party is initialized
+2021-09-13 13:27:39,756 - TestParty - INFO - party is initialized
+2021-09-13 13:27:39,756 - TestParty - INFO - party is initialized
+2021-09-13 13:27:49,713 - TestParty - INFO - party is initialized
+2021-09-13 13:27:57,637 - TestParty - INFO - party is initialized
+2021-09-13 13:27:57,637 - TestParty - INFO - party is initialized
+2021-09-13 13:28:27,035 - TestParty - INFO - party is initialized
+2021-09-13 13:28:28,222 - TestParty - INFO - party is initialized
+2021-09-13 13:28:28,222 - TestParty - INFO - party is initialized
+2021-09-13 13:28:55,995 - TestParty - INFO - party is initialized
+2021-09-13 13:28:55,995 - TestParty - INFO - party is initialized
+2021-09-13 13:28:55,995 - TestParty - INFO - party is initialized
+2021-09-13 13:28:56,102 - TestParty - INFO - party is initialized
+2021-09-13 13:28:56,102 - TestParty - INFO - party is initialized
+2021-09-13 13:28:56,102 - TestParty - INFO - party is initialized
+2021-09-13 13:28:56,102 - TestParty - INFO - party is initialized
+2021-09-13 13:28:56,215 - TestParty - INFO - party is initialized
+2021-09-13 13:28:56,215 - TestParty - INFO - party is initialized
+2021-09-13 13:28:56,215 - TestParty - INFO - party is initialized
+2021-09-13 13:28:56,215 - TestParty - INFO - party is initialized
+2021-09-13 13:28:56,215 - TestParty - INFO - party is initialized
+2021-09-13 13:30:21,660 - TestParty - INFO - party is initialized
+2021-09-13 13:30:26,075 - TestParty - INFO - party is initialized
+2021-09-13 13:30:26,075 - TestParty - INFO - party is initialized
+2021-09-13 13:30:26,186 - TestParty - INFO - party is initialized
+2021-09-13 13:30:26,186 - TestParty - INFO - party is initialized
+2021-09-13 13:30:26,186 - TestParty - INFO - party is initialized
+2021-09-13 13:30:26,296 - TestParty - INFO - party is initialized
+2021-09-13 13:30:26,296 - TestParty - INFO - party is initialized
+2021-09-13 13:30:26,296 - TestParty - INFO - party is initialized
+2021-09-13 13:30:26,296 - TestParty - INFO - party is initialized
+2021-09-13 13:30:26,407 - TestParty - INFO - party is initialized
+2021-09-13 13:30:26,407 - TestParty - INFO - party is initialized
+2021-09-13 13:30:26,407 - TestParty - INFO - party is initialized
+2021-09-13 13:30:26,407 - TestParty - INFO - party is initialized
+2021-09-13 13:30:26,407 - TestParty - INFO - party is initialized
+2021-09-13 13:33:09,917 - TestParty - INFO - party is initialized
+2021-09-13 13:33:10,023 - TestParty - INFO - party is initialized
+2021-09-13 13:33:10,023 - TestParty - INFO - party is initialized
+2021-09-13 13:33:10,134 - TestParty - INFO - party is initialized
+2021-09-13 13:33:10,134 - TestParty - INFO - party is initialized
+2021-09-13 13:33:10,134 - TestParty - INFO - party is initialized
+2021-09-13 13:33:10,240 - TestParty - INFO - party is initialized
+2021-09-13 13:33:10,240 - TestParty - INFO - party is initialized
+2021-09-13 13:33:10,240 - TestParty - INFO - party is initialized
+2021-09-13 13:33:10,240 - TestParty - INFO - party is initialized
+2021-09-13 13:33:10,347 - TestParty - INFO - party is initialized
+2021-09-13 13:33:10,347 - TestParty - INFO - party is initialized
+2021-09-13 13:33:10,347 - TestParty - INFO - party is initialized
+2021-09-13 13:33:10,347 - TestParty - INFO - party is initialized
+2021-09-13 13:33:10,347 - TestParty - INFO - party is initialized
+2021-09-13 13:35:13,799 - TestParty - INFO - party is initialized
+2021-09-13 13:35:13,909 - TestParty - INFO - party is initialized
+2021-09-13 13:35:13,909 - TestParty - INFO - party is initialized
+2021-09-13 13:35:14,016 - TestParty - INFO - party is initialized
+2021-09-13 13:35:14,016 - TestParty - INFO - party is initialized
+2021-09-13 13:35:14,016 - TestParty - INFO - party is initialized
+2021-09-13 13:35:14,126 - TestParty - INFO - party is initialized
+2021-09-13 13:35:14,126 - TestParty - INFO - party is initialized
+2021-09-13 13:35:14,126 - TestParty - INFO - party is initialized
+2021-09-13 13:35:14,126 - TestParty - INFO - party is initialized
+2021-09-13 13:35:14,233 - TestParty - INFO - party is initialized
+2021-09-13 13:35:14,233 - TestParty - INFO - party is initialized
+2021-09-13 13:35:14,233 - TestParty - INFO - party is initialized
+2021-09-13 13:35:14,233 - TestParty - INFO - party is initialized
+2021-09-13 13:35:14,233 - TestParty - INFO - party is initialized
+2021-09-13 13:36:50,093 - TestParty - INFO - party is initialized
+2021-09-13 13:36:50,200 - TestParty - INFO - party is initialized
+2021-09-13 13:36:50,200 - TestParty - INFO - party is initialized
+2021-09-13 13:36:50,306 - TestParty - INFO - party is initialized
+2021-09-13 13:36:50,306 - TestParty - INFO - party is initialized
+2021-09-13 13:36:50,306 - TestParty - INFO - party is initialized
+2021-09-13 13:36:50,412 - TestParty - INFO - party is initialized
+2021-09-13 13:36:50,412 - TestParty - INFO - party is initialized
+2021-09-13 13:36:50,412 - TestParty - INFO - party is initialized
+2021-09-13 13:36:50,412 - TestParty - INFO - party is initialized
+2021-09-13 13:36:50,519 - TestParty - INFO - party is initialized
+2021-09-13 13:36:50,519 - TestParty - INFO - party is initialized
+2021-09-13 13:36:50,519 - TestParty - INFO - party is initialized
+2021-09-13 13:36:50,519 - TestParty - INFO - party is initialized
+2021-09-13 13:36:50,519 - TestParty - INFO - party is initialized
+2021-09-13 13:39:13,661 - TestParty - INFO - party is initialized
+2021-09-13 13:39:13,763 - TestParty - INFO - party is initialized
+2021-09-13 13:39:13,763 - TestParty - INFO - party is initialized
+2021-09-13 13:39:13,869 - TestParty - INFO - party is initialized
+2021-09-13 13:39:13,869 - TestParty - INFO - party is initialized
+2021-09-13 13:39:13,869 - TestParty - INFO - party is initialized
+2021-09-13 13:39:13,980 - TestParty - INFO - party is initialized
+2021-09-13 13:39:13,980 - TestParty - INFO - party is initialized
+2021-09-13 13:39:13,980 - TestParty - INFO - party is initialized
+2021-09-13 13:39:13,980 - TestParty - INFO - party is initialized
+2021-09-13 13:39:14,089 - TestParty - INFO - party is initialized
+2021-09-13 13:39:14,089 - TestParty - INFO - party is initialized
+2021-09-13 13:39:14,089 - TestParty - INFO - party is initialized
+2021-09-13 13:39:14,089 - TestParty - INFO - party is initialized
+2021-09-13 13:39:14,089 - TestParty - INFO - party is initialized
+2021-09-13 13:42:28,627 - TestParty - INFO - party is initialized
+2021-09-13 13:42:28,730 - TestParty - INFO - party is initialized
+2021-09-13 13:42:28,730 - TestParty - INFO - party is initialized
+2021-09-13 13:42:28,838 - TestParty - INFO - party is initialized
+2021-09-13 13:42:28,838 - TestParty - INFO - party is initialized
+2021-09-13 13:42:28,838 - TestParty - INFO - party is initialized
+2021-09-13 13:42:28,947 - TestParty - INFO - party is initialized
+2021-09-13 13:42:28,947 - TestParty - INFO - party is initialized
+2021-09-13 13:42:28,947 - TestParty - INFO - party is initialized
+2021-09-13 13:42:28,947 - TestParty - INFO - party is initialized
+2021-09-13 13:42:29,056 - TestParty - INFO - party is initialized
+2021-09-13 13:42:29,056 - TestParty - INFO - party is initialized
+2021-09-13 13:42:29,056 - TestParty - INFO - party is initialized
+2021-09-13 13:42:29,056 - TestParty - INFO - party is initialized
+2021-09-13 13:42:29,056 - TestParty - INFO - party is initialized
+2021-09-13 13:43:32,722 - TestParty - INFO - party is initialized
+2021-09-13 13:43:32,829 - TestParty - INFO - party is initialized
+2021-09-13 13:43:32,829 - TestParty - INFO - party is initialized
+2021-09-13 13:43:32,937 - TestParty - INFO - party is initialized
+2021-09-13 13:43:32,937 - TestParty - INFO - party is initialized
+2021-09-13 13:43:32,937 - TestParty - INFO - party is initialized
+2021-09-13 13:43:33,043 - TestParty - INFO - party is initialized
+2021-09-13 13:43:33,043 - TestParty - INFO - party is initialized
+2021-09-13 13:43:33,043 - TestParty - INFO - party is initialized
+2021-09-13 13:43:33,043 - TestParty - INFO - party is initialized
+2021-09-13 13:43:33,151 - TestParty - INFO - party is initialized
+2021-09-13 13:43:33,151 - TestParty - INFO - party is initialized
+2021-09-13 13:43:33,151 - TestParty - INFO - party is initialized
+2021-09-13 13:43:33,151 - TestParty - INFO - party is initialized
+2021-09-13 13:43:33,151 - TestParty - INFO - party is initialized
+2021-09-13 13:44:18,297 - TestParty - INFO - party is initialized
+2021-09-13 13:44:18,402 - TestParty - INFO - party is initialized
+2021-09-13 13:44:18,402 - TestParty - INFO - party is initialized
+2021-09-13 13:44:18,509 - TestParty - INFO - party is initialized
+2021-09-13 13:44:18,509 - TestParty - INFO - party is initialized
+2021-09-13 13:44:18,509 - TestParty - INFO - party is initialized
+2021-09-13 13:44:18,617 - TestParty - INFO - party is initialized
+2021-09-13 13:44:18,617 - TestParty - INFO - party is initialized
+2021-09-13 13:44:18,617 - TestParty - INFO - party is initialized
+2021-09-13 13:44:18,617 - TestParty - INFO - party is initialized
+2021-09-13 13:44:18,727 - TestParty - INFO - party is initialized
+2021-09-13 13:44:18,727 - TestParty - INFO - party is initialized
+2021-09-13 13:44:18,727 - TestParty - INFO - party is initialized
+2021-09-13 13:44:18,727 - TestParty - INFO - party is initialized
+2021-09-13 13:44:18,727 - TestParty - INFO - party is initialized
+2021-09-13 13:44:46,076 - TestParty - INFO - party is initialized
+2021-09-13 13:44:46,182 - TestParty - INFO - party is initialized
+2021-09-13 13:44:46,182 - TestParty - INFO - party is initialized
+2021-09-13 13:44:46,289 - TestParty - INFO - party is initialized
+2021-09-13 13:44:46,289 - TestParty - INFO - party is initialized
+2021-09-13 13:44:46,289 - TestParty - INFO - party is initialized
+2021-09-13 13:44:46,397 - TestParty - INFO - party is initialized
+2021-09-13 13:44:46,397 - TestParty - INFO - party is initialized
+2021-09-13 13:44:46,397 - TestParty - INFO - party is initialized
+2021-09-13 13:44:46,397 - TestParty - INFO - party is initialized
+2021-09-13 13:44:46,504 - TestParty - INFO - party is initialized
+2021-09-13 13:44:46,504 - TestParty - INFO - party is initialized
+2021-09-13 13:44:46,504 - TestParty - INFO - party is initialized
+2021-09-13 13:44:46,504 - TestParty - INFO - party is initialized
+2021-09-13 13:44:46,504 - TestParty - INFO - party is initialized
Index: geniuswebcore/test/geniusweb/utilsTest.py
===================================================================
--- geniuswebcore/test/geniusweb/utilsTest.py	(revision 67)
+++ geniuswebcore/test/geniusweb/utilsTest.py	(revision 67)
@@ -0,0 +1,11 @@
+import unittest
+from geniusweb.utils import val
+
+
+class utilsTest (unittest.TestCase):
+    def testVal(self):
+        a:Optional[int]=None
+        self.assertRaises(ValueError, lambda:val(a))
+        a=1
+        self.assertEqual(1, val(a))
+        
