diff --git a/conftest.py b/conftest.py index 6bb56ec..edf2b13 100644 --- a/conftest.py +++ b/conftest.py @@ -20,7 +20,7 @@ Review, TestCase, TestStepArtifact, - TestStepArtifactType + TestStepArtifactType, ) from testguide_report_generator.model.TestCaseFolder import TestCaseFolder from testguide_report_generator.model.TestSuite import TestSuite @@ -145,16 +145,16 @@ def review(): @pytest.fixture def testcase( - artifact_mock_hash, - teststep, - teststep_2, - teststep_folder, - parameter, - parameter_2, - constant, - constant_2, - attribute, - review, + artifact_mock_hash, + teststep, + teststep_2, + teststep_folder, + parameter, + parameter_2, + constant, + constant_2, + attribute, + review, ): testcase = TestCase("testcase_one", 1670248341000, Verdict.PASSED) @@ -162,7 +162,7 @@ def testcase( testcase.set_execution_time_in_sec(5) testcase.add_parameter_set("myset", [parameter, parameter_2]) testcase.add_constants([constant, constant_2]) - testcase.add_constant(Constant("", "")) + testcase.add_constant(Constant("key", "value")) testcase.add_constant_pair("const_key", "const_val") testcase.add_attribute_pair("an", "attribute") testcase.add_setup_teststep(teststep) diff --git a/testguide_report_generator/model/TestCase.py b/testguide_report_generator/model/TestCase.py index a724f56..76be288 100644 --- a/testguide_report_generator/model/TestCase.py +++ b/testguide_report_generator/model/TestCase.py @@ -20,21 +20,21 @@ Verdict """ import errno -from enum import Enum import logging import os +import re +from enum import Enum from typing import List, Union - from testguide_report_generator.util.Json2AtxRepr import Json2AtxRepr from testguide_report_generator.util.File import get_md5_hash_from_file -from testguide_report_generator.util.ValidityChecks import check_name_length, gen_error_msg, \ - validate_new_teststep +from testguide_report_generator.util.ValidityChecks import check_string_length, validate_new_teststep class Verdict(Enum): """ ATX-Verdicts. """ + NONE = 1 PASSED = 2 INCONCLUSIVE = 3 @@ -46,6 +46,7 @@ class TestStepArtifactType(Enum): """ Possible types of artifacts attached to test steps """ + __test__ = False # pytest ignore IMAGE = 1 @@ -107,6 +108,7 @@ class Direction(Enum): """ Parameter directions. """ + IN = 1 OUT = 2 INOUT = 3 @@ -136,11 +138,7 @@ def create_json_repr(self): """ :see: :class:`Json2AtxRepr` """ - result = { - "name": self.__name, - "value": self.__value, - "direction": self.__direction.name - } + result = {"name": self.__name, "value": self.__value, "direction": self.__direction.name} return result @@ -149,6 +147,8 @@ class Constant(Json2AtxRepr): ATX-Constant """ + PATTERN = "^[a-zA-Z]([a-zA-Z0-9]|_[a-zA-Z0-9])*_?$" + def __init__(self, key: str, value: str): """ Constructor @@ -158,6 +158,10 @@ def __init__(self, key: str, value: str): :param value: Constant value :type value: str """ + pattern = re.compile(Constant.PATTERN) + if not pattern.match(key): + raise ValueError(f"Constant keys need to be structured following this pattern: {Constant.PATTERN}") + check_string_length(key, 1, 128, "Constant", "key") self.__key = key self.__value = value @@ -165,10 +169,7 @@ def create_json_repr(self): """ :see: :class:`Json2AtxRepr` """ - result = { - "key": self.__key, - "value": self.__value - } + result = {"key": self.__key, "value": self.__value} return result @@ -177,6 +178,11 @@ class Attribute(Json2AtxRepr): ATX-Attribute. """ + PATTERN = ( + "^[-.0-9:A-Z_a-z\u00b7\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u037d\u037f-\u1fff\u200c-\u200d" + "\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]+$" + ) + def __init__(self, key: str, value: str): """ Constructor @@ -186,6 +192,10 @@ def __init__(self, key: str, value: str): :param value: Attribute value :type value: str """ + pattern = re.compile(Attribute.PATTERN) + if not pattern.match(key): + raise ValueError(f"Attribute keys need to be structured following this pattern: {Attribute.PATTERN}") + check_string_length(key, 1, 255, "Attribute", "key") self.__key = key self.__value = value @@ -193,10 +203,7 @@ def create_json_repr(self): """ :see: :class:`Json2AtxRepr` """ - result = { - "key": self.__key, - "value": self.__value - } + result = {"key": self.__key, "value": self.__value} return result @@ -215,11 +222,9 @@ def __init__(self, comment: str, author: str, timestamp: int): :param timestamp: UTC timestamp in seconds :rtype timestamp: int """ - if not 1 <= len(comment) <= 10000: - raise ValueError("Comment length must be between 1 and 10000 characters.") - if len(author) > 512: - raise ValueError("Author length cannot exceed 512 characters.") + check_string_length(comment, 1, 10000, "Review", "comment") + check_string_length(author, 0, 512, "Review", "author") self.__comment = comment self.__author = author @@ -257,8 +262,7 @@ def set_summary(self, summary: str): :return: this object :rtype: Review """ - if len(summary) > 512: - raise ValueError("Summary length cannot exceed 512 characters.") + check_string_length(summary, 0, 512, "Review", "summary") self.__summary = summary return self @@ -271,19 +275,21 @@ def set_defect(self, defect: str): :return: this object :rtype: Review """ + check_string_length(defect, 0, 64, "Review", "defect") self.__defect = defect return self - def set_defect_priority(self, priority: str): + def set_defect_priority(self, defect_priority: str): """ Set the defect priority. - :param priority: Review priority + :param defect_priority: Review priority :type priority: str :return: this object :rtype: Review """ - self.__defect_priority = priority + check_string_length(defect_priority, 0, 64, "Review", "defectPriority") + self.__defect_priority = defect_priority return self def add_tickets(self, tickets: List[str]): @@ -296,8 +302,7 @@ def add_tickets(self, tickets: List[str]): :rtype: Review """ for ticket in tickets: - if len(ticket) > 512: - raise ValueError("Ticket length exceeds the maximum allowed (512 characters).") + check_string_length(ticket, 0, 512, "Review", "ticket") self.__tickets.extend(tickets) return self @@ -313,16 +318,17 @@ def set_invalid_run(self, invalid: bool): self.__invalid_run = invalid return self - def set_custom_evaluation(self, evaluation: str): + def set_custom_evaluation(self, custom_evaluation: str): """ Set a custom evaluation message. - :param evaluation: Review evaluation - :type evaluation: str + :param custom_evaluation: Review evaluation + :type custom_evaluation: str :return: this object :rtype: Review """ - self.__custom_evaluation = evaluation + check_string_length(custom_evaluation, 0, 64, "Review", "customEvaluation") + self.__custom_evaluation = custom_evaluation return self def add_tags(self, tags: List[str]): @@ -346,8 +352,7 @@ def add_contacts(self, contacts: List[str]): :rtype: Review """ for contact in contacts: - if len(contact) > 255: - raise ValueError("Contact length exceeds the maximum allowed (255 characters).") + check_string_length(contact, 0, 255, "Review", "contact") self.__contacts.extend(contacts) return self @@ -367,7 +372,7 @@ def create_json_repr(self): "invalidRun": self.__invalid_run, "customEvaluation": self.__custom_evaluation, "tags": self.__tags, - "contacts": self.__contacts + "contacts": self.__contacts, } if self.__verdict: result["verdict"] = self.__verdict.name @@ -423,7 +428,7 @@ class TestStep(Json2AtxRepr): __test__ = False # pytest ignore - def __init__(self, name: str, verdict: Verdict, expected_result: str = ''): + def __init__(self, name: str, verdict: Verdict, expected_result: str = ""): """ Constructor @@ -435,14 +440,14 @@ def __init__(self, name: str, verdict: Verdict, expected_result: str = ''): :type expected_result: str :raises TypeError: argument 'verdict' is not of type Verdict """ - self.__name = check_name_length(name, gen_error_msg("TestStep", name)) + self.__name = check_string_length(name, 1, 255, "TestStep", "name") if not isinstance(verdict, Verdict): raise TypeError("Argument 'verdict' must be of type 'Verdict'.") self.__description: str | None = None self.__verdict = verdict - self.__expected_result = expected_result + self.__expected_result = check_string_length(expected_result, 0, 1024, "TestStep", "expected_result") self.__artifacts: list[Artifact] = [] def set_description(self, desc: str): @@ -478,8 +483,9 @@ def add_artifact(self, file_path: str, artifact_type: TestStepArtifactType, igno except OSError as error: if not ignore_on_error: raise error - logging.warning(f"Artifact path '{file_path}' for teststep '{self.__name}' is invalid, " - f"will be ignored!") + logging.warning( + f"Artifact path '{file_path}' for teststep '{self.__name}' is invalid, " f"will be ignored!" + ) return self def get_artifacts(self): @@ -496,13 +502,13 @@ def create_json_repr(self): :see: :class:`Json2AtxRepr` """ result = { - "@type": "teststep", - "name": self.__name, - "description": self.__description, - "verdict": self.__verdict.name, - "expected_result": self.__expected_result, - "testStepArtifacts": [each.create_json_repr() for each in self.__artifacts] - } + "@type": "teststep", + "name": self.__name, + "description": self.__description, + "verdict": self.__verdict.name, + "expected_result": self.__expected_result, + "testStepArtifacts": [each.create_json_repr() for each in self.__artifacts], + } return result @@ -521,9 +527,9 @@ def __init__(self, name: str): :param name: TestStepFolder name :type name: str """ - self.__name = check_name_length(name, gen_error_msg("TestStepFolder", name)) + self.__name = check_string_length(name, 1, 255, "TestStepFolder", "name") self.__description: str | None = None - self.__teststeps: list[Union[TestStep, TestStepFolder]] = [] + self.__teststeps: list[Union[TestStep, TestStepFolder]] = [] def set_description(self, desc: str): """ @@ -565,11 +571,11 @@ def create_json_repr(self): :see: :class:`Json2AtxRepr` """ result = { - "@type": "teststepfolder", - "name": self.__name, - "description": self.__description, - "teststeps": [each.create_json_repr() for each in self.__teststeps], - } + "@type": "teststepfolder", + "name": self.__name, + "description": self.__description, + "teststeps": [each.create_json_repr() for each in self.__teststeps], + } return result @@ -595,7 +601,7 @@ def __init__(self, name: str, timestamp: int, verdict: Verdict): :raises: TypeError, if the argument 'verdict' is not of type Verdict """ - self.__name = check_name_length(name, gen_error_msg("TestCase", name)) + self.__name = check_string_length(name, 1, 120, "TestCase", "name") self.__timestamp = timestamp if not isinstance(verdict, Verdict): @@ -643,20 +649,20 @@ def set_execution_time_in_sec(self, exec_time: int): self.__execution_time = exec_time return self - def add_parameter_set(self, name: str, params: List[Parameter]): + def add_parameter_set(self, param_set: str, params: List[Parameter]): """ Set the parameter set. - :param name: name of the parameter set - :type name: str or None + :param param_set: name of the parameter set + :type param_set: str or None :param params: list of Parameter :type params: list :raises TypeError: the 'params' parameter has the wrong type :return: this object :rtype: TestCase """ - - self.__param_set = name + check_string_length(param_set, 0, 1024, "TestCase", "paramSet") + self.__param_set = param_set if not all(isinstance(param, Parameter) for param in params): raise TypeError("Argument params must be of type list from Parameter.") @@ -791,8 +797,9 @@ def add_artifact(self, artifact_file_path: str, ignore_on_error: bool = False): except OSError as error: if not ignore_on_error: raise error - logging.warning(f"Artifact path '{artifact_file_path}' for testcase" - f" '{self.__name}' is invalid, will be ignored!") + logging.warning( + f"Artifact path '{artifact_file_path}' for testcase" f" '{self.__name}' is invalid, will be ignored!" + ) return self def get_artifacts(self): @@ -828,11 +835,11 @@ def create_json_repr(self): :see: :class:`Json2AtxRepr` """ result = { - '@type': "testcase", - 'name': self.__name, - 'verdict': self.__verdict.name, - 'description': self.__description, - 'timestamp': self.__timestamp, + "@type": "testcase", + "name": self.__name, + "verdict": self.__verdict.name, + "description": self.__description, + "timestamp": self.__timestamp, "executionTime": self.__execution_time, "parameters": [each.create_json_repr() for each in self.__parameters], "paramSet": self.__param_set, @@ -843,7 +850,7 @@ def create_json_repr(self): "constants": [each.create_json_repr() for each in self.__constants], "environments": [], "artifacts": [each.create_json_repr() for each in self.__artifacts], - } + } if self.__review: result["review"] = self.__review.create_json_repr() return result diff --git a/testguide_report_generator/model/TestCaseFolder.py b/testguide_report_generator/model/TestCaseFolder.py index f3ec928..328ab73 100644 --- a/testguide_report_generator/model/TestCaseFolder.py +++ b/testguide_report_generator/model/TestCaseFolder.py @@ -11,8 +11,7 @@ from typing_extensions import Self from testguide_report_generator.model.TestCase import TestCase from testguide_report_generator.util.Json2AtxRepr import Json2AtxRepr -from testguide_report_generator.util.ValidityChecks import check_name_length, gen_error_msg, \ - validate_testcase +from testguide_report_generator.util.ValidityChecks import check_string_length, validate_testcase class TestCaseFolder(Json2AtxRepr): @@ -32,7 +31,7 @@ def __init__(self, name: str): :param name: name of the testcase folder :type name: str """ - self.__name = check_name_length(name, gen_error_msg("TestCaseFolder", name)) + self.__name = check_string_length(name, 1, 120, "TestCaseFolder", "name") self.__testcases: list[TestCase | TestCaseFolder] = [] def add_testcase(self, testcase: TestCase | Self) -> Self: @@ -62,8 +61,8 @@ def create_json_repr(self): :see: :class:`Json2AtxRepr` """ result = { - '@type': "testcasefolder", - 'name': self.__name, + "@type": "testcasefolder", + "name": self.__name, "testcases": [each.create_json_repr() for each in self.__testcases], - } + } return result diff --git a/testguide_report_generator/model/TestSuite.py b/testguide_report_generator/model/TestSuite.py index 9415596..bfc61dd 100644 --- a/testguide_report_generator/model/TestSuite.py +++ b/testguide_report_generator/model/TestSuite.py @@ -13,7 +13,7 @@ from testguide_report_generator.model.TestCase import TestCase from testguide_report_generator.model.TestCaseFolder import TestCaseFolder from testguide_report_generator.util.Json2AtxRepr import Json2AtxRepr -from testguide_report_generator.util.ValidityChecks import check_name_length, validate_testcase +from testguide_report_generator.util.ValidityChecks import check_string_length, validate_testcase class TestSuite(Json2AtxRepr): @@ -24,7 +24,6 @@ class TestSuite(Json2AtxRepr): """ __test__ = False # pytest ignore - NAME_ERROR_MSG = "The name of the TestSuite must have a length between 1-120 characters." def __init__(self, name: str, timestamp: int): """ @@ -35,7 +34,7 @@ def __init__(self, name: str, timestamp: int): :param timestamp: timestamp in milliseconds :type timestamp: int """ - self.__name = check_name_length(name, self.NAME_ERROR_MSG) + self.__name = check_string_length(name, 1, 120, "TestSuite", "name") self.__timestamp = timestamp self.__testcases: list[Union[TestCase, TestCaseFolder]] = [] diff --git a/testguide_report_generator/schema/schema.json b/testguide_report_generator/schema/schema.json index c3ac2e6..b04e754 100644 --- a/testguide_report_generator/schema/schema.json +++ b/testguide_report_generator/schema/schema.json @@ -27,11 +27,19 @@ "minimum": 0, "description": "A point in time. Expressed as number of milliseconds since the Unix Epoch, i.e. 00:00:00 UTC on January 1st, 1970." }, - "OptionalString": { + "OptionalString64": { "type": [ "string", "null" - ] + ], + "maxLength": 64 + }, + "OptionalString1024": { + "type": [ + "string", + "null" + ], + "maxLength": 1024 }, "OptionalDescription": { "type": [ @@ -45,7 +53,21 @@ "type": "string", "minLength": 1, "maxLength": 120, - "$comment": "ATX standard provides a maximum of 128 characters for the shortnames, we allow 120 characters to buffer the ATX path reference assignment, like e.g. for Testcase_1 or Testcase_42." + "$comment": "ATX standard provides a maximum of 128 characters for the short names, we allow 120 characters to buffer the ATX path reference assignment, like e.g. for Testcase_1 or Testcase_42." + }, + "StrictShortNameString": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "pattern": "^[a-zA-Z]([a-zA-Z0-9]|_[a-zA-Z0-9])*_?$", + "$comment": "Equivalent to an ATX short name." + }, + "AttributeKeyString": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "pattern": "^[-.0-9:A-Z_a-z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]+$", + "$comment": "Derived from XSD's NMTOKEN with added length limit." }, "TestStepNameString": { "type": "string", @@ -133,7 +155,7 @@ "$ref": "#/definitions/Review" }, "paramSet": { - "$ref": "#/definitions/OptionalString" + "$ref": "#/definitions/OptionalString1024" }, "environments": { "type": "array", @@ -178,7 +200,7 @@ "type": "object", "properties": { "key": { - "$ref": "#/definitions/ShortNameString" + "$ref": "#/definitions/AttributeKeyString" }, "value": { "oneOf": [ @@ -203,7 +225,7 @@ "type": "object", "properties": { "key": { - "$ref": "#/definitions/ShortNameString" + "$ref": "#/definitions/StrictShortNameString" }, "value": { "oneOf": [ @@ -253,7 +275,7 @@ "$ref": "#/definitions/Verdict" }, "expectedResult": { - "$ref": "#/definitions/OptionalString" + "$ref": "#/definitions/OptionalString1024" }, "testStepArtifacts": { "type": "array", @@ -288,7 +310,7 @@ "$ref": "#/definitions/Verdict" }, "expectedResult": { - "$ref": "#/definitions/OptionalString" + "$ref": "#/definitions/OptionalString1024" }, "teststeps": { "type": "array", @@ -400,10 +422,10 @@ "maxLength": 512 }, "defect": { - "$ref": "#/definitions/OptionalString" + "$ref": "#/definitions/OptionalString64" }, "defectPriority": { - "$ref": "#/definitions/OptionalString" + "$ref": "#/definitions/OptionalString64" }, "tickets": { "type": "array", @@ -416,7 +438,7 @@ "type": "boolean" }, "customEvaluation": { - "$ref": "#/definitions/OptionalString" + "$ref": "#/definitions/OptionalString64" }, "tags": { "type": "array", @@ -481,7 +503,7 @@ "artifactType": { "type": "string", "enum": [ - "IMAGE" + "IMAGE" ] } }, @@ -489,6 +511,6 @@ "path", "artifactType" ] - } + } } -} \ No newline at end of file +} diff --git a/testguide_report_generator/util/ValidityChecks.py b/testguide_report_generator/util/ValidityChecks.py index 0f85fb2..359696c 100644 --- a/testguide_report_generator/util/ValidityChecks.py +++ b/testguide_report_generator/util/ValidityChecks.py @@ -11,37 +11,29 @@ """ -def check_name_length(name, error_msg): +def check_string_length(value: str, min_len: int, max_len: int, obj: str, prop: str): """ - Checks whether the given name complies to length restrictions according to the schema. - - :param name: name - :type name: str - :param error_msg: the error message pertaining to the error thrown, if 'name' is invalid - :type error_msg: str - :raises: ValueError, if 'name' length is invalid - :return: name, if check was successful + Checks if the given string property of an object has a length within the specified bounds. + :param value: string value to check + :type value: str + :param min_len: minimum allowed length (inclusive) + :type min_len: int + :param max_len: maximum allowed length (inclusive) + :type max_len: int + :param obj: name of the object containing the property + :type obj: str + :param prop: name of the property being checked + :type prop: str + :raises ValueError: if the string length is not within the specified bounds. + :return: The original string value if valid :rtype: str """ - - if len(name) not in range(1, 121): + error_msg = ( + f"The {obj}:{prop} must have a length between {min_len} and {max_len} characters. Was {len(value)} -> {value}" + ) + if len(value) not in range(min_len, max_len + 1): raise ValueError(error_msg) - - return name - - -def gen_error_msg(obj_type, name): - """ - Dynamic error message. - - :param obj_type: type of object to which this error message belongs to. - :type obj_type: str - :param name: name parameter of the object - :type name: str - :return: error message - :rtype: str - """ - return f"The name of the {obj_type} must have a length between 1-120 characters. Name was: {name}" + return value def validate_new_teststep(teststep, stepclass, folderclass): diff --git a/tests/model/test_TestCase.py b/tests/model/test_TestCase.py index 00e4b60..a9ab31f 100644 --- a/tests/model/test_TestCase.py +++ b/tests/model/test_TestCase.py @@ -6,9 +6,17 @@ import json from unittest.mock import patch -from testguide_report_generator.model.TestCase import (TestCase, TestStep, Verdict, Artifact, TestStepArtifact, - TestStepArtifactType, Review) -from testguide_report_generator.util.ValidityChecks import gen_error_msg +from testguide_report_generator.model.TestCase import ( + Attribute, + Constant, + TestCase, + TestStep, + Verdict, + Artifact, + TestStepArtifact, + TestStepArtifactType, + Review, +) class TestArtifact: @@ -50,9 +58,9 @@ def test_correct_json_repr(self, mock, teststep, artifact_path): json_str = json.dumps(teststep.create_json_repr()) assert ( - '{"@type": "teststep", "name": "ts", "description": null, ' - '"verdict": "NONE", "expected_result": "undefined", "testStepArtifacts": ' - '[{"path": "hash/artifact.txt", "artifactType": "IMAGE"}]}' == json_str + '{"@type": "teststep", "name": "ts", "description": null, ' + '"verdict": "NONE", "expected_result": "undefined", "testStepArtifacts": ' + '[{"path": "hash/artifact.txt", "artifactType": "IMAGE"}]}' == json_str ) def test_invalid_verdict(self): @@ -79,12 +87,12 @@ def test_correct_json_repr(self, teststep_folder): json_str = json.dumps(tsf.create_json_repr()) assert ( - '{"@type": "teststepfolder", "name": "tsf", "description": "abc", ' - '"teststeps": [' - '{"@type": "teststep", "name": "ts", "description": null, "verdict": "NONE", "expected_result": ' - '"undefined", "testStepArtifacts": []},' - ' {"@type": "teststep", "name": "ts2", "description": "teststep2", "verdict": "ERROR",' - ' "expected_result": "err", "testStepArtifacts": []}]}' == json_str + '{"@type": "teststepfolder", "name": "tsf", "description": "abc", ' + '"teststeps": [' + '{"@type": "teststep", "name": "ts", "description": null, "verdict": "NONE", "expected_result": ' + '"undefined", "testStepArtifacts": []},' + ' {"@type": "teststep", "name": "ts2", "description": "teststep2", "verdict": "ERROR",' + ' "expected_result": "err", "testStepArtifacts": []}]}' == json_str ) def test_add_teststep_error(self, teststep_folder): @@ -119,6 +127,36 @@ def test_add_constant_error(self, testcase): assert str(error.value) == "Argument constant must be of type Constant." + def test_add_empty_string_constant_error(self, testcase): + with pytest.raises(ValueError) as error: + testcase.add_constant(Constant("", "")) + + assert str(error.value) == f"Constant keys need to be structured following this pattern: {Constant.PATTERN}" + + def test_add_long_string_constant_error(self, testcase): + with pytest.raises(ValueError) as error: + testcase.add_constant(Constant("x" * 129, "")) + + assert ( + str(error.value) + == "The Constant:key must have a length between 1 and 128 characters. Was 129 -> " + "x" * 129 + ) + + def test_add_empty_string_attribute_error(self, testcase): + with pytest.raises(ValueError) as error: + testcase.add_attribute_pair(Attribute("", "")) + + assert str(error.value) == f"Attribute keys need to be structured following this pattern: {Attribute.PATTERN}" + + def test_add_long_string_attribute_error(self, testcase): + with pytest.raises(ValueError) as error: + testcase.add_attribute_pair(Attribute("x" * 256, "")) + + assert ( + str(error.value) + == "The Attribute:key must have a length between 1 and 255 characters. Was 256 -> " + "x" * 256 + ) + def test_set_review_error(self, testcase): with pytest.raises(TypeError) as error: testcase.set_review("") @@ -138,10 +176,13 @@ def test_default(self, input_name): @pytest.mark.parametrize("input_name", ["", "x" * 121]) def test_value_error(self, input_name): verdict = Verdict.FAILED + error_msg = ( + f"The TestCase:name must have a length between 1 and 120 characters. Was {len(input_name)} -> {input_name}" + ) with pytest.raises(ValueError) as e: TestCase(input_name, 0, verdict) - assert str(e.value) == gen_error_msg("TestCase", input_name) + assert str(e.value) == error_msg def test_invalid_verdict(self): with pytest.raises(TypeError) as e: @@ -227,50 +268,70 @@ def test_correct_json_repr(self, attribute): json_str = json.dumps(attribute.create_json_repr()) assert '{"key": "an", "value": "attribute"}' == json_str + class TestReview: def test_correct_json_repr(self, review): json_str = json.dumps(review.create_json_repr()) - assert ('{"comment": "comment", "timestamp": 1670254005, "verdict": "PASSED", "author": "chucknorris", ' - '"summary": null, "defect": null, "defectPriority": null, "tickets": [], "invalidRun": false, ' - '"customEvaluation": null, "tags": [], "contacts": []}') == json_str + assert ( + '{"comment": "comment", "timestamp": 1670254005, "verdict": "PASSED", "author": "chucknorris", ' + '"summary": null, "defect": null, "defectPriority": null, "tickets": [], "invalidRun": false, ' + '"customEvaluation": null, "tags": [], "contacts": []}' + ) == json_str def test_default(self, review): review = Review("Review-Comment", "Reviewer", 1423576765001) json_str = json.dumps(review.create_json_repr()) - assert ('{"comment": "Review-Comment", "timestamp": 1423576765001, "verdict": "PASSED", "author": "Reviewer", ' - '"summary": null, "defect": null, "defectPriority": null, "tickets": [], "invalidRun": false, ' - '"customEvaluation": null, "tags": [], "contacts": []}') == json_str + assert ( + '{"comment": "Review-Comment", "timestamp": 1423576765001, "verdict": "PASSED", "author": "Reviewer", ' + '"summary": null, "defect": null, "defectPriority": null, "tickets": [], "invalidRun": false, ' + '"customEvaluation": null, "tags": [], "contacts": []}' + ) == json_str @pytest.mark.parametrize( - "comment, expected_error", + "comment, error_msg", [ - ("x" * 10001, "Comment length must be between 1 and 10000 characters."), - ("x" * 0, "Comment length must be between 1 and 10000 characters."), - ] + ( + "x" * 10001, + "The Review:comment must have a length between 1 and 10000 characters. Was 10001 -> " + "x" * 10001, + ), + ( + "x" * 0, + "The Review:comment must have a length between 1 and 10000 characters. Was 0 -> ", + ), + ], ) - def test_comment_error(self, comment, expected_error): - with pytest.raises(ValueError, match=expected_error): + def test_comment_error(self, comment, error_msg): + with pytest.raises(ValueError) as e: Review(comment, "Reviewer", 1423576765001) + assert str(e.value) == error_msg def test_author_error(self, review): - with pytest.raises(ValueError, match="Author length cannot exceed 512 characters."): + error_msg = "The Review:author must have a length between 0 and 512 characters. Was 513 -> " + "x" * 513 + with pytest.raises(ValueError) as e: Review("Review-Comment", "x" * 513, 1423576765001) + assert str(e.value) == error_msg def test_set_verdict_error(self, review): with pytest.raises(TypeError, match="Argument 'verdict' must be of type 'Verdict'."): review.set_verdict("invalid_verdict") def test_set_summary_error(self, review): - with pytest.raises(ValueError, match="Summary length cannot exceed 512 characters."): + error_msg = "The Review:summary must have a length between 0 and 512 characters. Was 513 -> " + "x" * 513 + with pytest.raises(ValueError) as e: review.set_summary("x" * 513) + assert str(e.value) == error_msg def test_add_tickets_error(self, review): - with pytest.raises(ValueError, match=r"Ticket length exceeds the maximum allowed \(512 characters\)."): + error_msg = "The Review:ticket must have a length between 0 and 512 characters. Was 513 -> " + "x" * 513 + with pytest.raises(ValueError) as e: review.add_tickets(["x" * 513]) + assert str(e.value) == error_msg def test_add_contacts_error(self, review): - with pytest.raises(ValueError, match=r"Contact length exceeds the maximum allowed \(255 characters\)."): + error_msg = "The Review:contact must have a length between 0 and 255 characters. Was 256 -> " + "x" * 256 + with pytest.raises(ValueError) as e: review.add_contacts(["x" * 256]) + assert str(e.value) == error_msg def test_full_review_object(self, review): review.set_verdict(Verdict.PASSED) diff --git a/tests/model/test_TestSuite.py b/tests/model/test_TestSuite.py index e192a66..b5a63fe 100644 --- a/tests/model/test_TestSuite.py +++ b/tests/model/test_TestSuite.py @@ -7,7 +7,7 @@ from testguide_report_generator.model.TestSuite import TestSuite import json -NAME_ERROR_MSG = "The name of the TestSuite must have a length between 1-120 characters." +NAME_ERROR_MSG = "The TestSuite:name must have a length between 1 and 120 characters." def test_empty(testsuite): @@ -38,7 +38,7 @@ def test_value_error(input_name): with pytest.raises(ValueError) as e: TestSuite(input_name, 0) - assert str(e.value) == NAME_ERROR_MSG + assert str(e.value) == NAME_ERROR_MSG + f" Was {len(input_name)} -> {input_name}" @pytest.mark.parametrize("input_name", ["a", "x" * 120]) diff --git a/tests/resources/invalid.json b/tests/resources/invalid.json index a59bc48..a988866 100644 --- a/tests/resources/invalid.json +++ b/tests/resources/invalid.json @@ -14,8 +14,18 @@ "setupTestSteps": [], "executionTestSteps": [], "teardownTestSteps": [], - "attributes": [], - "constants": [], + "attributes": [ + { + "key": "", + "value": "" + } + ], + "constants": [ + { + "key": "", + "value": "" + } + ], "environments": [], "artifacts": [] }, diff --git a/tests/resources/testcase.json b/tests/resources/testcase.json index 1ccdd58..de7ea51 100644 --- a/tests/resources/testcase.json +++ b/tests/resources/testcase.json @@ -133,8 +133,8 @@ "value": "const" }, { - "key": "", - "value": "" + "key": "key", + "value": "value" }, { "key": "const_key", diff --git a/tests/resources/testsuite.json b/tests/resources/testsuite.json index 31281d0..fb65fd9 100644 --- a/tests/resources/testsuite.json +++ b/tests/resources/testsuite.json @@ -181,8 +181,8 @@ "value": "const" }, { - "key": "", - "value": "" + "key": "key", + "value": "value" }, { "key": "const_key", diff --git a/tests/util/test_ValidityChecks.py b/tests/util/test_ValidityChecks.py index aa29b85..e0ae503 100644 --- a/tests/util/test_ValidityChecks.py +++ b/tests/util/test_ValidityChecks.py @@ -4,34 +4,25 @@ import pytest -from testguide_report_generator.util.ValidityChecks import gen_error_msg, check_name_length, validate_new_teststep, \ - validate_testcase +from testguide_report_generator.util.ValidityChecks import check_string_length, validate_new_teststep, validate_testcase from testguide_report_generator.model.TestCase import TestCase, TestStep, TestStepFolder from testguide_report_generator.model.TestCaseFolder import TestCaseFolder @pytest.mark.parametrize("name", ["a", "x" * 120]) -def test_check_name_length(name): - assert name == check_name_length(name, "") +def test_check_string_length(name): + assert name == check_string_length(name, 1, 120, "TestCase", "name") @pytest.mark.parametrize("name", ["", "x" * 121]) -def test_check_name_length_error(name): - error_msg = "bad name" +def test_check_string_length_error(name): + error_msg = f"The TestCase:name must have a length between 1 and 120 characters. Was {len(name)} -> {name}" with pytest.raises(ValueError) as e: - check_name_length(name, error_msg) + check_string_length(name, 1, 120, "TestCase", "name") assert str(e.value) == error_msg -def test_error_msg(): - obj_type = "Type" - name = "Fred" - - assert "The name of the Type must have a length between 1-120 characters. Name was: Fred" == \ - gen_error_msg(obj_type, name) - - def test_teststep_checks(teststep_folder): assert validate_new_teststep(teststep_folder, TestStep, TestStepFolder)