# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE import io import json import os import sys from datetime import date, datetime, time, timedelta, timezone from decimal import ROUND_HALF_UP, Decimal, localcontext from enum import Enum from io import StringIO from mimetypes import guess_type from unittest.mock import patch from hypothesis import given from hypothesis import strategies as st from PIL import Image import frappe from frappe.installer import parse_app_name from frappe.model.document import Document from frappe.tests import IntegrationTestCase, MockedRequestTestCase from frappe.utils import ( add_trackers_to_url, ceil, dict_to_str, execute_in_shell, floor, flt, format_timedelta, get_bench_path, get_file_timestamp, get_gravatar, get_site_info, get_sites, get_url, map_trackers, money_in_words, parse_and_map_trackers_from_url, parse_timedelta, random_string, remove_blanks, safe_json_loads, scrub_urls, validate_email_address, validate_name, validate_phone_number_with_country_code, validate_url, ) from frappe.utils.change_log import ( get_source_url, parse_github_url, ) from frappe.utils.data import ( add_to_date, add_years, cast, cint, cstr, duration_to_seconds, evaluate_filters, expand_relative_urls, get_datetime, get_first_day_of_week, get_time, get_timedelta, get_timespan_date_range, get_url_to_form, get_year_ending, getdate, is_invalid_date_string, now_datetime, nowtime, pretty_date, rounded, sha256_hash, to_timedelta, validate_python_code, ) from frappe.utils.dateutils import get_dates_from_timegrain from frappe.utils.diff import _get_value_from_version, get_version_diff, version_query from frappe.utils.identicon import Identicon from frappe.utils.image import optimize_image, strip_exif_data from frappe.utils.make_random import can_make, get_random, how_many from frappe.utils.response import json_handler from frappe.utils.synchronization import LockTimeoutError, filelock class Capturing(list): # ref: https://stackoverflow.com/a/16571630/10309266 def __enter__(self): self._stdout = sys.stdout sys.stdout = self._stringio = StringIO() return self def __exit__(self, *args): self.extend(self._stringio.getvalue().splitlines()) del self._stringio sys.stdout = self._stdout class TestFilters(IntegrationTestCase): def test_simple_dict(self): self.assertTrue(evaluate_filters({"doctype": "User", "status": "Open"}, {"status": "Open"})) self.assertFalse(evaluate_filters({"doctype": "User", "status": "Open"}, {"status": "Closed"})) def test_multiple_dict(self): self.assertTrue( evaluate_filters( {"doctype": "User", "status": "Open", "name": "Test 1"}, {"status": "Open", "name": "Test 1"}, ) ) self.assertFalse( evaluate_filters( {"doctype": "User", "status": "Open", "name": "Test 1"}, {"status": "Closed", "name": "Test 1"}, ) ) def test_list_filters(self): self.assertTrue( evaluate_filters( {"doctype": "User", "status": "Open", "name": "Test 1"}, [{"status": "Open"}, {"name": "Test 1"}], ) ) self.assertFalse( evaluate_filters( {"doctype": "User", "status": "Open", "name": "Test 1"}, [{"status": "Open"}, {"name": "Test 2"}], ) ) def test_list_filters_as_list(self): self.assertTrue( evaluate_filters( {"doctype": "User", "status": "Open", "name": "Test 1"}, [["status", "=", "Open"], ["name", "=", "Test 1"]], ) ) self.assertFalse( evaluate_filters( {"doctype": "User", "status": "Open", "name": "Test 1"}, [["status", "=", "Open"], ["name", "=", "Test 2"]], ) ) def test_lt_gt(self): self.assertTrue( evaluate_filters( {"doctype": "User", "status": "Open", "age": 20}, {"status": "Open", "age": (">", 10)}, ) ) self.assertFalse( evaluate_filters( {"doctype": "User", "status": "Open", "age": 20}, {"status": "Open", "age": (">", 30)}, ) ) def test_date_time(self): # date fields self.assertTrue( evaluate_filters( {"doctype": "User", "birth_date": "2023-02-28"}, [("User", "birth_date", ">", "01-04-2022")], ) ) self.assertFalse( evaluate_filters( {"doctype": "User", "birth_date": "2023-02-28"}, [("User", "birth_date", "<", "28-02-2023")], ) ) # datetime fields self.assertTrue( evaluate_filters( {"doctype": "User", "last_active": "2023-02-28 15:14:56"}, [("User", "last_active", ">", "01-04-2022 00:00:00")], ) ) self.assertFalse( evaluate_filters( {"doctype": "User", "last_active": "2023-02-28 15:14:56"}, [("User", "last_active", "<", "28-02-2023 00:00:00")], ) ) def test_filter_evaluation(self): doc = { "doctype": "User", "username": "test_abc", "prefix": "startswith", "suffix": "endswith", "empty": None, "number": 0, } test_cases = [ ([["username", "like", "test"]], True), ([["username", "like", "user1"]], False), ([["username", "not like", "test"]], False), ([["username", "not like", "user1"]], True), ([["prefix", "like", "start%"]], True), ([["prefix", "not like", "end%"]], True), ([["suffix", "like", "%with"]], True), ([["suffix", "not like", "%end"]], True), ([["suffix", "is", "set"]], True), ([["suffix", "is", "not set"]], False), ([["empty", "is", "set"]], False), ([["empty", "is", "not set"]], True), ([["number", "is", "set"]], True), ] for filter, expected_result in test_cases: self.assertEqual(evaluate_filters(doc, filter), expected_result, msg=f"{filter}") def test_timespan(self): doc = { "doctype": "User", "last_password_reset_date": getdate(), } self.assertTrue(evaluate_filters(doc, [("last_password_reset_date", "Timespan", "today")])) self.assertFalse(evaluate_filters(doc, [("last_password_reset_date", "Timespan", "last year")])) doc = { "doctype": "User", "last_password_reset_date": None, } self.assertFalse(evaluate_filters(doc, [("last_password_reset_date", "Timespan", "today")])) class TestMoney(IntegrationTestCase): def test_money_in_words(self): test_cases = { "BHD": [ (5000, "BHD Five Thousand only."), (5000.0, "BHD Five Thousand only."), (0.1, "One Hundred Fils only."), (0, "BHD Zero only."), ("Fail", ""), ], "NGN": [ (5000, "NGN Five Thousand only."), (5000.0, "NGN Five Thousand only."), (0.1, "Ten Kobo only."), (0, "NGN Zero only."), ("Fail", ""), ], "MRO": [ (5000, "MRO Five Thousand only."), (5000.0, "MRO Five Thousand only."), (1.4, "MRO One and Two Khoums only."), (0.2, "One Khoums only."), (0, "MRO Zero only."), ("Fail", ""), ], } for currency, cases in test_cases.items(): for money, expected_words in cases: words = money_in_words(money, currency) self.assertEqual( words, expected_words, f"{words} is not the same as {expected_words}", ) class TestDataManipulation(IntegrationTestCase): def test_scrub_urls(self): html = """

You have a new message from: John

Hey, wassup!

Test link 1 Test link 2 Test link 3
Please mail us at email
""" html = scrub_urls(html) url = get_url() self.assertTrue('Test link 1' in html) self.assertTrue(f'Test link 2' in html) self.assertTrue(f'Test link 3' in html) self.assertTrue(f'' in html) self.assertTrue(f"style=\"background-image: url('{url}/assets/frappe/bg.jpg') !important\"" in html) self.assertTrue('email' in html) class TestFieldCasting(IntegrationTestCase): def test_str_types(self): STR_TYPES = ( "Data", "Text", "Small Text", "Long Text", "Text Editor", "Select", "Link", "Dynamic Link", ) for fieldtype in STR_TYPES: self.assertIsInstance(cast(fieldtype, value=None), str) self.assertIsInstance(cast(fieldtype, value="12-12-2021"), str) self.assertIsInstance(cast(fieldtype, value=""), str) self.assertIsInstance(cast(fieldtype, value=[]), str) self.assertIsInstance(cast(fieldtype, value=set()), str) def test_float_types(self): FLOAT_TYPES = ("Currency", "Float", "Percent") for fieldtype in FLOAT_TYPES: self.assertIsInstance(cast(fieldtype, value=None), float) self.assertIsInstance(cast(fieldtype, value=1.12), float) self.assertIsInstance(cast(fieldtype, value=112), float) def test_int_types(self): INT_TYPES = ("Int", "Check") for fieldtype in INT_TYPES: self.assertIsInstance(cast(fieldtype, value=None), int) self.assertIsInstance(cast(fieldtype, value=1.12), int) self.assertIsInstance(cast(fieldtype, value=112), int) def test_datetime_types(self): self.assertIsInstance(cast("Datetime", value=None), datetime) self.assertIsInstance(cast("Datetime", value="12-2-22"), datetime) def test_date_types(self): self.assertIsInstance(cast("Date", value=None), date) self.assertIsInstance(cast("Date", value="12-12-2021"), date) def test_time_types(self): self.assertIsInstance(cast("Time", value=None), timedelta) self.assertIsInstance(cast("Time", value="12:03:34"), timedelta) class TestMathUtils(IntegrationTestCase): def test_floor(self): from decimal import Decimal self.assertEqual(floor(2), 2) self.assertEqual(floor(12.32904), 12) self.assertEqual(floor(22.7330), 22) self.assertEqual(floor("24.7"), 24) self.assertEqual(floor("26.7"), 26) self.assertEqual(floor(Decimal(29.45)), 29) def test_ceil(self): from decimal import Decimal self.assertEqual(ceil(2), 2) self.assertEqual(ceil(12.32904), 13) self.assertEqual(ceil(22.7330), 23) self.assertEqual(ceil("24.7"), 25) self.assertEqual(ceil("26.7"), 27) self.assertEqual(ceil(Decimal(29.45)), 30) class TestHTMLUtils(IntegrationTestCase): def test_clean_email_html(self): from frappe.utils.html_utils import clean_email_html sample = """

Hello

Para

""" clean = clean_email_html(sample) self.assertFalse("