blob: 40c95eb7a3a46efd1a7b770892a447a36a9cb38c [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
Devin Slauenwhited636c3f2022-02-09 10:52:38 -050069 contact_two = frappe.get_doc({
70 "doctype": 'Contact',
71 "first_name": "_Test Contact 2 for _Test Customer",
72 "links": [
73 {
74 "link_doctype": "Customer",
75 "link_name": "_Test Customer"
76 }
77 ]
78 })
79 contact_two.add_email("test_contact_two_customer@example.com", is_primary=True)
80 contact_two.add_phone("+92 0000000000", is_primary_phone=True)
81 contact_two.insert()
82
Ankush Menat76dd6e92021-05-23 16:19:48 +053083
84@contextmanager
85def change_settings(doctype, settings_dict):
86 """ A context manager to ensure that settings are changed before running
87 function and restored after running it regardless of exceptions occured.
88 This is useful in tests where you want to make changes in a function but
89 don't retain those changes.
90 import and use as decorator to cover full function or using `with` statement.
91
92 example:
93 @change_settings("Stock Settings", {"item_naming_by": "Naming Series"})
94 def test_case(self):
95 ...
96 """
97
98 try:
99 settings = frappe.get_doc(doctype)
100 # remember setting
101 previous_settings = copy.deepcopy(settings_dict)
102 for key in previous_settings:
103 previous_settings[key] = getattr(settings, key)
104
105 # change setting
106 for key, value in settings_dict.items():
107 setattr(settings, key, value)
108 settings.save()
Ankush Menat3d3f0132022-01-24 18:44:08 +0530109 # singles are cached by default, clear to avoid flake
110 frappe.db.value_cache[settings] = {}
Ankush Menat76dd6e92021-05-23 16:19:48 +0530111 yield # yield control to calling function
112
113 finally:
114 # restore settings
115 settings = frappe.get_doc(doctype)
116 for key, value in previous_settings.items():
117 setattr(settings, key, value)
118 settings.save()
Ankush Menat70c203d2021-09-15 19:24:35 +0530119
120
121def execute_script_report(
122 report_name: ReportName,
123 module: str,
124 filters: ReportFilters,
125 default_filters: Optional[ReportFilters] = None,
126 optional_filters: Optional[ReportFilters] = None
127 ):
128 """Util for testing execution of a report with specified filters.
129
130 Tests the execution of report with default_filters + filters.
131 Tests the execution using optional_filters one at a time.
132
133 Args:
134 report_name: Human readable name of report (unscrubbed)
135 module: module to which report belongs to
136 filters: specific values for filters
137 default_filters: default values for filters such as company name.
138 optional_filters: filters which should be tested one at a time in addition to default filters.
139 """
140
141 if default_filters is None:
142 default_filters = {}
143
Ankush Menatf195f802022-01-09 19:23:27 +0530144 test_filters = []
Ankush Menat70c203d2021-09-15 19:24:35 +0530145 report_execute_fn = frappe.get_attr(get_report_module_dotted_path(module, report_name) + ".execute")
146 report_filters = frappe._dict(default_filters).copy().update(filters)
147
Ankush Menatf195f802022-01-09 19:23:27 +0530148 test_filters.append(report_filters)
Ankush Menat70c203d2021-09-15 19:24:35 +0530149
150 if optional_filters:
151 for key, value in optional_filters.items():
Ankush Menatf195f802022-01-09 19:23:27 +0530152 test_filters.append(report_filters.copy().update({key: value}))
Ankush Menat70c203d2021-09-15 19:24:35 +0530153
Ankush Menatf195f802022-01-09 19:23:27 +0530154 for test_filter in test_filters:
155 try:
156 report_execute_fn(test_filter)
157 except Exception:
158 print(f"Report failed to execute with filters: {test_filter}")
159 raise
160
Ankush Menat445966a2021-12-01 16:34:05 +0530161
162
163def timeout(seconds=30, error_message="Test timed out."):
164 """ Timeout decorator to ensure a test doesn't run for too long.
165
166 adapted from https://stackoverflow.com/a/2282656"""
167 def decorator(func):
168 def _handle_timeout(signum, frame):
169 raise Exception(error_message)
170
171 def wrapper(*args, **kwargs):
172 signal.signal(signal.SIGALRM, _handle_timeout)
173 signal.alarm(seconds)
174 try:
175 result = func(*args, **kwargs)
176 finally:
177 signal.alarm(0)
178 return result
179 return wrapper
180 return decorator