blob: bc9f04e0892fa1058b481ee6546f15487ee82007 [file] [log] [blame]
Ankush Menat76dd6e92021-05-23 16:19:48 +05301# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
Rushabh Mehta982be9f2017-01-17 17:57:19 +05302# License: GNU General Public License v3. See license.txt
3
Ankush Menat76dd6e92021-05-23 16:19:48 +05304import copy
Ankush Menat445966a2021-12-01 16:34:05 +05305import signal
Ankush Menat06fa35a2021-09-14 20:05:16 +05306import unittest
Ankush Menat76dd6e92021-05-23 16:19:48 +05307from contextlib import contextmanager
Ankush Menat70c203d2021-09-15 19:24:35 +05308from typing import Any, Dict, NewType, Optional
Rushabh Mehta982be9f2017-01-17 17:57:19 +05309
10import frappe
Ankush Menat70c203d2021-09-15 19:24:35 +053011from frappe.core.doctype.report.report import get_report_module_dotted_path
12
13ReportFilters = Dict[str, Any]
14ReportName = NewType("ReportName", str)
Rushabh Mehta982be9f2017-01-17 17:57:19 +053015
Chillar Anand915b3432021-09-02 16:44:59 +053016
Ankush Menat06fa35a2021-09-14 20:05:16 +053017class ERPNextTestCase(unittest.TestCase):
18 """A sane default test class for ERPNext tests."""
19
Ankush Menatacdb26a2021-10-12 14:45:29 +053020
21 @classmethod
22 def setUpClass(cls) -> None:
Ankush Menat06fa35a2021-09-14 20:05:16 +053023 frappe.db.commit()
Ankush Menatacdb26a2021-10-12 14:45:29 +053024 return super().setUpClass()
Ankush Menat06fa35a2021-09-14 20:05:16 +053025
Ankush Menatacdb26a2021-10-12 14:45:29 +053026 @classmethod
27 def tearDownClass(cls) -> None:
Ankush Menat06fa35a2021-09-14 20:05:16 +053028 frappe.db.rollback()
Ankush Menatacdb26a2021-10-12 14:45:29 +053029 return super().tearDownClass()
Ankush Menat06fa35a2021-09-14 20:05:16 +053030
31
Rushabh Mehta982be9f2017-01-17 17:57:19 +053032def create_test_contact_and_address():
Rushabh Mehtaa0c41b72017-01-18 14:14:20 +053033 frappe.db.sql('delete from tabContact')
Nabin Hait1aa8c2e2020-03-26 13:15:31 +053034 frappe.db.sql('delete from `tabContact Email`')
35 frappe.db.sql('delete from `tabContact Phone`')
Rushabh Mehtaa0c41b72017-01-18 14:14:20 +053036 frappe.db.sql('delete from tabAddress')
37 frappe.db.sql('delete from `tabDynamic Link`')
Rushabh Mehta982be9f2017-01-17 17:57:19 +053038
Himanshu25ab1e42019-09-30 10:08:15 +053039 frappe.get_doc({
40 "doctype": "Address",
41 "address_title": "_Test Address for Customer",
42 "address_type": "Office",
43 "address_line1": "Station Road",
44 "city": "_Test City",
45 "state": "Test State",
46 "country": "India",
47 "links": [
48 {
49 "link_doctype": "Customer",
50 "link_name": "_Test Customer"
51 }
52 ]
53 }).insert()
Rushabh Mehtaa0c41b72017-01-18 14:14:20 +053054
Himanshu25ab1e42019-09-30 10:08:15 +053055 contact = frappe.get_doc({
56 "doctype": 'Contact',
57 "first_name": "_Test Contact for _Test Customer",
58 "links": [
59 {
60 "link_doctype": "Customer",
61 "link_name": "_Test Customer"
62 }
63 ]
64 })
65 contact.add_email("test_contact_customer@example.com", is_primary=True)
66 contact.add_phone("+91 0000000000", is_primary_phone=True)
67 contact.insert()
Ankush Menat76dd6e92021-05-23 16:19:48 +053068
69
70@contextmanager
71def change_settings(doctype, settings_dict):
72 """ A context manager to ensure that settings are changed before running
73 function and restored after running it regardless of exceptions occured.
74 This is useful in tests where you want to make changes in a function but
75 don't retain those changes.
76 import and use as decorator to cover full function or using `with` statement.
77
78 example:
79 @change_settings("Stock Settings", {"item_naming_by": "Naming Series"})
80 def test_case(self):
81 ...
82 """
83
84 try:
85 settings = frappe.get_doc(doctype)
86 # remember setting
87 previous_settings = copy.deepcopy(settings_dict)
88 for key in previous_settings:
89 previous_settings[key] = getattr(settings, key)
90
91 # change setting
92 for key, value in settings_dict.items():
93 setattr(settings, key, value)
94 settings.save()
95 yield # yield control to calling function
96
97 finally:
98 # restore settings
99 settings = frappe.get_doc(doctype)
100 for key, value in previous_settings.items():
101 setattr(settings, key, value)
102 settings.save()
Ankush Menat70c203d2021-09-15 19:24:35 +0530103
104
105def execute_script_report(
106 report_name: ReportName,
107 module: str,
108 filters: ReportFilters,
109 default_filters: Optional[ReportFilters] = None,
110 optional_filters: Optional[ReportFilters] = None
111 ):
112 """Util for testing execution of a report with specified filters.
113
114 Tests the execution of report with default_filters + filters.
115 Tests the execution using optional_filters one at a time.
116
117 Args:
118 report_name: Human readable name of report (unscrubbed)
119 module: module to which report belongs to
120 filters: specific values for filters
121 default_filters: default values for filters such as company name.
122 optional_filters: filters which should be tested one at a time in addition to default filters.
123 """
124
125 if default_filters is None:
126 default_filters = {}
127
Ankush Menatf195f802022-01-09 19:23:27 +0530128 test_filters = []
Ankush Menat70c203d2021-09-15 19:24:35 +0530129 report_execute_fn = frappe.get_attr(get_report_module_dotted_path(module, report_name) + ".execute")
130 report_filters = frappe._dict(default_filters).copy().update(filters)
131
Ankush Menatf195f802022-01-09 19:23:27 +0530132 test_filters.append(report_filters)
Ankush Menat70c203d2021-09-15 19:24:35 +0530133
134 if optional_filters:
135 for key, value in optional_filters.items():
Ankush Menatf195f802022-01-09 19:23:27 +0530136 test_filters.append(report_filters.copy().update({key: value}))
Ankush Menat70c203d2021-09-15 19:24:35 +0530137
Ankush Menatf195f802022-01-09 19:23:27 +0530138 for test_filter in test_filters:
139 try:
140 report_execute_fn(test_filter)
141 except Exception:
142 print(f"Report failed to execute with filters: {test_filter}")
143 raise
144
Ankush Menat445966a2021-12-01 16:34:05 +0530145
146
147def timeout(seconds=30, error_message="Test timed out."):
148 """ Timeout decorator to ensure a test doesn't run for too long.
149
150 adapted from https://stackoverflow.com/a/2282656"""
151 def decorator(func):
152 def _handle_timeout(signum, frame):
153 raise Exception(error_message)
154
155 def wrapper(*args, **kwargs):
156 signal.signal(signal.SIGALRM, _handle_timeout)
157 signal.alarm(seconds)
158 try:
159 result = func(*args, **kwargs)
160 finally:
161 signal.alarm(0)
162 return result
163 return wrapper
164 return decorator