Beispiel #1
0
    def update_email(api_key: str, email: str, response: Response):
        email_obj = ndb.Key("UserEmail", api_key).get()
        if email_obj is None:
            email_obj = UserEmail(id=api_key, email=email)
            response.status_code = status.HTTP_201_CREATED
        else:
            email_obj.email = email
            response.status_code = status.HTTP_200_OK

        email_obj.put()
Beispiel #2
0
    def testUpdate(self):
        self.user_a.upate('*****@*****.**', 'Bar Foy')
        self.assertEqual('Bar Foy', self.user_a.name)
        self.assertTrue(self.user_a.has_email())
        self.assertEqual(self.user_a, UserEmail.find_user('*****@*****.**'))

        self.user_b.upate('*****@*****.**', 'Foo Bar')
        self.assertEqual('Foo Bar', self.user_b.name)
        self.assertEqual('*****@*****.**', self.user_b.get_default_email())
        self.assertEqual(self.user_b, UserEmail.find_user('*****@*****.**'))
Beispiel #3
0
def send(request):
    email = request.POST.get('mail', None)
    if email:
        user_emails = UserEmail.objects.filter(email=email)
        if user_emails.count() == 0:
            user_email = UserEmail(user=request.user, email=email)
            user_email.save()
    try:
        send_mail(
            'Letter',
            'msg',
            '*****@*****.**',
            [request.POST.get('mail', '')],
            fail_silently=False,
            html_message=request.POST.get('html')
        )
        return HttpResponse('200')
    except:
        return HttpResponse('500')
Beispiel #4
0
    def setUp(self):
        self.testbed = testbed.Testbed()
        self.testbed.activate()
        self.testbed.init_datastore_v3_stub()
        self.testbed.init_memcache_stub()

        self.user_a = User()
        self.user_a.put()

        self.user_b = User()
        self.user_b.name = 'Foo Bar'
        self.user_b.clientID = 'C123'
        self.user_b.put()

        self.setting = UserSettingKey()
        self.setting.name = 'SEND_EMAIL'
        self.setting.description = 'Acceptance to receiv E-mail'
        self.setting.values = ['0', '1']
        self.setting.put()

        self.user_setting = UserSetting()
        self.user_setting.setting_key = self.setting.key
        self.user_setting.user = self.user_b.key
        self.user_setting.value = '0'
        self.user_setting.put()

        self.email_a = UserEmail()
        self.email_a.email = '*****@*****.**'
        self.email_a.is_activated = False
        self.email_a.is_default = False
        self.email_a.user = self.user_b.key
        self.email_a.put()

        self.email_b = UserEmail()
        self.email_b.email = '*****@*****.**'
        self.email_b.is_activated = True
        self.email_b.is_default = True
        self.email_b.user = self.user_b.key
        self.email_b.put()
Beispiel #5
0
    def testDefaultEmailConsistancy(self):
        self.assertEqual(None, self.user_a.get_default_email())
        self.assertEqual('*****@*****.**', self.user_b.get_default_email())

        UserEmail.create_or_update(self.user_a, '*****@*****.**', True)
        UserEmail.create_or_update(self.user_b, '*****@*****.**', True)

        user_a_emails = UserEmail.gql("WHERE user = :1",
                                      self.user_a.key).fetch()
        user_b_emails = UserEmail.gql("WHERE user = :1",
                                      self.user_b.key).fetch()

        self.assertEqual(1, len(user_a_emails))
        self.assertTrue(user_a_emails[0].is_default)

        self.assertEqual(3, len(user_b_emails))
        for email in user_b_emails:
            if '*****@*****.**' == email.email:
                self.assertTrue(email.is_default)
            else:
                self.assertFalse(email.is_default)
Beispiel #6
0
  def match(self, session, order, order_matcher_disabled=False):
    other_side = []
    self_side = []
    if order.is_buy:
      self_side = self.buy_side
      other_side = self.sell_side
    elif order.is_sell:
      other_side = self.buy_side
      self_side = self.sell_side


    execution_reports = []
    trades_to_publish = []

    execution_side = '1' if order.is_buy else '2'

    rpt_order  = ExecutionReport( order, execution_side )
    execution_reports.append( ( order.user_id, rpt_order.toJson() )  )
    if order.user_id != order.account_id:
      execution_reports.append( ( order.account_id, rpt_order.toJson() )  )

    is_last_match_a_partial_execution_on_counter_order = False
    execution_counter = 0
    number_of_filled_counter_market_orders = 0

    if not order_matcher_disabled:
      for execution_counter in xrange(0, len(other_side) + 1):
        if execution_counter == len(other_side):
          break # workaround to make the execution_counter be counted until the last order.

        if not order.leaves_qty > 0:
          break

        counter_order = other_side[execution_counter]

        if not order.has_match(counter_order):
          break

        # check for self execution
        if order.account_id == counter_order.account_id:
          # self execution.... let's cancel the counter order
          counter_order.cancel_qty( counter_order.leaves_qty )

          # generate a cancel report
          cancel_rpt_counter_order  = ExecutionReport( counter_order, execution_side )
          execution_reports.append( ( counter_order.user_id, cancel_rpt_counter_order.toJson() )  )
          if counter_order.user_id != counter_order.account_id:
            execution_reports.append( ( counter_order.account_id, cancel_rpt_counter_order.toJson() )  )

          # go to the next order
          is_last_match_a_partial_execution_on_counter_order = False
          continue

        # Get the desired executed price and qty, by matching against the counter_order
        executed_qty = order.match( counter_order, order.leaves_qty)

        if counter_order.type == '1': # Market Order
          executed_price = order.price
          number_of_filled_counter_market_orders += 1
        else:
          executed_price = counter_order.price

        # let's get the available qty to execute on the order side
        available_qty_on_order_side = order.get_available_qty_to_execute(session,
                                                                         '1' if order.is_buy else '2',
                                                                         executed_qty,
                                                                         executed_price )

        qty_to_cancel_from_order = 0
        if available_qty_on_order_side <  executed_qty:
          # ops ... looks like the order.user didn't have enough to execute the order
          executed_qty = available_qty_on_order_side

          # cancel the remaining  qty
          qty_to_cancel_from_order = order.leaves_qty - executed_qty


        # check if the order got fully cancelled
        if not executed_qty:
          order.cancel_qty( qty_to_cancel_from_order )
          cancel_rpt_order  = ExecutionReport( order, execution_side )
          execution_reports.append( ( order.user_id, cancel_rpt_order.toJson() )  )
          if order.user_id != order.account_id:
            execution_reports.append( ( order.account_id, cancel_rpt_order.toJson() )  )
          break


        # let's get the available qty to execute on the counter side
        available_qty_on_counter_side = counter_order.get_available_qty_to_execute(session,
                                                                                   '1' if counter_order.is_buy else '2',
                                                                                   executed_qty,
                                                                                   executed_price )

        qty_to_cancel_from_counter_order = 0
        if available_qty_on_counter_side <  executed_qty:
          if qty_to_cancel_from_order:
            qty_to_cancel_from_order -= executed_qty - available_qty_on_order_side

            # ops ... looks like the counter_order.user didn't have enough to execute the order
          executed_qty = available_qty_on_counter_side

          # cancel the remaining  qty
          qty_to_cancel_from_counter_order = counter_order.leaves_qty - executed_qty


        # check if the counter order was fully cancelled due the lack
        if not executed_qty:
          # just cancel the counter order, and go to the next order.
          counter_order.cancel_qty( qty_to_cancel_from_counter_order )

          # generate a cancel report
          cancel_rpt_counter_order  = ExecutionReport( counter_order, execution_side )
          execution_reports.append( ( counter_order.user_id, cancel_rpt_counter_order.toJson() )  )
          if counter_order.user_id != counter_order.account_id:
            execution_reports.append( ( counter_order.account_id, cancel_rpt_counter_order.toJson() )  )

          # go to the next order
          is_last_match_a_partial_execution_on_counter_order = False
          continue

        # lets perform the execution
        if executed_qty:
          order.execute( executed_qty, executed_price )
          counter_order.execute(executed_qty, executed_price )

          trade = Trade.create(session, order, counter_order, self.symbol, executed_qty, executed_price )
          trades_to_publish.append(trade)

          rpt_order         = ExecutionReport( order, execution_side )
          execution_reports.append( ( order.user_id, rpt_order.toJson() )  )
          if order.user_id != order.account_id:
            execution_reports.append( ( order.account_id, rpt_order.toJson() )  )

          rpt_counter_order = ExecutionReport( counter_order, execution_side )
          execution_reports.append( ( counter_order.user_id, rpt_counter_order.toJson() )  )
          if counter_order.user_id != counter_order.account_id:
            execution_reports.append( ( counter_order.account_id, rpt_counter_order.toJson() )  )

          def generate_email_subject_and_body( session, order, trade ):
            from json import  dumps
            from pyblinktrade.json_encoder import  JsonEncoder
            from models import Currency

            qty_currency = order.symbol[:3]
            formatted_qty = Currency.format_number( session, qty_currency, trade.size / 1.e8 )


            price_currency = order.symbol[3:]
            formatted_price = Currency.format_number( session, price_currency, trade.price / 1.e8 )

            formatted_total_price = Currency.format_number( session, price_currency, trade.size/1.e8 * trade.price/1.e8 )

            email_subject =  'E'
            email_template = "order-execution"
            email_params = {
              'username': order.user.username,
              'order_id': order.id,
              'trade_id': trade.id,
              'side': order.side,
              'executed_when': trade.created,
              'qty': formatted_qty,
              'price': formatted_price,
              'total': formatted_total_price
            }
            return  email_subject, email_template, dumps(email_params, cls=JsonEncoder)

          email_data = generate_email_subject_and_body(session, order, trade)
          UserEmail.create( session = session,
                            user_id = order.account_id,
                            broker_id = order.broker_id,
                            subject = email_data[0],
                            template= email_data[1],
                            language= order.email_lang,
                            params  = email_data[2])

          email_data = generate_email_subject_and_body(session, counter_order, trade)
          UserEmail.create( session = session,
                            user_id = counter_order.account_id,
                            broker_id = counter_order.broker_id,
                            subject = email_data[0],
                            template= email_data[1],
                            language= counter_order.email_lang,
                            params  = email_data[2])


        #
        # let's do the partial cancels
        #

        # Cancel the qty from the current order
        if qty_to_cancel_from_order:
          order.cancel_qty(qty_to_cancel_from_order)

          # generate a cancel report
          cancel_rpt_order  = ExecutionReport( order, execution_side )
          execution_reports.append( ( order.user_id, cancel_rpt_order.toJson() )  )

          if order.user_id != order.account_id:
            execution_reports.append( ( order.account_id, cancel_rpt_order.toJson() )  )


        if qty_to_cancel_from_counter_order:
          counter_order.cancel_qty(qty_to_cancel_from_counter_order)

          # generate a cancel report
          cancel_rpt_counter_order  = ExecutionReport( counter_order, execution_side )
          execution_reports.append( ( counter_order.user_id, cancel_rpt_counter_order.toJson() )  )
          if counter_order.user_id != counter_order.account_id:
            execution_reports.append( ( counter_order.account_id, cancel_rpt_counter_order.toJson() )  )

        if counter_order.leaves_qty > 0:
          is_last_match_a_partial_execution_on_counter_order = True


    md_entry_type = '0' if order.is_buy else '1'
    counter_md_entry_type = '1' if order.is_buy else '0'

    # let's include the order in the book if the order is not fully executed.
    if order.leaves_qty > 0:
      insert_pos = bisect.bisect_right(self_side, order)
      self_side.insert( insert_pos, order )

      if order.type == '2': # Limited orders go to the book.
        MarketDataPublisher.publish_new_order( self.symbol, md_entry_type , insert_pos, order)

    # don't send the first execution report (NEW) if the order was fully cancelled
    if order.is_cancelled and order.cum_qty == 0:
      execution_reports.pop(0)

    # Publish all execution reports
    for user_id, execution_report in execution_reports:
      TradeApplication.instance().publish( user_id, execution_report )

    # Publish Market Data for the counter order
    if execution_counter:
      if is_last_match_a_partial_execution_on_counter_order:
        del other_side[0: execution_counter-1]
        MarketDataPublisher.publish_executions( self.symbol,
                                                 counter_md_entry_type,
                                                 execution_counter - 1 - number_of_filled_counter_market_orders,
                                                 other_side[0] )
      else:
        del other_side[0: execution_counter]
        MarketDataPublisher.publish_executions( self.symbol,
                                                 counter_md_entry_type,
                                                 execution_counter - number_of_filled_counter_market_orders )

    if trades_to_publish:
      MarketDataPublisher.publish_trades(self.symbol, trades_to_publish)
    return ""
Beispiel #7
0
  def match(self, session, order, order_matcher_disabled=False):
    other_side = []
    self_side = []
    if order.is_buy:
      self_side = self.buy_side
      other_side = self.sell_side
    elif order.is_sell:
      other_side = self.buy_side
      self_side = self.sell_side


    execution_reports = []
    trades_to_publish = []

    execution_side = '1' if order.is_buy else '2'

    rpt_order  = ExecutionReport( order, execution_side )
    execution_reports.append( ( order.user_id, rpt_order.toJson() )  )
    if order.user_id != order.account_id:
      execution_reports.append( ( order.account_id, rpt_order.toJson() )  )

    is_last_match_a_partial_execution_on_counter_order = False
    execution_counter = 0
    number_of_filled_counter_market_orders = 0

    if not order_matcher_disabled:
      for execution_counter in xrange(0, len(other_side) + 1):
        if execution_counter == len(other_side):
          break # workaround to make the execution_counter be counted until the last order.

        if not order.leaves_qty > 0:
          break

        counter_order = other_side[execution_counter]

        if not order.has_match(counter_order):
          break

        # check for self execution
        if order.account_id == counter_order.account_id:
          # self execution.... let's cancel the counter order
          counter_order.cancel_qty( counter_order.leaves_qty )

          # generate a cancel report
          cancel_rpt_counter_order  = ExecutionReport( counter_order, execution_side )
          execution_reports.append( ( counter_order.user_id, cancel_rpt_counter_order.toJson() )  )
          if counter_order.user_id != counter_order.account_id:
            execution_reports.append( ( counter_order.account_id, cancel_rpt_counter_order.toJson() )  )

          # go to the next order
          is_last_match_a_partial_execution_on_counter_order = False
          continue

        # Get the desired executed price and qty, by matching against the counter_order
        executed_qty = order.match( counter_order, order.leaves_qty)

        if counter_order.type == '1': # Market Order
          executed_price = order.price
          number_of_filled_counter_market_orders += 1
        else:
          executed_price = counter_order.price

        # let's get the available qty to execute on the order side
        available_qty_on_order_side = order.get_available_qty_to_execute(session,
                                                                         '1' if order.is_buy else '2',
                                                                         executed_qty,
                                                                         executed_price )

        qty_to_cancel_from_order = 0
        if available_qty_on_order_side <  executed_qty:
          # ops ... looks like the order.user didn't have enough to execute the order
          executed_qty = available_qty_on_order_side

          # cancel the remaining  qty
          qty_to_cancel_from_order = order.leaves_qty - executed_qty


        # check if the order got fully cancelled
        if not executed_qty:
          order.cancel_qty( qty_to_cancel_from_order )
          cancel_rpt_order  = ExecutionReport( order, execution_side )
          execution_reports.append( ( order.user_id, cancel_rpt_order.toJson() )  )
          if order.user_id != order.account_id:
            execution_reports.append( ( order.account_id, cancel_rpt_order.toJson() )  )
          break


        # let's get the available qty to execute on the counter side
        available_qty_on_counter_side = counter_order.get_available_qty_to_execute(session,
                                                                                   '1' if counter_order.is_buy else '2',
                                                                                   executed_qty,
                                                                                   executed_price )

        qty_to_cancel_from_counter_order = 0
        if available_qty_on_counter_side <  executed_qty:
          if qty_to_cancel_from_order:
            qty_to_cancel_from_order -= executed_qty - available_qty_on_order_side

            # ops ... looks like the counter_order.user didn't have enough to execute the order
          executed_qty = available_qty_on_counter_side

          # cancel the remaining  qty
          qty_to_cancel_from_counter_order = counter_order.leaves_qty - executed_qty


        # check if the counter order was fully cancelled due the lack
        if not executed_qty:
          # just cancel the counter order, and go to the next order.
          counter_order.cancel_qty( qty_to_cancel_from_counter_order )

          # generate a cancel report
          cancel_rpt_counter_order  = ExecutionReport( counter_order, execution_side )
          execution_reports.append( ( counter_order.user_id, cancel_rpt_counter_order.toJson() )  )
          if counter_order.user_id != counter_order.account_id:
            execution_reports.append( ( counter_order.account_id, cancel_rpt_counter_order.toJson() )  )

          # go to the next order
          is_last_match_a_partial_execution_on_counter_order = False
          continue

        # lets perform the execution
        if executed_qty:
          order.execute( executed_qty, executed_price )
          counter_order.execute(executed_qty, executed_price )

          trade = Trade.create(session, order, counter_order, self.symbol, executed_qty, executed_price )
          trades_to_publish.append(trade)

          rpt_order         = ExecutionReport( order, execution_side )
          execution_reports.append( ( order.user_id, rpt_order.toJson() )  )
          if order.user_id != order.account_id:
            execution_reports.append( ( order.account_id, rpt_order.toJson() )  )

          rpt_counter_order = ExecutionReport( counter_order, execution_side )
          execution_reports.append( ( counter_order.user_id, rpt_counter_order.toJson() )  )
          if counter_order.user_id != counter_order.account_id:
            execution_reports.append( ( counter_order.account_id, rpt_counter_order.toJson() )  )

          def generate_email_subject_and_body( session, order, trade ):
            from json import  dumps
            from pyblinktrade.json_encoder import  JsonEncoder
            from models import Currency

            qty_currency = order.symbol[:3]
            formatted_qty = Currency.format_number( session, qty_currency, trade.size / 1.e8 )


            price_currency = order.symbol[3:]
            formatted_price = Currency.format_number( session, price_currency, trade.price / 1.e8 )

            formatted_total_price = Currency.format_number( session, price_currency, trade.size/1.e8 * trade.price/1.e8 )

            email_subject =  'E'
            email_template = "order-execution"
            email_params = {
              'username': order.user.username,
              'order_id': order.id,
              'trade_id': trade.id,
              'side': order.side,
              'executed_when': trade.created,
              'qty': formatted_qty,
              'price': formatted_price,
              'total': formatted_total_price
            }
            return  email_subject, email_template, dumps(email_params, cls=JsonEncoder)

          email_data = generate_email_subject_and_body(session, counter_order, trade)
          UserEmail.create( session = session,
                            user_id = counter_order.account_id,
                            broker_id = counter_order.broker_id,
                            subject = email_data[0],
                            template= email_data[1],
                            language= counter_order.email_lang,
                            params  = email_data[2])


        #
        # let's do the partial cancels
        #

        # Cancel the qty from the current order
        if qty_to_cancel_from_order:
          order.cancel_qty(qty_to_cancel_from_order)

          # generate a cancel report
          cancel_rpt_order  = ExecutionReport( order, execution_side )
          execution_reports.append( ( order.user_id, cancel_rpt_order.toJson() )  )

          if order.user_id != order.account_id:
            execution_reports.append( ( order.account_id, cancel_rpt_order.toJson() )  )


        if qty_to_cancel_from_counter_order:
          counter_order.cancel_qty(qty_to_cancel_from_counter_order)

          # generate a cancel report
          cancel_rpt_counter_order  = ExecutionReport( counter_order, execution_side )
          execution_reports.append( ( counter_order.user_id, cancel_rpt_counter_order.toJson() )  )
          if counter_order.user_id != counter_order.account_id:
            execution_reports.append( ( counter_order.account_id, cancel_rpt_counter_order.toJson() )  )

        if counter_order.leaves_qty > 0:
          is_last_match_a_partial_execution_on_counter_order = True


    md_entry_type = '0' if order.is_buy else '1'
    counter_md_entry_type = '1' if order.is_buy else '0'

    # let's include the order in the book if the order is not fully executed.
    if order.leaves_qty > 0:
      insert_pos = bisect.bisect_right(self_side, order)
      self_side.insert( insert_pos, order )

      if order.type == '2': # Limited orders go to the book.
        MarketDataPublisher.publish_new_order( self.symbol, md_entry_type , insert_pos, order)

    # don't send the first execution report (NEW) if the order was fully cancelled
    if order.is_cancelled and order.cum_qty == 0:
      execution_reports.pop(0)

    # Publish all execution reports
    for user_id, execution_report in execution_reports:
      TradeApplication.instance().publish( user_id, execution_report )

    # Publish Market Data for the counter order
    if execution_counter:
      if is_last_match_a_partial_execution_on_counter_order:
        del other_side[0: execution_counter-1]
        MarketDataPublisher.publish_executions( self.symbol,
                                                 counter_md_entry_type,
                                                 execution_counter - 1 - number_of_filled_counter_market_orders,
                                                 other_side[0] )
      else:
        del other_side[0: execution_counter]
        MarketDataPublisher.publish_executions( self.symbol,
                                                 counter_md_entry_type,
                                                 execution_counter - number_of_filled_counter_market_orders )

    if trades_to_publish:
      MarketDataPublisher.publish_trades(self.symbol, trades_to_publish)
    return ""
Beispiel #8
0
 def get(self):
     return UserEmail(id="123abc", email="*****@*****.**")
Beispiel #9
0
class Test(unittest.TestCase):
    def setUp(self):
        self.testbed = testbed.Testbed()
        self.testbed.activate()
        self.testbed.init_datastore_v3_stub()
        self.testbed.init_memcache_stub()

        self.user_a = User()
        self.user_a.put()

        self.user_b = User()
        self.user_b.name = 'Foo Bar'
        self.user_b.clientID = 'C123'
        self.user_b.put()

        self.setting = UserSettingKey()
        self.setting.name = 'SEND_EMAIL'
        self.setting.description = 'Acceptance to receiv E-mail'
        self.setting.values = ['0', '1']
        self.setting.put()

        self.user_setting = UserSetting()
        self.user_setting.setting_key = self.setting.key
        self.user_setting.user = self.user_b.key
        self.user_setting.value = '0'
        self.user_setting.put()

        self.email_a = UserEmail()
        self.email_a.email = '*****@*****.**'
        self.email_a.is_activated = False
        self.email_a.is_default = False
        self.email_a.user = self.user_b.key
        self.email_a.put()

        self.email_b = UserEmail()
        self.email_b.email = '*****@*****.**'
        self.email_b.is_activated = True
        self.email_b.is_default = True
        self.email_b.user = self.user_b.key
        self.email_b.put()

    def tearDown(self):
        self.testbed.deactivate()

    def testGetSettings(self):
        self.assertEqual(0, len(self.user_a.get_settings()))

        settings = self.user_b.get_settings()
        self.assertEqual(1, len(settings))

        self.assertEqual('SEND_EMAIL', settings[0].setting_key.get().name)

    def testUpdateSettings(self):
        self.user_a.update_setting(self.setting, '1')
        self.assertEqual('0', self.user_setting.value)

        self.user_b.update_setting(self.setting, '1')
        self.assertEqual('1', self.user_setting.value)

    def testUpdate(self):
        self.user_a.upate('*****@*****.**', 'Bar Foy')
        self.assertEqual('Bar Foy', self.user_a.name)
        self.assertTrue(self.user_a.has_email())
        self.assertEqual(self.user_a, UserEmail.find_user('*****@*****.**'))

        self.user_b.upate('*****@*****.**', 'Foo Bar')
        self.assertEqual('Foo Bar', self.user_b.name)
        self.assertEqual('*****@*****.**', self.user_b.get_default_email())
        self.assertEqual(self.user_b, UserEmail.find_user('*****@*****.**'))

    def testHasEmail(self):
        self.assertFalse(self.user_a.has_email())
        self.assertTrue(self.user_b.has_email())

    def testDefaultEmailConsistancy(self):
        self.assertEqual(None, self.user_a.get_default_email())
        self.assertEqual('*****@*****.**', self.user_b.get_default_email())

        UserEmail.create_or_update(self.user_a, '*****@*****.**', True)
        UserEmail.create_or_update(self.user_b, '*****@*****.**', True)

        user_a_emails = UserEmail.gql("WHERE user = :1",
                                      self.user_a.key).fetch()
        user_b_emails = UserEmail.gql("WHERE user = :1",
                                      self.user_b.key).fetch()

        self.assertEqual(1, len(user_a_emails))
        self.assertTrue(user_a_emails[0].is_default)

        self.assertEqual(3, len(user_b_emails))
        for email in user_b_emails:
            if '*****@*****.**' == email.email:
                self.assertTrue(email.is_default)
            else:
                self.assertFalse(email.is_default)

    def testFindByEmail(self):
        user = User.find_by_email('*****@*****.**')
        self.assertEqual(self.user_b, user)

        user = User.find_by_email('*****@*****.**')
        self.assertEqual(self.user_b, user)

        user = User.find_by_email('*****@*****.**')
        self.assertEqual(None, user)

    def testClinet(self):
        self.assertEqual(self.user_b, User.client('C123'))

        user = User.client('C001')
        self.assertNotEqual(None, user)
        self.assertEqual('C001', user.clientID)