from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union from models.category import Category from models.generation_strategy import GenerationStrategy from models.openapi import Operation from models.test_case import Case if TYPE_CHECKING: from models.affected_element import AffectedElement class EnhancedOperation: def __init__( self, path: str, method: str, operation: Operation, available_methods: List[str], coefficient_dict: Dict[str, int], cases: List[Dict[str, Any]], ): self.path = path self.method = method self.operation = operation self.available_methods = available_methods self.coefficient_dict = coefficient_dict self.typical_positive_case = None for c in cases: # cases now dict of cases without multi-step cases case = Case.from_dict(c) if case.type == "Positive": self.typical_positive_case = case def __getattr__(self, name: str) -> Any: if name not in [ "path", "method", "operation", "coefficient_dict", "typical_positive_case", "available_methods", ]: return getattr(self.operation, name) else: return getattr(self, name) def get_path_resources(self) -> List[str]: # Remove the operation part after the colon (if exists) path = self.path.split(":")[0] # Split the path into parts parts = path.split("/") # Remove empty parts and parts enclosed in curly braces resources = [ part for part in parts if part and not (part.startswith("{") and part.endswith("}")) ] # Return the count of unique resources return list(set(resources)) def is_element_required(self, element: "AffectedElement") -> bool: if isinstance(element.reference.required, bool): return element.reference.required else: # check the parent key if element.key.startswith( "body." ): # element.key == 'body' need not be checked in response_predictor.py parts = element.key.split(".") schema = self.operation._get_parameters("body")[ 0 ].schema # body exists and only one for i, part in enumerate(parts[1:], 1): if part.endswith("[]"): part = part[:-2] if schema.properties[part].type != "array": # type: ignore return False if i == len(parts) - 1: # return schema.minItems and schema.minItems > 0 return True schema = schema.properties[part].items # type: ignore else: # if ( # schema.type != "object" # type: ignore # or not schema.properties # type: ignore # or part not in schema.properties # type: ignore # ): # return False is_required = schema.required is not None and part in schema.required # type: ignore if i == len(parts) - 1: return is_required schema = schema.properties[part] # type: ignore return False # TODO: What about parent is not required but child is required? else: raise NotImplementedError("Only body elements are supported currently") @classmethod def from_openapi( cls, path: str, method: str, operation: Operation, available_methods: List[str], coefficient_dict: Dict[str, int], cases: List[Dict[str, Any]], ) -> "EnhancedOperation": return cls(path, method, operation, available_methods, coefficient_dict, cases) def should_generate_case( self, category: Category, strategy: GenerationStrategy, allowlist: Optional[Dict[str, Dict[str, Union[List[str], str]]]] = None, use_allowlist: bool = False, ) -> bool: if not use_allowlist or allowlist is None: return True def check_rule(rule_type: str, value: str) -> bool: if rule_type not in allowlist: return True rule = allowlist[rule_type] allow = rule.get("allow") deny = rule.get("deny") if allow is not None: if allow == "all" or (isinstance(allow, list) and value in allow): return True return False if deny is not None: if deny == "all" or (isinstance(deny, list) and value in deny): return False return True return True operation_id_allowed = check_rule("operation_ids", self.operationId) category_allowed = check_rule("categories", category.name) strategy_allowed = check_rule("generation_strategies", strategy.name) return operation_id_allowed and category_allowed and strategy_allowed def generate_cases( self, dry_run: bool = False, use_allowlist: bool = False, allowlist: Optional[dict] = None, ) -> List[Dict[str, Any]]: cases = [] if self.typical_positive_case is not None: cases.append(self.typical_positive_case.__dict__) for category in Category: # if category.value.position == "environment": # continue affected_elements = category.value.get_affected_elements( self ) # .operation) for element in affected_elements: for strategy in element.generation_strategies: if dry_run and not use_allowlist: cases.extend( strategy.value.handler( self, category, element, dry_run=dry_run, llm=False ) ) if dry_run and use_allowlist: if self.should_generate_case( category, strategy, allowlist=allowlist, use_allowlist=use_allowlist, ): cases.extend( strategy.value.handler( self, category, element, dry_run=dry_run, llm=False ) ) if not dry_run and not use_allowlist: if self.typical_positive_case is not None: cases.extend( strategy.value.handler( self, category, element, dry_run=dry_run, llm=True ) ) else: cases.extend( strategy.value.handler( self, category, element, dry_run=True, llm=True ) ) if not dry_run and use_allowlist: if ( self.typical_positive_case is not None and self.should_generate_case( category, strategy, allowlist=allowlist, use_allowlist=use_allowlist, ) ): cases.extend( strategy.value.handler( self, category, element, dry_run=dry_run, llm=True ) ) else: cases.extend( strategy.value.handler( self, category, element, dry_run=True, llm=True ) ) return cases