From 92a7f8c13fa6bc84a66f973f9454971d346fd801 Mon Sep 17 00:00:00 2001
From: qdp-odoo <qdp@odoo.com>
Date: Wed, 8 Jul 2020 12:36:17 +0000
Subject: [PATCH] [IMP] base, tests: use freezegun lib to mock dates and
 datetimes

closes odoo/odoo#54227

Related: odoo/enterprise#11742
Signed-off-by: Olivier Dony (odo) <odo@openerp.com>
---
 addons/account/tests/common.py                |  28 ---
 .../tests/test_account_journal_dashboard.py   | 172 +++++++++---------
 .../test_reconciliation_matching_rules.py     |   5 +-
 .../account_edi_facturx/tests/test_facturx.py | 122 +++++++------
 debian/control                                |   1 +
 requirements.txt                              |   1 +
 setup.cfg                                     |   1 +
 setup.py                                      |   2 +-
 8 files changed, 155 insertions(+), 177 deletions(-)

diff --git a/addons/account/tests/common.py b/addons/account/tests/common.py
index 7bf7c391b186..0cebdeb16ee8 100644
--- a/addons/account/tests/common.py
+++ b/addons/account/tests/common.py
@@ -6,10 +6,7 @@ from odoo.tests.common import SavepointCase, HttpCase, tagged, Form
 
 import time
 import logging
-import datetime
 
-from contextlib import contextmanager
-from unittest.mock import patch
 
 _logger = logging.getLogger(__name__)
 
@@ -986,31 +983,6 @@ class AccountTestInvoicingCommon(SavepointCase):
             ],
         })
 
-    @contextmanager
-    def mocked_today(self, forced_today):
-        ''' Helper to make easily a python "with statement" mocking the "today" date.
-        :param forced_today:    The expected "today" date as a str or Date object.
-        :return:                An object to be used like 'with self.mocked_today(<today>):'.
-        '''
-
-        if isinstance(forced_today, str):
-            forced_today_date = fields.Date.from_string(forced_today)
-            forced_today_datetime = fields.Datetime.from_string(forced_today)
-        elif isinstance(forced_today, datetime.datetime):
-            forced_today_datetime = forced_today
-            forced_today_date = forced_today_datetime.date()
-        else:
-            forced_today_date = forced_today
-            forced_today_datetime = datetime.datetime.combine(forced_today_date, datetime.time())
-
-        def today(*args, **kwargs):
-            return forced_today_date
-
-        with patch.object(fields.Date, 'today', today):
-            with patch.object(fields.Date, 'context_today', today):
-                with patch.object(fields.Datetime, 'now', return_value=forced_today_datetime):
-                    yield
-
     @classmethod
     def init_invoice(cls, move_type, partner=None, invoice_date=None):
         move_form = Form(cls.env['account.move'].with_context(default_move_type=move_type))
diff --git a/addons/account/tests/test_account_journal_dashboard.py b/addons/account/tests/test_account_journal_dashboard.py
index 41f7023ff76c..316b8a0ee84a 100644
--- a/addons/account/tests/test_account_journal_dashboard.py
+++ b/addons/account/tests/test_account_journal_dashboard.py
@@ -1,94 +1,94 @@
 # -*- coding: utf-8 -*-
+from freezegun import freeze_time
+
 from odoo.addons.account.tests.common import AccountTestInvoicingCommon
 from odoo.tests import tagged
 
-
 @tagged('post_install', '-at_install')
 class TestAccountJournalDashboard(AccountTestInvoicingCommon):
 
+    @freeze_time("2019-01-22")
     def test_customer_invoice_dashboard(self):
-        with self.mocked_today('2019-01-22'):
-
-            journal = self.company_data['default_journal_sale']
-
-            invoice = self.env['account.move'].create({
-                'move_type': 'out_invoice',
-                'journal_id': journal.id,
-                'partner_id': self.partner_a.id,
-                'invoice_date': '2019-01-21',
-                'date': '2019-01-21',
-                'invoice_line_ids': [(0, 0, {
-                    'product_id': self.product_a.id,
-                    'quantity': 40.0,
-                    'name': 'product test 1',
-                    'discount': 10.00,
-                    'price_unit': 2.27,
-                })]
-            })
-            refund = self.env['account.move'].create({
-                'move_type': 'out_refund',
-                'journal_id': journal.id,
-                'partner_id': self.partner_a.id,
-                'invoice_date': '2019-01-21',
-                'date': '2019-01-21',
-                'invoice_line_ids': [(0, 0, {
-                    'product_id': self.product_a.id,
-                    'quantity': 1.0,
-                    'name': 'product test 1',
-                    'price_unit': 13.3,
-                })]
-            })
-
-            # Check Draft
-            dashboard_data = journal.get_journal_dashboard_datas()
-
-            self.assertEqual(dashboard_data['number_draft'], 2)
-            self.assertIn('68.42', dashboard_data['sum_draft'])
-
-            self.assertEqual(dashboard_data['number_waiting'], 0)
-            self.assertIn('0.00', dashboard_data['sum_waiting'])
-
-            # Check Both
-            invoice.post()
-
-            dashboard_data = journal.get_journal_dashboard_datas()
-            self.assertEqual(dashboard_data['number_draft'], 1)
-            self.assertIn('-13.30', dashboard_data['sum_draft'])
-
-            self.assertEqual(dashboard_data['number_waiting'], 1)
-            self.assertIn('81.72', dashboard_data['sum_waiting'])
-
-            # Check waiting payment
-            refund.post()
-
-            dashboard_data = journal.get_journal_dashboard_datas()
-            self.assertEqual(dashboard_data['number_draft'], 0)
-            self.assertIn('0.00', dashboard_data['sum_draft'])
-
-            self.assertEqual(dashboard_data['number_waiting'], 2)
-            self.assertIn('68.42', dashboard_data['sum_waiting'])
-
-            # Check partial
-            receivable_account = refund.line_ids.mapped('account_id').filtered(lambda a: a.internal_type == 'receivable')
-            payment = self.env['account.payment'].create({
-                'amount': 10.0,
-                'payment_type': 'outbound',
-                'partner_type': 'customer',
-                'partner_id': self.partner_a.id,
-            })
-            payment.action_post()
-
-            (refund + payment.move_id).line_ids\
-                .filtered(lambda line: line.account_internal_type == 'receivable')\
-                .reconcile()
-
-            dashboard_data = journal.get_journal_dashboard_datas()
-            self.assertEqual(dashboard_data['number_draft'], 0)
-            self.assertIn('0.00', dashboard_data['sum_draft'])
-
-            self.assertEqual(dashboard_data['number_waiting'], 2)
-            self.assertIn('78.42', dashboard_data['sum_waiting'])
-
-            dashboard_data = journal.get_journal_dashboard_datas()
-            self.assertEqual(dashboard_data['number_late'], 2)
-            self.assertIn('78.42', dashboard_data['sum_late'])
+        journal = self.company_data['default_journal_sale']
+
+        invoice = self.env['account.move'].create({
+            'move_type': 'out_invoice',
+            'journal_id': journal.id,
+            'partner_id': self.partner_a.id,
+            'invoice_date': '2019-01-21',
+            'date': '2019-01-21',
+            'invoice_line_ids': [(0, 0, {
+                'product_id': self.product_a.id,
+                'quantity': 40.0,
+                'name': 'product test 1',
+                'discount': 10.00,
+                'price_unit': 2.27,
+            })]
+        })
+        refund = self.env['account.move'].create({
+            'move_type': 'out_refund',
+            'journal_id': journal.id,
+            'partner_id': self.partner_a.id,
+            'invoice_date': '2019-01-21',
+            'date': '2019-01-21',
+            'invoice_line_ids': [(0, 0, {
+                'product_id': self.product_a.id,
+                'quantity': 1.0,
+                'name': 'product test 1',
+                'price_unit': 13.3,
+            })]
+        })
+
+        # Check Draft
+        dashboard_data = journal.get_journal_dashboard_datas()
+
+        self.assertEqual(dashboard_data['number_draft'], 2)
+        self.assertIn('68.42', dashboard_data['sum_draft'])
+
+        self.assertEqual(dashboard_data['number_waiting'], 0)
+        self.assertIn('0.00', dashboard_data['sum_waiting'])
+
+        # Check Both
+        invoice.post()
+
+        dashboard_data = journal.get_journal_dashboard_datas()
+        self.assertEqual(dashboard_data['number_draft'], 1)
+        self.assertIn('-13.30', dashboard_data['sum_draft'])
+
+        self.assertEqual(dashboard_data['number_waiting'], 1)
+        self.assertIn('81.72', dashboard_data['sum_waiting'])
+
+        # Check waiting payment
+        refund.post()
+
+        dashboard_data = journal.get_journal_dashboard_datas()
+        self.assertEqual(dashboard_data['number_draft'], 0)
+        self.assertIn('0.00', dashboard_data['sum_draft'])
+
+        self.assertEqual(dashboard_data['number_waiting'], 2)
+        self.assertIn('68.42', dashboard_data['sum_waiting'])
+
+        # Check partial
+        receivable_account = refund.line_ids.mapped('account_id').filtered(lambda a: a.internal_type == 'receivable')
+        payment = self.env['account.payment'].create({
+            'amount': 10.0,
+            'payment_type': 'outbound',
+            'partner_type': 'customer',
+            'partner_id': self.partner_a.id,
+        })
+        payment.action_post()
+
+        (refund + payment.move_id).line_ids\
+            .filtered(lambda line: line.account_internal_type == 'receivable')\
+            .reconcile()
+
+        dashboard_data = journal.get_journal_dashboard_datas()
+        self.assertEqual(dashboard_data['number_draft'], 0)
+        self.assertIn('0.00', dashboard_data['sum_draft'])
+
+        self.assertEqual(dashboard_data['number_waiting'], 2)
+        self.assertIn('78.42', dashboard_data['sum_waiting'])
+
+        dashboard_data = journal.get_journal_dashboard_datas()
+        self.assertEqual(dashboard_data['number_late'], 2)
+        self.assertIn('78.42', dashboard_data['sum_late'])
diff --git a/addons/account/tests/test_reconciliation_matching_rules.py b/addons/account/tests/test_reconciliation_matching_rules.py
index fb3a902c8e9d..bb68dab220b8 100644
--- a/addons/account/tests/test_reconciliation_matching_rules.py
+++ b/addons/account/tests/test_reconciliation_matching_rules.py
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
-from odoo import fields
+from freezegun import freeze_time
+
 from odoo.addons.account.tests.common import AccountTestInvoicingCommon
 from odoo.tests.common import Form
 from odoo.tests import tagged
@@ -625,7 +626,7 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
 
         self.env['account.reconcile.model'].flush()
 
-        with self.mocked_today('2017-01-01'):
+        with freeze_time('2017-01-01'):
             self._check_statement_matching(matching_rule, {
                 statement_line.id: {'aml_ids': (move_line_1 + move_line_2).ids, 'model': matching_rule}
             }, statements=statement)
diff --git a/addons/account_edi_facturx/tests/test_facturx.py b/addons/account_edi_facturx/tests/test_facturx.py
index 98a0f056ef54..faf1e89c38ed 100644
--- a/addons/account_edi_facturx/tests/test_facturx.py
+++ b/addons/account_edi_facturx/tests/test_facturx.py
@@ -1,4 +1,6 @@
 # -*- coding: utf-8 -*-
+from freezegun import freeze_time
+
 from odoo.addons.account.tests.account_test_xml import AccountTestEdiCommon
 from odoo.tests import tagged
 
@@ -151,74 +153,74 @@ class TestAccountEdiFacturx(AccountTestEdiCommon):
             </CrossIndustryInvoice>
         '''
 
+    @freeze_time('2017-02-01')
     def test_facturx(self):
         ''' Test the generated Facturx Edi attachment without any modification of the invoice. '''
 
-        with self.mocked_today('2017-02-01'):
-            self.invoice.post()
+        self.invoice.post()
 
-            xml_content = self.facturx_edi_format._export_invoice_to_attachment(self.invoice)['datas']
-            current_etree = self.get_xml_tree_from_string(xml_content)
-            expected_etree = self.get_xml_tree_from_string(self.expected_invoice_facturx_values)
-            self.assertXmlTreeEqual(current_etree, expected_etree)
+        xml_content = self.facturx_edi_format._export_invoice_to_attachment(self.invoice)['datas']
+        current_etree = self.get_xml_tree_from_string(xml_content)
+        expected_etree = self.get_xml_tree_from_string(self.expected_invoice_facturx_values)
+        self.assertXmlTreeEqual(current_etree, expected_etree)
 
+    @freeze_time('2017-02-01')
     def test_facturx_group_of_taxes(self):
         ''' Same as above with a group of taxes. '''
         self.invoice.write({
             'invoice_line_ids': [(1, self.invoice.invoice_line_ids.id, {'tax_ids': [(6, 0, self.tax_group.ids)]})],
         })
 
-        with self.mocked_today('2017-02-01'):
-            self.invoice.post()
-
-            xml_content = self.facturx_edi_format._export_invoice_to_attachment(self.invoice)['datas']
-            current_etree = self.get_xml_tree_from_string(xml_content)
-            expected_etree = self.with_applied_xpath(
-                self.get_xml_tree_from_string(self.expected_invoice_facturx_values),
-                '''
-                    <xpath expr="//GrossPriceProductTradePrice/ChargeAmount" position="replace">
-                        <ChargeAmount currencyID="Gol">275.000</ChargeAmount>
-                    </xpath>
-                    <xpath expr="//SpecifiedLineTradeSettlement" position="replace">
-                        <SpecifiedLineTradeSettlement>
-                            <ApplicableTradeTax>
-                                <RateApplicablePercent>10.0</RateApplicablePercent>
-                            </ApplicableTradeTax>
-                            <ApplicableTradeTax>
-                                <RateApplicablePercent>20.0</RateApplicablePercent>
-                            </ApplicableTradeTax>
-                            <SpecifiedTradeSettlementLineMonetarySummation>
-                                <LineTotalAmount currencyID="Gol">1000.000</LineTotalAmount>
-                            </SpecifiedTradeSettlementLineMonetarySummation>
-                        </SpecifiedLineTradeSettlement>
-                    </xpath>
-                    <xpath expr="//ApplicableHeaderTradeSettlement" position="replace">
-                        <ApplicableHeaderTradeSettlement>
-                            <ApplicableTradeTax>
-                                <CalculatedAmount currencyID="Gol">220.000</CalculatedAmount>
-                                <BasisAmount currencyID="Gol">1100.000</BasisAmount>
-                                <RateApplicablePercent>20.0</RateApplicablePercent>
-                            </ApplicableTradeTax>
-                            <ApplicableTradeTax>
-                                <CalculatedAmount currencyID="Gol">100.000</CalculatedAmount>
-                                <BasisAmount currencyID="Gol">1000.000</BasisAmount>
-                                <RateApplicablePercent>10.0</RateApplicablePercent>
-                            </ApplicableTradeTax>
-                            <SpecifiedTradePaymentTerms>
-                                <DueDateDateTime>
-                                    <DateTimeString>20170101</DateTimeString>
-                                </DueDateDateTime>
-                            </SpecifiedTradePaymentTerms>
-                            <SpecifiedTradeSettlementHeaderMonetarySummation>
-                                <LineTotalAmount currencyID="Gol">1000.000</LineTotalAmount>
-                                <TaxBasisTotalAmount currencyID="Gol">1000.000</TaxBasisTotalAmount>
-                                <TaxTotalAmount currencyID="Gol">320.000</TaxTotalAmount>
-                                <GrandTotalAmount currencyID="Gol">1320.000</GrandTotalAmount>
-                                <TotalPrepaidAmount currencyID="Gol">0.000</TotalPrepaidAmount>
-                                <DuePayableAmount currencyID="Gol">1320.000</DuePayableAmount>
-                            </SpecifiedTradeSettlementHeaderMonetarySummation>
-                        </ApplicableHeaderTradeSettlement>
-                    </xpath>
-                ''',
-            )
-            self.assertXmlTreeEqual(current_etree, expected_etree)
+        self.invoice.post()
+
+        xml_content = self.facturx_edi_format._export_invoice_to_attachment(self.invoice)['datas']
+        current_etree = self.get_xml_tree_from_string(xml_content)
+        expected_etree = self.with_applied_xpath(
+            self.get_xml_tree_from_string(self.expected_invoice_facturx_values),
+            '''
+                <xpath expr="//GrossPriceProductTradePrice/ChargeAmount" position="replace">
+                    <ChargeAmount currencyID="Gol">275.000</ChargeAmount>
+                </xpath>
+                <xpath expr="//SpecifiedLineTradeSettlement" position="replace">
+                    <SpecifiedLineTradeSettlement>
+                        <ApplicableTradeTax>
+                            <RateApplicablePercent>10.0</RateApplicablePercent>
+                        </ApplicableTradeTax>
+                        <ApplicableTradeTax>
+                            <RateApplicablePercent>20.0</RateApplicablePercent>
+                        </ApplicableTradeTax>
+                        <SpecifiedTradeSettlementLineMonetarySummation>
+                            <LineTotalAmount currencyID="Gol">1000.000</LineTotalAmount>
+                        </SpecifiedTradeSettlementLineMonetarySummation>
+                    </SpecifiedLineTradeSettlement>
+                </xpath>
+                <xpath expr="//ApplicableHeaderTradeSettlement" position="replace">
+                    <ApplicableHeaderTradeSettlement>
+                        <ApplicableTradeTax>
+                            <CalculatedAmount currencyID="Gol">220.000</CalculatedAmount>
+                            <BasisAmount currencyID="Gol">1100.000</BasisAmount>
+                            <RateApplicablePercent>20.0</RateApplicablePercent>
+                        </ApplicableTradeTax>
+                        <ApplicableTradeTax>
+                            <CalculatedAmount currencyID="Gol">100.000</CalculatedAmount>
+                            <BasisAmount currencyID="Gol">1000.000</BasisAmount>
+                            <RateApplicablePercent>10.0</RateApplicablePercent>
+                        </ApplicableTradeTax>
+                        <SpecifiedTradePaymentTerms>
+                            <DueDateDateTime>
+                                <DateTimeString>20170101</DateTimeString>
+                            </DueDateDateTime>
+                        </SpecifiedTradePaymentTerms>
+                        <SpecifiedTradeSettlementHeaderMonetarySummation>
+                            <LineTotalAmount currencyID="Gol">1000.000</LineTotalAmount>
+                            <TaxBasisTotalAmount currencyID="Gol">1000.000</TaxBasisTotalAmount>
+                            <TaxTotalAmount currencyID="Gol">320.000</TaxTotalAmount>
+                            <GrandTotalAmount currencyID="Gol">1320.000</GrandTotalAmount>
+                            <TotalPrepaidAmount currencyID="Gol">0.000</TotalPrepaidAmount>
+                            <DuePayableAmount currencyID="Gol">1320.000</DuePayableAmount>
+                        </SpecifiedTradeSettlementHeaderMonetarySummation>
+                    </ApplicableHeaderTradeSettlement>
+                </xpath>
+            ''',
+        )
+        self.assertXmlTreeEqual(current_etree, expected_etree)
diff --git a/debian/control b/debian/control
index c8fdd67b96c9..3b5cb117ef89 100644
--- a/debian/control
+++ b/debian/control
@@ -22,6 +22,7 @@ Depends:
  python3-decorator,
  python3-docutils,
  python3-feedparser,
+ python3-freezegun,
  python3-html2text,
  python3-pil,
  python3-jinja2,
diff --git a/requirements.txt b/requirements.txt
index 693b83479077..26fb9582b70a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,6 +4,7 @@ decorator==4.3.0
 docutils==0.14
 ebaysdk==2.1.5
 feedparser==5.2.1
+freezegun==0.3.11
 gevent==1.1.2 ; sys_platform != 'win32' and python_version < '3.7'
 gevent==1.3.7 ; sys_platform != 'win32' and python_version >= '3.7'
 gevent==1.4.0 ; sys_platform == 'win32'
diff --git a/setup.cfg b/setup.cfg
index 0523f3f81c1b..5881e460d350 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -17,6 +17,7 @@ requires =
   python3-decorator
   python3-docutils
   python3-feedparser
+  python3-freezegun
   python3-gevent
   python3-greenlet
   python3-html2text
diff --git a/setup.py b/setup.py
index 6142915cdded..355d07c71fa2 100644
--- a/setup.py
+++ b/setup.py
@@ -64,6 +64,6 @@ setup(
         'SSL': ['pyopenssl'],
     },
     tests_require=[
-        'mock',
+        'freezegun',
     ],
 )
-- 
GitLab