blob: fbf25948a79f7816680b28e98b11d09760316cb8 [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
128 report_execute_fn = frappe.get_attr(get_report_module_dotted_path(module, report_name) + ".execute")
129 report_filters = frappe._dict(default_filters).copy().update(filters)
130
131 report_data = report_execute_fn(report_filters)
132
133 if optional_filters:
134 for key, value in optional_filters.items():
135 filter_with_optional_param = report_filters.copy().update({key: value})
136 report_execute_fn(filter_with_optional_param)
137
138 return report_data
Ankush Menat445966a2021-12-01 16:34:05 +0530139
140
141def timeout(seconds=30, error_message="Test timed out."):
142 """ Timeout decorator to ensure a test doesn't run for too long.
143
144 adapted from https://stackoverflow.com/a/2282656"""
145 def decorator(func):
146 def _handle_timeout(signum, frame):
147 raise Exception(error_message)
148
149 def wrapper(*args, **kwargs):
150 signal.signal(signal.SIGALRM, _handle_timeout)
151 signal.alarm(seconds)
152 try:
153 result = func(*args, **kwargs)
154 finally:
155 signal.alarm(0)
156 return result
157 return wrapper
158 return decorator