blob: 2bd7e9e71d0d1114acd07f2bce90377ee838a5fa [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()
Ankush Menat3d3f0132022-01-24 18:44:08 +053095 # singles are cached by default, clear to avoid flake
96 frappe.db.value_cache[settings] = {}
Ankush Menat76dd6e92021-05-23 16:19:48 +053097 yield # yield control to calling function
98
99 finally:
100 # restore settings
101 settings = frappe.get_doc(doctype)
102 for key, value in previous_settings.items():
103 setattr(settings, key, value)
104 settings.save()
Ankush Menat70c203d2021-09-15 19:24:35 +0530105
106
107def execute_script_report(
108 report_name: ReportName,
109 module: str,
110 filters: ReportFilters,
111 default_filters: Optional[ReportFilters] = None,
112 optional_filters: Optional[ReportFilters] = None
113 ):
114 """Util for testing execution of a report with specified filters.
115
116 Tests the execution of report with default_filters + filters.
117 Tests the execution using optional_filters one at a time.
118
119 Args:
120 report_name: Human readable name of report (unscrubbed)
121 module: module to which report belongs to
122 filters: specific values for filters
123 default_filters: default values for filters such as company name.
124 optional_filters: filters which should be tested one at a time in addition to default filters.
125 """
126
127 if default_filters is None:
128 default_filters = {}
129
Ankush Menatf195f802022-01-09 19:23:27 +0530130 test_filters = []
Ankush Menat70c203d2021-09-15 19:24:35 +0530131 report_execute_fn = frappe.get_attr(get_report_module_dotted_path(module, report_name) + ".execute")
132 report_filters = frappe._dict(default_filters).copy().update(filters)
133
Ankush Menatf195f802022-01-09 19:23:27 +0530134 test_filters.append(report_filters)
Ankush Menat70c203d2021-09-15 19:24:35 +0530135
136 if optional_filters:
137 for key, value in optional_filters.items():
Ankush Menatf195f802022-01-09 19:23:27 +0530138 test_filters.append(report_filters.copy().update({key: value}))
Ankush Menat70c203d2021-09-15 19:24:35 +0530139
Ankush Menatf195f802022-01-09 19:23:27 +0530140 for test_filter in test_filters:
141 try:
142 report_execute_fn(test_filter)
143 except Exception:
144 print(f"Report failed to execute with filters: {test_filter}")
145 raise
146
Ankush Menat445966a2021-12-01 16:34:05 +0530147
148
149def timeout(seconds=30, error_message="Test timed out."):
150 """ Timeout decorator to ensure a test doesn't run for too long.
151
152 adapted from https://stackoverflow.com/a/2282656"""
153 def decorator(func):
154 def _handle_timeout(signum, frame):
155 raise Exception(error_message)
156
157 def wrapper(*args, **kwargs):
158 signal.signal(signal.SIGALRM, _handle_timeout)
159 signal.alarm(seconds)
160 try:
161 result = func(*args, **kwargs)
162 finally:
163 signal.alarm(0)
164 return result
165 return wrapper
166 return decorator