import unittest from random import choice import requests from semantic_version import Version import frappe from frappe.utils import get_site_url def maintain_state(f): def wrapper(*args, **kwargs): frappe.db.rollback() r = f(*args, **kwargs) frappe.db.commit() return r return wrapper class TestResourceAPI(unittest.TestCase): SITE_URL = get_site_url(frappe.local.site) RESOURCE_URL = f"{SITE_URL}/api/resource" DOCTYPE = "ToDo" GENERATED_DOCUMENTS = [] @classmethod @maintain_state def setUpClass(self): for _ in range(10): doc = frappe.get_doc( {"doctype": "ToDo", "description": frappe.mock("paragraph")} ).insert() self.GENERATED_DOCUMENTS.append(doc.name) @classmethod @maintain_state def tearDownClass(self): for name in self.GENERATED_DOCUMENTS: frappe.delete_doc_if_exists(self.DOCTYPE, name) @property def sid(self): if not getattr(self, "_sid", None): self._sid = requests.post( f"{self.SITE_URL}/api/method/login", data={ "usr": "Administrator", "pwd": frappe.conf.admin_password or "admin", }, ).cookies.get("sid") return self._sid def get(self, path, params=""): return requests.get(f"{self.RESOURCE_URL}/{path}?sid={self.sid}{params}") def post(self, path, data): return requests.post( f"{self.RESOURCE_URL}/{path}?sid={self.sid}", data=frappe.as_json(data) ) def put(self, path, data): return requests.put( f"{self.RESOURCE_URL}/{path}?sid={self.sid}", data=frappe.as_json(data) ) def delete(self, path): return requests.delete(f"{self.RESOURCE_URL}/{path}?sid={self.sid}") def test_unauthorized_call(self): # test 1: fetch documents without auth response = requests.get(f"{self.RESOURCE_URL}/{self.DOCTYPE}") self.assertEqual(response.status_code, 403) def test_get_list(self): # test 2: fetch documents without params response = self.get(self.DOCTYPE) self.assertEqual(response.status_code, 200) self.assertIsInstance(response.json(), dict) self.assertIn("data", response.json()) def test_get_list_limit(self): # test 3: fetch data with limit response = self.get(self.DOCTYPE, "&limit=2") self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()["data"]), 2) def test_get_list_dict(self): # test 4: fetch response as (not) dict response = self.get(self.DOCTYPE, "&as_dict=True") json = frappe._dict(response.json()) self.assertEqual(response.status_code, 200) self.assertIsInstance(json.data, list) self.assertIsInstance(json.data[0], dict) response = self.get(self.DOCTYPE, "&as_dict=False") json = frappe._dict(response.json()) self.assertEqual(response.status_code, 200) self.assertIsInstance(json.data, list) self.assertIsInstance(json.data[0], list) def test_get_list_debug(self): # test 5: fetch response with debug response = self.get(self.DOCTYPE, "&debug=true") self.assertEqual(response.status_code, 200) self.assertIn("exc", response.json()) self.assertIsInstance(response.json()["exc"], str) self.assertIsInstance(eval(response.json()["exc"]), list) def test_get_list_fields(self): # test 6: fetch response with fields response = self.get(self.DOCTYPE, r'&fields=["description"]') self.assertEqual(response.status_code, 200) json = frappe._dict(response.json()) self.assertIn("description", json.data[0]) def test_create_document(self): # test 7: POST method on /api/resource to create doc data = {"description": frappe.mock("paragraph")} response = self.post(self.DOCTYPE, data) self.assertEqual(response.status_code, 200) docname = response.json()["data"]["name"] self.assertIsInstance(docname, str) self.GENERATED_DOCUMENTS.append(docname) def test_update_document(self): # test 8: PUT method on /api/resource to update doc generated_desc = frappe.mock("paragraph") data = {"description": generated_desc} random_doc = choice(self.GENERATED_DOCUMENTS) desc_before_update = frappe.db.get_value(self.DOCTYPE, random_doc, "description") response = self.put(f"{self.DOCTYPE}/{random_doc}", data=data) self.assertEqual(response.status_code, 200) self.assertNotEqual(response.json()["data"]["description"], desc_before_update) self.assertEqual(response.json()["data"]["description"], generated_desc) def test_delete_document(self): # test 9: DELETE method on /api/resource doc_to_delete = choice(self.GENERATED_DOCUMENTS) response = self.delete(f"{self.DOCTYPE}/{doc_to_delete}") self.assertEqual(response.status_code, 202) self.assertDictEqual(response.json(), {"message": "ok"}) non_existent_doc = frappe.generate_hash(length=12) response = self.delete(f"{self.DOCTYPE}/{non_existent_doc}") self.assertEqual(response.status_code, 404) self.assertDictEqual(response.json(), {}) class TestMethodAPI(unittest.TestCase): METHOD_URL = f"{get_site_url(frappe.local.site)}/api/method" def test_version(self): # test 1: test for /api/method/version response = requests.get(f"{self.METHOD_URL}/version") json = frappe._dict(response.json()) self.assertEqual(response.status_code, 200) self.assertIsInstance(json, dict) self.assertIsInstance(json.message, str) self.assertEqual(Version(json.message), Version(frappe.__version__)) def test_ping(self): # test 2: test for /api/method/ping response = requests.get(f"{self.METHOD_URL}/ping") self.assertEqual(response.status_code, 200) self.assertIsInstance(response.json(), dict) self.assertEqual(response.json()['message'], "pong")