From 927b5ed9143f01b41db206f7698e3928f26259ea Mon Sep 17 00:00:00 2001 From: ZB-io <58132646+chaitra1403@users.noreply.github.com> Date: Wed, 26 Nov 2025 09:53:21 +0000 Subject: [PATCH 1/2] Add API Tests (Pytest Framework, Claude AI) generated by RoostGPT Using AI Model claude-opus-4-5-20251101 --- Roost-README.md | 25 ++ requirements-roost.txt | 62 +++ .../api.json | 1 + .../auth_register.json | 74 +++ .../config.yml | 22 + .../conftest.py | 205 +++++++++ .../test_auth_register_post.py | 425 ++++++++++++++++++ .../validator.py | 202 +++++++++ 8 files changed, 1016 insertions(+) create mode 100644 Roost-README.md create mode 100644 requirements-roost.txt create mode 100644 tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/api.json create mode 100644 tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/auth_register.json create mode 100644 tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/config.yml create mode 100644 tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/conftest.py create mode 100644 tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/test_auth_register_post.py create mode 100644 tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/validator.py diff --git a/Roost-README.md b/Roost-README.md new file mode 100644 index 000000000..dd5bcc8d8 --- /dev/null +++ b/Roost-README.md @@ -0,0 +1,25 @@ + +# RoostGPT generated pytest code for API Testing + +RoostGPT generats code in `tests` folder within given project path. +Dependency file i.e. `requirements-roost.txt` is also created in the given project path + +Below are the sample steps to run the generated tests. Sample commands contains use of package manager i.e. `uv`. Alternatively python and pip can be used directly. +1. ( Optional ) Create virtual Env . +2. Install dependencies +``` +uv venv // Create virtual Env +uv pip install -r requirements-roost.txt // Install all dependencies + +``` + +Test configurations and test_data is loaded from config.yml. e.g. API HOST, auth, common path parameters of endpoint. +Either set defalt value in this config.yml file OR use ENV. e.g. export API_HOST="https://example.com/api/v2" + +Once configuration values are set, use below commands to run the tests. +``` +// Run generated tests +uv run pytest -m smoke // Run only smoke tests +uv run pytest -s tests/generated-test.py // Run specific test file +``` + \ No newline at end of file diff --git a/requirements-roost.txt b/requirements-roost.txt new file mode 100644 index 000000000..de7bd70f6 --- /dev/null +++ b/requirements-roost.txt @@ -0,0 +1,62 @@ + +agentocr +beautifulsoup4 +boto3 +botocore +chromedriver_binary +click +django_recaptcha +dlib +dnspython +emoji +exifread +ffmpeg +ffpyplayer +Flask +flask_sqlalchemy +geopy +googletrans +gtts +img2pdf +jsonschema +keras +lxml +matplotlib +nltk +numpy +pandas +pil +Pillow +psutil +pyautogui +pycryptodome +pycryptodomex +PyDictionary +pygame +pymysql +pynotifier +PyPDF2 +pytest +python-telegram-bot +pyttsx3 +pywhatkit +PyYAML +qrcode +referencing +Requests +rich +scikit_learn +selenium +sumeval +sumy +tensorflow +textblob +tqdm +tweepy +urllib3 +webdriver_manager +wechaty +wechaty_puppet +wikipedia +wordcloud +xmltodict \ No newline at end of file diff --git a/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/api.json b/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/api.json new file mode 100644 index 000000000..ee76c9ac7 --- /dev/null +++ b/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/api.json @@ -0,0 +1 @@ +{"openapi":"3.0.3","info":{"version":"1.0.0","title":"Guice Grizzly Jersey Openapi Swagger Example API","description":"OpenAPI swagger configuration example in sample project that uses Guice, Grizzly, Jersey. This is an extended sample API (users, products, orders, auth) for demonstration purposes.","contact":{"email":"jayeshmaheshpatel@gmail.com"},"license":{"name":"MIT License","url":"https://en.wikipedia.org/wiki/MIT_License"}},"servers":[{"url":"http://localhost:8080/OpenAPIExample/","description":"Guice Grizzly Jersey Openapi Swagger Example API server"}],"tags":[{"name":"auth","description":"Authentication endpoints (register, login)"},{"name":"users","description":"User management"},{"name":"products","description":"Product catalog"},{"name":"orders","description":"Order processing"}],"paths":{"/auth/register":{"post":{"tags":["auth"],"summary":"Register a new user","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCreate"},"examples":{"newUser":{"summary":"New user example","value":{"username":"jdoe","email":"jdoe@example.com","password":"P@ssw0rd!"}}}}}},"responses":{"201":{"description":"User created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/auth/login":{"post":{"tags":["auth"],"summary":"Authenticate user and return JWT","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"},"examples":{"login":{"summary":"Login example","value":{"username":"jdoe","password":"P@ssw0rd!"}}}}}},"responses":{"200":{"description":"Authentication successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/users":{"get":{"tags":["users"],"summary":"List users (paginated)","parameters":[{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1},"description":"Page number"},{"name":"size","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"description":"Page size"}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"A paginated list of users","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedUsers"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["users"],"summary":"Create a user","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCreate"}}}},"responses":{"201":{"description":"User created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/users/{userId}":{"get":{"tags":["users"],"summary":"Get user by ID","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"User found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["users"],"summary":"Update user","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserUpdate"}}}},"responses":{"200":{"description":"User updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["users"],"summary":"Delete user","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"security":[{"bearerAuth":[]}],"responses":{"204":{"description":"User deleted (no content)"},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/products":{"get":{"tags":["products"],"summary":"List products (paginated)","parameters":[{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"size","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"q","in":"query","schema":{"type":"string"},"description":"Search query (name/description)"}],"responses":{"200":{"description":"Paginated products","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedProducts"}}}}}},"post":{"tags":["products"],"summary":"Create a product","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductCreate"}}}},"responses":{"201":{"description":"Product created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Product"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/products/{productId}":{"get":{"tags":["products"],"summary":"Get product by ID","parameters":[{"name":"productId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Product found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Product"}}}},"404":{"description":"Product not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["products"],"summary":"Update product","security":[{"bearerAuth":[]}],"parameters":[{"name":"productId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductUpdate"}}}},"responses":{"200":{"description":"Product updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Product"}}}},"404":{"description":"Product not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["products"],"summary":"Delete product","security":[{"bearerAuth":[]}],"parameters":[{"name":"productId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Product deleted"},"404":{"description":"Product not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/orders":{"get":{"tags":["orders"],"summary":"List orders for authenticated user","security":[{"bearerAuth":[]}],"parameters":[{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"size","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"Paginated orders","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedOrders"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["orders"],"summary":"Create an order","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCreate"}}}},"responses":{"201":{"description":"Order created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/orders/{orderId}":{"get":{"tags":["orders"],"summary":"Get order by ID (owner or admin only)","security":[{"bearerAuth":[]}],"parameters":[{"name":"orderId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Order found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Order not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\""}},"schemas":{"UserCreate":{"type":"object","required":["username","email","password"],"properties":{"username":{"type":"string","example":"jdoe"},"email":{"type":"string","format":"email","example":"jdoe@example.com"},"password":{"type":"string","format":"password","example":"P@ssw0rd!"}}},"UserUpdate":{"type":"object","properties":{"username":{"type":"string"},"email":{"type":"string","format":"email"},"firstName":{"type":"string"},"lastName":{"type":"string"}}},"UserResponse":{"type":"object","properties":{"id":{"type":"string","example":"user_12345"},"username":{"type":"string","example":"jdoe"},"email":{"type":"string","format":"email","example":"jdoe@example.com"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"PaginatedUsers":{"type":"object","properties":{"page":{"type":"integer"},"size":{"type":"integer"},"total":{"type":"integer"},"items":{"type":"array","items":{"$ref":"#/components/schemas/UserResponse"}}}},"LoginRequest":{"type":"object","required":["username","password"],"properties":{"username":{"type":"string"},"password":{"type":"string","format":"password"}}},"AuthResponse":{"type":"object","properties":{"token":{"type":"string","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."},"expiresIn":{"type":"integer","example":3600}}},"Product":{"type":"object","properties":{"id":{"type":"string","example":"prod_12345"},"name":{"type":"string","example":"Wireless Mouse"},"description":{"type":"string"},"price":{"type":"number","format":"float","example":29.99},"currency":{"type":"string","example":"USD"},"available":{"type":"boolean","example":true},"createdAt":{"type":"string","format":"date-time"}}},"ProductCreate":{"type":"object","required":["name","price"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"price":{"type":"number","format":"float"},"currency":{"type":"string","default":"USD"},"available":{"type":"boolean","default":true}}},"ProductUpdate":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"price":{"type":"number","format":"float"},"currency":{"type":"string"},"available":{"type":"boolean"}}},"PaginatedProducts":{"type":"object","properties":{"page":{"type":"integer"},"size":{"type":"integer"},"total":{"type":"integer"},"items":{"type":"array","items":{"$ref":"#/components/schemas/Product"}}}},"OrderItem":{"type":"object","required":["productId","quantity"],"properties":{"productId":{"type":"string"},"quantity":{"type":"integer","minimum":1},"unitPrice":{"type":"number","format":"float"}}},"OrderCreate":{"type":"object","required":["items","shippingAddress"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/OrderItem"}},"shippingAddress":{"$ref":"#/components/schemas/Address"},"notes":{"type":"string"}}},"Order":{"type":"object","properties":{"id":{"type":"string","example":"order_98765"},"userId":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/OrderItem"}},"subtotal":{"type":"number","format":"float"},"shipping":{"type":"number","format":"float"},"total":{"type":"number","format":"float"},"currency":{"type":"string","example":"USD"},"status":{"type":"string","example":"PENDING"},"shippingAddress":{"$ref":"#/components/schemas/Address"},"createdAt":{"type":"string","format":"date-time"}}},"PaginatedOrders":{"type":"object","properties":{"page":{"type":"integer"},"size":{"type":"integer"},"total":{"type":"integer"},"items":{"type":"array","items":{"$ref":"#/components/schemas/Order"}}}},"Address":{"type":"object","properties":{"line1":{"type":"string"},"line2":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"postalCode":{"type":"string"},"country":{"type":"string"}}},"Error":{"type":"object","properties":{"code":{"type":"integer","example":400},"message":{"type":"string","example":"Invalid request"},"details":{"type":"array","items":{"type":"string"}}}}}},"security":[{"bearerAuth":[]}],"externalDocs":{"description":"Project README / docs","url":"http://localhost:8080/OpenAPIExample/docs"}} \ No newline at end of file diff --git a/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/auth_register.json b/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/auth_register.json new file mode 100644 index 000000000..268bd786c --- /dev/null +++ b/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/auth_register.json @@ -0,0 +1,74 @@ +[ + { + "username": "jdoe", + "email": "jdoe@example.com", + "statusCode": 201, + "scenario": "Successful responses: Created" + }, + { + "username": "msmith", + "email": "msmith@company.org", + "firstName": "Michael", + "lastName": "Smith", + "statusCode": 201, + "scenario": "Successful responses: Created" + }, + { + "username": "agarcia", + "email": "agarcia@domain.net", + "firstName": "Ana", + "lastName": "Garcia", + "statusCode": 201, + "scenario": "Successful responses: Created" + }, + { + "email": "missing_username@test.com", + "firstName": "Test", + "lastName": "User", + "statusCode": 400, + "scenario": "Client error responses: Bad Request" + }, + { + "username": "noEmailUser", + "firstName": "John", + "lastName": "Doe", + "statusCode": 400, + "scenario": "Client error responses: Bad Request" + }, + { + "username": "invalidEmail", + "email": "not-a-valid-email", + "firstName": "Invalid", + "lastName": "Email", + "statusCode": 400, + "scenario": "Client error responses: Bad Request" + }, + { + "username": "", + "email": "empty_username@test.com", + "statusCode": 400, + "scenario": "Client error responses: Bad Request" + }, + { + "username": "specialChars!@#", + "email": "special@test.com", + "firstName": "Special", + "lastName": "Characters", + "statusCode": 400, + "scenario": "Client error responses: Bad Request" + }, + { + "username": "validuser123", + "email": "validuser123@email.com", + "statusCode": 201, + "scenario": "Successful responses: Created" + }, + { + "username": "fullprofile", + "email": "fullprofile@example.com", + "firstName": "Full", + "lastName": "Profile", + "statusCode": 201, + "scenario": "Successful responses: Created" + } +] \ No newline at end of file diff --git a/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/config.yml b/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/config.yml new file mode 100644 index 000000000..eabefe512 --- /dev/null +++ b/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/config.yml @@ -0,0 +1,22 @@ + +# This config.yml contains user provided data for api testing. Allows to define values here or use ENV to load values. e.g. ENV[API_HOST] = "https://exampl2.com" +# api: +# host: "${API_HOST:-https://example.com/api/v2}" # includes base path +# auth: +# api_key: "${API_KEY:-}" +# api_key_header: "${KEYNAME:-DefaultValue}" # openapi.spec.security.KEY_NAME +# basic_auth: "${username:-}:${password:-}" +# test_data: +# id: "${TEST_ID:-282739-1238371-219393-2833}" # Any test data key value pair e.g. GET /api/v1/cart/:id +# context-id: "${TEST_context-id:-}" # GET /api/v1/{context-id}/summary + + + +api: + host: "${API_HOST:-http://localhost:8080/OpenAPIExample/}" +auth: + bearerAuth: "${BEARER_TOKEN:-}" +test_data: + userId: "${TEST_userId:-}" + productId: "${TEST_productId:-}" + orderId: "${TEST_orderId:-}" diff --git a/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/conftest.py b/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/conftest.py new file mode 100644 index 000000000..318c118c2 --- /dev/null +++ b/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/conftest.py @@ -0,0 +1,205 @@ +import os +import re +import pytest +import requests +import yaml +from pathlib import Path +from requests.adapters import HTTPAdapter +from urllib3.util.retry import Retry + + +def _expand_env_in_string(value): + """Expand environment variables in string with format ${VAR:-default}.""" + if not isinstance(value, str): + return value + + pattern = r'\$\{([^}]+)\}' + + def replace_env(match): + content = match.group(1) + if ':-' in content: + var_name, default = content.split(':-', 1) + else: + var_name = content + default = '' + return os.environ.get(var_name, default) + + return re.sub(pattern, replace_env, value) + + +def _expand_env_in_dict(data): + """Recursively expand environment variables in dictionary.""" + if isinstance(data, dict): + return {key: _expand_env_in_dict(value) for key, value in data.items()} + elif isinstance(data, list): + return [_expand_env_in_dict(item) for item in data] + elif isinstance(data, str): + return _expand_env_in_string(data) + return data + + +class APIClient: + """Simple API client for making HTTP requests.""" + + def __init__(self, base_url, auth_token=None, timeout=30): + self.base_url = base_url.strip().rstrip('/') + self.auth_token = auth_token + self.timeout = timeout + self.session = self._create_session() + + def _create_session(self): + """Create a session with retry configuration.""" + session = requests.Session() + + retry_strategy = Retry( + total=3, + backoff_factor=1, + status_forcelist=[429, 500, 502, 503, 504], + allowed_methods=["HEAD", "GET", "OPTIONS", "POST", "PUT", "DELETE", "PATCH"] + ) + + adapter = HTTPAdapter(max_retries=retry_strategy) + session.mount("http://", adapter) + session.mount("https://", adapter) + + return session + + def _get_headers(self, headers=None): + """Build headers with authentication.""" + default_headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + + if self.auth_token: + default_headers['Authorization'] = f'Bearer {self.auth_token}' + + if headers: + default_headers.update(headers) + + return default_headers + + def _build_url(self, endpoint): + """Build full URL from endpoint.""" + endpoint = endpoint.strip().lstrip('/') + return f"{self.base_url}/{endpoint}" + + def make_request(self, endpoint, params=None, headers=None, method='GET', json=None, data=None): + """Make an HTTP request.""" + url = self._build_url(endpoint) + request_headers = self._get_headers(headers) + + response = self.session.request( + method=method.upper(), + url=url, + params=params, + headers=request_headers, + json=json, + data=data, + timeout=self.timeout + ) + + return response + + def get(self, endpoint, headers=None, params=None): + """Make a GET request.""" + return self.make_request(endpoint, params=params, headers=headers, method='GET') + + def post(self, endpoint, json=None, data=None, headers=None, params=None): + """Make a POST request.""" + return self.make_request(endpoint, params=params, headers=headers, method='POST', json=json, data=data) + + def put(self, endpoint, json=None, data=None, headers=None, params=None): + """Make a PUT request.""" + return self.make_request(endpoint, params=params, headers=headers, method='PUT', json=json, data=data) + + def patch(self, endpoint, json=None, data=None, headers=None, params=None): + """Make a PATCH request.""" + return self.make_request(endpoint, params=params, headers=headers, method='PATCH', json=json, data=data) + + def delete(self, endpoint, headers=None, params=None): + """Make a DELETE request.""" + return self.make_request(endpoint, params=params, headers=headers, method='DELETE') + + +def pytest_configure(config): + """Register custom markers.""" + config.addinivalue_line( + "markers", "smoke: For all success scenarios" + ) + + +@pytest.fixture(scope="session") +def config(): + """Load configuration from config.yml file.""" + config_path = os.path.join(os.path.dirname(__file__), 'config.yml') + + if not os.path.exists(config_path): + raise FileNotFoundError(f"Configuration file not found: {config_path}") + + try: + with open(config_path, 'r') as f: + raw_config = yaml.safe_load(f) + except yaml.YAMLError as e: + raise ValueError(f"Error parsing config.yml: {e}") + + if raw_config is None: + raise ValueError("Configuration file is empty") + + expanded_config = _expand_env_in_dict(raw_config) + + return expanded_config + + +@pytest.fixture(scope="session") +def api_host(config): + """Get API host from configuration.""" + return config.get('api', {}).get('host', '').strip() + + +@pytest.fixture(scope="session") +def auth(config): + """Get auth configuration.""" + return config.get('auth', {}) + + +@pytest.fixture(scope="session") +def bearer_token(auth): + """Get bearer token from auth configuration.""" + return auth.get('bearerAuth', '') + + +@pytest.fixture(scope="session") +def api_client(api_host, bearer_token): + """Create API client instance.""" + client = APIClient( + base_url=api_host, + auth_token=bearer_token if bearer_token else None, + timeout=30 + ) + yield client + client.session.close() + + +@pytest.fixture(scope="session") +def config_test_data(config): + """Load test_data from config fixture.""" + return config.get('test_data', {}) + + +@pytest.fixture(scope="session") +def test_user_id(config_test_data): + """Get test userId from test_data.""" + return config_test_data.get('userId', '') + + +@pytest.fixture(scope="session") +def test_product_id(config_test_data): + """Get test productId from test_data.""" + return config_test_data.get('productId', '') + + +@pytest.fixture(scope="session") +def test_order_id(config_test_data): + """Get test orderId from test_data.""" + return config_test_data.get('orderId', '') diff --git a/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/test_auth_register_post.py b/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/test_auth_register_post.py new file mode 100644 index 000000000..5b07b54bf --- /dev/null +++ b/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/test_auth_register_post.py @@ -0,0 +1,425 @@ +# ********RoostGPT******** + +# Test generated by RoostGPT for test API-test-py01 using AI Type Claude AI and AI Model claude-opus-4-5-20251101 +# +# Test file generated for /auth/register_post for http method type POST +# RoostTestHash=5d30eb1c66 +# +# + +# ********RoostGPT******** +""" +Test Suite for POST /auth/register API Endpoint + +This module contains comprehensive pytest tests for the user registration endpoint. +Tests cover successful registration, validation errors, and edge cases. + +Setup: + 1. Ensure config.yml is properly configured with API host + 2. Place auth_register.json test data file in the same directory + 3. Run tests: pytest test_auth_register.py -v + 4. Run smoke tests only: pytest test_auth_register.py -v -m smoke +""" + +import json +import os +import pytest +from pathlib import Path +from validator import SwaggerSchemaValidator + + +# Load test data from JSON file +def load_test_data(): + """Load test data from auth_register.json file.""" + test_data_path = Path(__file__).parent / "auth_register.json" + with open(test_data_path, "r") as f: + return json.load(f) + + +ENDPOINT = "/auth/register" +HTTP_METHOD = "post" +API_SPEC_PATH = Path(__file__).parent / "api.json" + + +@pytest.fixture(scope="module") +def schema_validator(): + """Initialize SwaggerSchemaValidator with API spec.""" + return SwaggerSchemaValidator(str(API_SPEC_PATH)) + + +@pytest.fixture(scope="module") +def endpoint_test_data(): + """Load endpoint-specific test data from JSON file.""" + return load_test_data() + + +class TestAuthRegisterSuccess: + """Test class for successful user registration scenarios.""" + + @pytest.mark.smoke + def test_register_user_with_required_fields_only( + self, api_client, config_test_data, schema_validator + ): + """ + Test successful user registration with only required fields. + + Required fields: username, email, password + Expected: 201 Created response with user data + """ + # Build request payload with required fields from config + payload = { + "username": config_test_data.get("username", "testuser"), + "email": config_test_data.get("email", "testuser@example.com"), + "password": config_test_data.get("password", "P@ssw0rd!") + } + + # Make API request + response = api_client.post(ENDPOINT, json=payload) + + # Assert status code + assert response.status_code == 201, ( + f"Expected 201, got {response.status_code}. Response: {response.text}" + ) + + # Validate response schema + validation_result = schema_validator.validate_schema_by_response( + ENDPOINT, HTTP_METHOD, "201", response + ) + assert validation_result["valid"], ( + f"Schema validation failed: {validation_result.get('message', 'Unknown error')}" + ) + + # Validate response contains expected fields + response_data = response.json() + assert "id" in response_data, "Response should contain 'id' field" + assert "username" in response_data, "Response should contain 'username' field" + assert "email" in response_data, "Response should contain 'email' field" + + @pytest.mark.smoke + @pytest.mark.parametrize( + "test_case", + load_test_data(), + ids=lambda tc: tc.get("scenario", "unknown") + ) + def test_register_user_from_test_data( + self, api_client, config_test_data, schema_validator, test_case + ): + """ + Table-driven test for user registration using test data from JSON file. + + Iterates through all test cases defined in auth_register.json + """ + # Build payload from test case, using config_test_data as fallback for password + payload = { + "username": test_case.get("username"), + "email": test_case.get("email"), + "password": config_test_data.get("password", "P@ssw0rd!") + } + + # Add optional fields if present in test case + if "firstName" in test_case: + payload["firstName"] = test_case["firstName"] + if "lastName" in test_case: + payload["lastName"] = test_case["lastName"] + + # Make API request + response = api_client.post(ENDPOINT, json=payload) + + # Assert expected status code from test data + expected_status = test_case.get("statusCode", 201) + assert response.status_code == expected_status, ( + f"Scenario: {test_case.get('scenario')} - " + f"Expected {expected_status}, got {response.status_code}. " + f"Response: {response.text}" + ) + + # Validate response schema for successful responses + if expected_status == 201: + validation_result = schema_validator.validate_schema_by_response( + ENDPOINT, HTTP_METHOD, "201", response + ) + assert validation_result["valid"], ( + f"Schema validation failed for scenario '{test_case.get('scenario')}': " + f"{validation_result.get('message', 'Unknown error')}" + ) + + +class TestAuthRegisterValidation: + """Test class for request validation and error scenarios.""" + + def test_register_missing_username( + self, api_client, config_test_data, schema_validator + ): + """ + Test registration fails when username is missing. + + Expected: 400 Bad Request + """ + payload = { + "email": config_test_data.get("email", "test@example.com"), + "password": config_test_data.get("password", "P@ssw0rd!") + } + + response = api_client.post(ENDPOINT, json=payload) + + # Missing required field should return 400 + assert response.status_code == 400, ( + f"Expected 400 for missing username, got {response.status_code}. " + f"Response: {response.text}" + ) + + # Validate error response schema + validation_result = schema_validator.validate_schema_by_response( + ENDPOINT, HTTP_METHOD, "400", response + ) + assert validation_result["valid"], ( + f"Error response schema validation failed: " + f"{validation_result.get('message', 'Unknown error')}" + ) + + def test_register_missing_email( + self, api_client, config_test_data, schema_validator + ): + """ + Test registration fails when email is missing. + + Expected: 400 Bad Request + """ + payload = { + "username": config_test_data.get("username", "testuser"), + "password": config_test_data.get("password", "P@ssw0rd!") + } + + response = api_client.post(ENDPOINT, json=payload) + + assert response.status_code == 400, ( + f"Expected 400 for missing email, got {response.status_code}. " + f"Response: {response.text}" + ) + + validation_result = schema_validator.validate_schema_by_response( + ENDPOINT, HTTP_METHOD, "400", response + ) + assert validation_result["valid"], ( + f"Error response schema validation failed: " + f"{validation_result.get('message', 'Unknown error')}" + ) + + def test_register_missing_password( + self, api_client, config_test_data, schema_validator + ): + """ + Test registration fails when password is missing. + + Expected: 400 Bad Request + """ + payload = { + "username": config_test_data.get("username", "testuser"), + "email": config_test_data.get("email", "test@example.com") + } + + response = api_client.post(ENDPOINT, json=payload) + + assert response.status_code == 400, ( + f"Expected 400 for missing password, got {response.status_code}. " + f"Response: {response.text}" + ) + + validation_result = schema_validator.validate_schema_by_response( + ENDPOINT, HTTP_METHOD, "400", response + ) + assert validation_result["valid"], ( + f"Error response schema validation failed: " + f"{validation_result.get('message', 'Unknown error')}" + ) + + def test_register_empty_request_body( + self, api_client, schema_validator + ): + """ + Test registration fails with empty request body. + + Expected: 400 Bad Request + """ + response = api_client.post(ENDPOINT, json={}) + + assert response.status_code == 400, ( + f"Expected 400 for empty body, got {response.status_code}. " + f"Response: {response.text}" + ) + + def test_register_invalid_email_format( + self, api_client, config_test_data, schema_validator + ): + """ + Test registration fails with invalid email format. + + Expected: 400 Bad Request + """ + payload = { + "username": config_test_data.get("username", "testuser"), + "email": "invalid-email-format", + "password": config_test_data.get("password", "P@ssw0rd!") + } + + response = api_client.post(ENDPOINT, json=payload) + + assert response.status_code == 400, ( + f"Expected 400 for invalid email format, got {response.status_code}. " + f"Response: {response.text}" + ) + + @pytest.mark.parametrize( + "invalid_payload,description", + [ + ({"username": "", "email": "test@example.com", "password": "P@ssw0rd!"}, "empty username"), + ({"username": "user", "email": "", "password": "P@ssw0rd!"}, "empty email"), + ({"username": "user", "email": "test@example.com", "password": ""}, "empty password"), + ({"username": None, "email": "test@example.com", "password": "P@ssw0rd!"}, "null username"), + ({"username": "user", "email": None, "password": "P@ssw0rd!"}, "null email"), + ({"username": "user", "email": "test@example.com", "password": None}, "null password"), + ], + ids=[ + "empty_username", + "empty_email", + "empty_password", + "null_username", + "null_email", + "null_password" + ] + ) + def test_register_invalid_field_values( + self, api_client, schema_validator, invalid_payload, description + ): + """ + Test registration fails with invalid field values. + + Boundary testing for empty and null values in required fields. + Expected: 400 Bad Request + """ + response = api_client.post(ENDPOINT, json=invalid_payload) + + assert response.status_code == 400, ( + f"Expected 400 for {description}, got {response.status_code}. " + f"Response: {response.text}" + ) + + +class TestAuthRegisterEdgeCases: + """Test class for edge cases and boundary conditions.""" + + def test_register_with_extra_fields( + self, api_client, config_test_data, schema_validator + ): + """ + Test registration with additional unexpected fields. + + API should either ignore extra fields or return appropriate response. + """ + payload = { + "username": config_test_data.get("username", "testuser"), + "email": config_test_data.get("email", "test@example.com"), + "password": config_test_data.get("password", "P@ssw0rd!"), + "unexpectedField": "unexpected_value", + "anotherExtra": 12345 + } + + response = api_client.post(ENDPOINT, json=payload) + + # Should either succeed (201) or reject with 400 + assert response.status_code in [201, 400], ( + f"Expected 201 or 400, got {response.status_code}. " + f"Response: {response.text}" + ) + + def test_register_username_with_special_characters( + self, api_client, config_test_data, schema_validator + ): + """ + Test registration with special characters in username. + + Tests boundary condition for username format validation. + """ + payload = { + "username": "user@#$%^&*()", + "email": config_test_data.get("email", "test@example.com"), + "password": config_test_data.get("password", "P@ssw0rd!") + } + + response = api_client.post(ENDPOINT, json=payload) + + # Response depends on API validation rules + assert response.status_code in [201, 400], ( + f"Expected 201 or 400, got {response.status_code}. " + f"Response: {response.text}" + ) + + def test_register_very_long_username( + self, api_client, config_test_data, schema_validator + ): + """ + Test registration with very long username. + + Boundary testing for maximum field length. + """ + payload = { + "username": "a" * 1000, + "email": config_test_data.get("email", "test@example.com"), + "password": config_test_data.get("password", "P@ssw0rd!") + } + + response = api_client.post(ENDPOINT, json=payload) + + # Should either succeed or return 400 for length validation + assert response.status_code in [201, 400], ( + f"Expected 201 or 400 for long username, got {response.status_code}. " + f"Response: {response.text}" + ) + + def test_register_with_unicode_characters( + self, api_client, config_test_data, schema_validator + ): + """ + Test registration with unicode characters in fields. + + Tests internationalization support. + """ + payload = { + "username": "用户名测试", + "email": config_test_data.get("email", "test@example.com"), + "password": config_test_data.get("password", "P@ssw0rd!") + } + + response = api_client.post(ENDPOINT, json=payload) + + assert response.status_code in [201, 400], ( + f"Expected 201 or 400 for unicode username, got {response.status_code}. " + f"Response: {response.text}" + ) + + def test_register_content_type_validation( + self, api_client, config_test_data + ): + """ + Test registration with incorrect content type. + + API should require application/json content type. + """ + payload = { + "username": config_test_data.get("username", "testuser"), + "email": config_test_data.get("email", "test@example.com"), + "password": config_test_data.get("password", "P@ssw0rd!") + } + + # Send with wrong content type header + response = api_client.post( + ENDPOINT, + data=json.dumps(payload), + headers={"Content-Type": "text/plain"} + ) + + # Should return 400 or 415 (Unsupported Media Type) + assert response.status_code in [400, 415], ( + f"Expected 400 or 415 for wrong content type, got {response.status_code}. " + f"Response: {response.text}" + ) diff --git a/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/validator.py b/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/validator.py new file mode 100644 index 000000000..2f965738e --- /dev/null +++ b/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/validator.py @@ -0,0 +1,202 @@ + +import json +import yaml +from jsonschema import ( + Draft202012Validator, + Draft7Validator, + Draft4Validator, + ValidationError, +) +from referencing import Registry, Resource +from typing import Dict, Any +import requests + + +class SwaggerSchemaValidator: + """ + Validates JSON, XML, and text responses + """ + + def __init__(self, swagger_source: str): + self.spec = self._load_spec(swagger_source) + self.is_swagger2 = False + self.schemas = self._extract_schemas() + self.registry = Registry() + + for name, schema in self.schemas.items(): + pointer = ( + f"#/definitions/{name}" if self.is_swagger2 + else f"#/components/schemas/{name}" + ) + + wrapped = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + **schema, + } + self.registry = self.registry.with_resource( + pointer, + Resource.from_contents(wrapped) + ) + + def _load_spec(self, source: str) -> Dict[str, Any]: + if source.startswith(("http://", "https://")): + resp = requests.get(source) + resp.raise_for_status() + text = resp.text + + try: + return yaml.safe_load(text) + except yaml.YAMLError: + try: + return json.loads(text) + except json.JSONDecodeError: + raise ValueError("URL does not contain valid YAML or JSON") + + with open(source, "r") as f: + text = f.read() + + if source.endswith((".yaml", ".yml")): + return yaml.safe_load(text) + if source.endswith(".json"): + return json.loads(text) + + raise ValueError("File must be YAML or JSON") + + def _extract_schemas(self): + if "components" in self.spec and "schemas" in self.spec["components"]: + self.is_swagger2 = False + return self.spec["components"]["schemas"] + + if "definitions" in self.spec: + self.is_swagger2 = True + return self.spec["definitions"] + + raise ValueError("No schemas found under components/schemas or definitions") + + def get_version(self): + return self.spec.get("openapi") or self.spec.get("swagger") or "" + + def select_validator(self): + v = self.get_version() + + if v.startswith("2."): + return Draft4Validator + if v.startswith("3.0"): + return Draft7Validator + if v.startswith("3.1"): + return Draft202012Validator + + return Draft202012Validator + + def resolve_ref(self, ref): + if ref.startswith("#/"): + parts = ref.lstrip("#/").split("/") + node = self.spec + for p in parts: + node = node[p] + return node + + raise ValueError(f"External refs not supported: {ref}") + + def deref(self, schema): + if isinstance(schema, dict): + if "$ref" in schema: + resolved = self.resolve_ref(schema["$ref"]) + return self.deref(resolved) + return {k: self.deref(v) for k, v in schema.items()} + + if isinstance(schema, list): + return [self.deref(v) for v in schema] + + return schema + + def detect_format(self, response): + ctype = response.headers.get("Content-Type", "").lower() + if "json" in ctype: + return "json" + if "xml" in ctype: + return "xml" + if "text" in ctype: + return "text" + return "binary" + + def parse_body(self, response, fmt): + if fmt == "json": + return json.loads(response.text) + + if fmt == "xml": + import xmltodict + return xmltodict.parse(response.text) + + if fmt == "text": + return response.text + + return response.content + + def extract_schema_for_media_type(self, response_block, content_type): + content = response_block.get("content", {}) + + if content_type in content: + return content[content_type].get("schema") + + if "json" in content_type: + for k, v in content.items(): + if k == "application/json" or k.endswith("+json"): + return v.get("schema") + + if "xml" in content_type: + for k, v in content.items(): + if "xml" in k: + return v.get("schema") + + if "text/plain" in content: + return content["text/plain"].get("schema") + + return None + + + def validate_schema_by_response(self, endpoint, method, status_code, response): + fmt = self.detect_format(response) + + paths = self.spec.get("paths", {}) + op = paths.get(endpoint, {}).get(method.lower()) + + if not op: + return {"valid": False, "message": f"Method {method} not found at path {endpoint}"} + + responses = op.get("responses", {}) + response_block = responses.get(status_code) + + if not response_block: + return {"valid": False, "message": f"No response block for {status_code}"} + + ctype = response.headers.get("Content-Type", "").split(";")[0].strip() + + if "content" in response_block: + schema = self.extract_schema_for_media_type(response_block, ctype) + else: + schema = response_block.get("schema") + + if schema is None: + return {"valid": True, "message": "No schema defined for this content type"} + + try: + data = self.parse_body(response, fmt) + except Exception as e: + return {"valid": False, "message": f"Body parsing failed: {e}"} + + schema = self.deref(schema) + + validator_cls = self.select_validator() + validator = validator_cls(schema, registry=self.registry) + + try: + validator.validate(data) + return {"valid": True} + except ValidationError as e: + return { + "valid": False, + "message": e.message, + "path": list(e.path), + "schema_path": list(e.schema_path), + } From cc45815ba137c91ffc9340c670b32c07362e026b Mon Sep 17 00:00:00 2001 From: ZB-io <58132646+chaitra1403@users.noreply.github.com> Date: Wed, 26 Nov 2025 10:03:52 +0000 Subject: [PATCH 2/2] Improved Add API Tests (Pytest Framework, Claude AI) generated by RoostGPT Using AI Model claude-opus-4-5-20251101, for user feedback: -\sFormat\sthe\stest --- .../test_auth_register_post.py | 232 ++++++------------ 1 file changed, 77 insertions(+), 155 deletions(-) diff --git a/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/test_auth_register_post.py b/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/test_auth_register_post.py index 5b07b54bf..ceb22a204 100644 --- a/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/test_auth_register_post.py +++ b/tests/GUICE_GRIZZLY_JERSEY_OPENAPI_SWAGGER_EXAMPLE_API/test_auth_register_post.py @@ -1,4 +1,6 @@ + # ********RoostGPT******** +""" # Test generated by RoostGPT for test API-test-py01 using AI Type Claude AI and AI Model claude-opus-4-5-20251101 # @@ -7,19 +9,10 @@ # # -# ********RoostGPT******** +roost_feedback [26/11/2025, 10:03:09 AM]:-\sFormat\sthe\stest """ -Test Suite for POST /auth/register API Endpoint - -This module contains comprehensive pytest tests for the user registration endpoint. -Tests cover successful registration, validation errors, and edge cases. -Setup: - 1. Ensure config.yml is properly configured with API host - 2. Place auth_register.json test data file in the same directory - 3. Run tests: pytest test_auth_register.py -v - 4. Run smoke tests only: pytest test_auth_register.py -v -m smoke -""" +# ********RoostGPT******** import json import os @@ -28,7 +21,6 @@ from validator import SwaggerSchemaValidator -# Load test data from JSON file def load_test_data(): """Load test data from auth_register.json file.""" test_data_path = Path(__file__).parent / "auth_register.json" @@ -60,36 +52,25 @@ class TestAuthRegisterSuccess: def test_register_user_with_required_fields_only( self, api_client, config_test_data, schema_validator ): - """ - Test successful user registration with only required fields. - - Required fields: username, email, password - Expected: 201 Created response with user data - """ - # Build request payload with required fields from config payload = { "username": config_test_data.get("username", "testuser"), "email": config_test_data.get("email", "testuser@example.com"), - "password": config_test_data.get("password", "P@ssw0rd!") + "password": config_test_data.get("password", "P@ssw0rd!"), } - - # Make API request + response = api_client.post(ENDPOINT, json=payload) - - # Assert status code + assert response.status_code == 201, ( f"Expected 201, got {response.status_code}. Response: {response.text}" ) - - # Validate response schema + validation_result = schema_validator.validate_schema_by_response( ENDPOINT, HTTP_METHOD, "201", response ) assert validation_result["valid"], ( f"Schema validation failed: {validation_result.get('message', 'Unknown error')}" ) - - # Validate response contains expected fields + response_data = response.json() assert "id" in response_data, "Response should contain 'id' field" assert "username" in response_data, "Response should contain 'username' field" @@ -99,41 +80,31 @@ def test_register_user_with_required_fields_only( @pytest.mark.parametrize( "test_case", load_test_data(), - ids=lambda tc: tc.get("scenario", "unknown") + ids=lambda tc: tc.get("scenario", "unknown"), ) def test_register_user_from_test_data( self, api_client, config_test_data, schema_validator, test_case ): - """ - Table-driven test for user registration using test data from JSON file. - - Iterates through all test cases defined in auth_register.json - """ - # Build payload from test case, using config_test_data as fallback for password payload = { "username": test_case.get("username"), "email": test_case.get("email"), - "password": config_test_data.get("password", "P@ssw0rd!") + "password": config_test_data.get("password", "P@ssw0rd!"), } - - # Add optional fields if present in test case + if "firstName" in test_case: payload["firstName"] = test_case["firstName"] if "lastName" in test_case: payload["lastName"] = test_case["lastName"] - - # Make API request + response = api_client.post(ENDPOINT, json=payload) - - # Assert expected status code from test data + expected_status = test_case.get("statusCode", 201) assert response.status_code == expected_status, ( f"Scenario: {test_case.get('scenario')} - " f"Expected {expected_status}, got {response.status_code}. " f"Response: {response.text}" ) - - # Validate response schema for successful responses + if expected_status == 201: validation_result = schema_validator.validate_schema_by_response( ENDPOINT, HTTP_METHOD, "201", response @@ -150,25 +121,18 @@ class TestAuthRegisterValidation: def test_register_missing_username( self, api_client, config_test_data, schema_validator ): - """ - Test registration fails when username is missing. - - Expected: 400 Bad Request - """ payload = { "email": config_test_data.get("email", "test@example.com"), - "password": config_test_data.get("password", "P@ssw0rd!") + "password": config_test_data.get("password", "P@ssw0rd!"), } - + response = api_client.post(ENDPOINT, json=payload) - - # Missing required field should return 400 + assert response.status_code == 400, ( f"Expected 400 for missing username, got {response.status_code}. " f"Response: {response.text}" ) - - # Validate error response schema + validation_result = schema_validator.validate_schema_by_response( ENDPOINT, HTTP_METHOD, "400", response ) @@ -180,23 +144,18 @@ def test_register_missing_username( def test_register_missing_email( self, api_client, config_test_data, schema_validator ): - """ - Test registration fails when email is missing. - - Expected: 400 Bad Request - """ payload = { "username": config_test_data.get("username", "testuser"), - "password": config_test_data.get("password", "P@ssw0rd!") + "password": config_test_data.get("password", "P@ssw0rd!"), } - + response = api_client.post(ENDPOINT, json=payload) - + assert response.status_code == 400, ( f"Expected 400 for missing email, got {response.status_code}. " f"Response: {response.text}" ) - + validation_result = schema_validator.validate_schema_by_response( ENDPOINT, HTTP_METHOD, "400", response ) @@ -208,23 +167,18 @@ def test_register_missing_email( def test_register_missing_password( self, api_client, config_test_data, schema_validator ): - """ - Test registration fails when password is missing. - - Expected: 400 Bad Request - """ payload = { "username": config_test_data.get("username", "testuser"), - "email": config_test_data.get("email", "test@example.com") + "email": config_test_data.get("email", "test@example.com"), } - + response = api_client.post(ENDPOINT, json=payload) - + assert response.status_code == 400, ( f"Expected 400 for missing password, got {response.status_code}. " f"Response: {response.text}" ) - + validation_result = schema_validator.validate_schema_by_response( ENDPOINT, HTTP_METHOD, "400", response ) @@ -233,16 +187,9 @@ def test_register_missing_password( f"{validation_result.get('message', 'Unknown error')}" ) - def test_register_empty_request_body( - self, api_client, schema_validator - ): - """ - Test registration fails with empty request body. - - Expected: 400 Bad Request - """ + def test_register_empty_request_body(self, api_client, schema_validator): response = api_client.post(ENDPOINT, json={}) - + assert response.status_code == 400, ( f"Expected 400 for empty body, got {response.status_code}. " f"Response: {response.text}" @@ -251,19 +198,14 @@ def test_register_empty_request_body( def test_register_invalid_email_format( self, api_client, config_test_data, schema_validator ): - """ - Test registration fails with invalid email format. - - Expected: 400 Bad Request - """ payload = { "username": config_test_data.get("username", "testuser"), "email": "invalid-email-format", - "password": config_test_data.get("password", "P@ssw0rd!") + "password": config_test_data.get("password", "P@ssw0rd!"), } - + response = api_client.post(ENDPOINT, json=payload) - + assert response.status_code == 400, ( f"Expected 400 for invalid email format, got {response.status_code}. " f"Response: {response.text}" @@ -272,33 +214,45 @@ def test_register_invalid_email_format( @pytest.mark.parametrize( "invalid_payload,description", [ - ({"username": "", "email": "test@example.com", "password": "P@ssw0rd!"}, "empty username"), - ({"username": "user", "email": "", "password": "P@ssw0rd!"}, "empty email"), - ({"username": "user", "email": "test@example.com", "password": ""}, "empty password"), - ({"username": None, "email": "test@example.com", "password": "P@ssw0rd!"}, "null username"), - ({"username": "user", "email": None, "password": "P@ssw0rd!"}, "null email"), - ({"username": "user", "email": "test@example.com", "password": None}, "null password"), + ( + {"username": "", "email": "test@example.com", "password": "P@ssw0rd!"}, + "empty username", + ), + ( + {"username": "user", "email": "", "password": "P@ssw0rd!"}, + "empty email", + ), + ( + {"username": "user", "email": "test@example.com", "password": ""}, + "empty password", + ), + ( + {"username": None, "email": "test@example.com", "password": "P@ssw0rd!"}, + "null username", + ), + ( + {"username": "user", "email": None, "password": "P@ssw0rd!"}, + "null email", + ), + ( + {"username": "user", "email": "test@example.com", "password": None}, + "null password", + ), ], ids=[ "empty_username", - "empty_email", + "empty_email", "empty_password", "null_username", "null_email", - "null_password" - ] + "null_password", + ], ) def test_register_invalid_field_values( self, api_client, schema_validator, invalid_payload, description ): - """ - Test registration fails with invalid field values. - - Boundary testing for empty and null values in required fields. - Expected: 400 Bad Request - """ response = api_client.post(ENDPOINT, json=invalid_payload) - + assert response.status_code == 400, ( f"Expected 400 for {description}, got {response.status_code}. " f"Response: {response.text}" @@ -311,22 +265,16 @@ class TestAuthRegisterEdgeCases: def test_register_with_extra_fields( self, api_client, config_test_data, schema_validator ): - """ - Test registration with additional unexpected fields. - - API should either ignore extra fields or return appropriate response. - """ payload = { "username": config_test_data.get("username", "testuser"), "email": config_test_data.get("email", "test@example.com"), "password": config_test_data.get("password", "P@ssw0rd!"), "unexpectedField": "unexpected_value", - "anotherExtra": 12345 + "anotherExtra": 12345, } - + response = api_client.post(ENDPOINT, json=payload) - - # Should either succeed (201) or reject with 400 + assert response.status_code in [201, 400], ( f"Expected 201 or 400, got {response.status_code}. " f"Response: {response.text}" @@ -335,20 +283,14 @@ def test_register_with_extra_fields( def test_register_username_with_special_characters( self, api_client, config_test_data, schema_validator ): - """ - Test registration with special characters in username. - - Tests boundary condition for username format validation. - """ payload = { "username": "user@#$%^&*()", "email": config_test_data.get("email", "test@example.com"), - "password": config_test_data.get("password", "P@ssw0rd!") + "password": config_test_data.get("password", "P@ssw0rd!"), } - + response = api_client.post(ENDPOINT, json=payload) - - # Response depends on API validation rules + assert response.status_code in [201, 400], ( f"Expected 201 or 400, got {response.status_code}. " f"Response: {response.text}" @@ -357,20 +299,14 @@ def test_register_username_with_special_characters( def test_register_very_long_username( self, api_client, config_test_data, schema_validator ): - """ - Test registration with very long username. - - Boundary testing for maximum field length. - """ payload = { "username": "a" * 1000, "email": config_test_data.get("email", "test@example.com"), - "password": config_test_data.get("password", "P@ssw0rd!") + "password": config_test_data.get("password", "P@ssw0rd!"), } - + response = api_client.post(ENDPOINT, json=payload) - - # Should either succeed or return 400 for length validation + assert response.status_code in [201, 400], ( f"Expected 201 or 400 for long username, got {response.status_code}. " f"Response: {response.text}" @@ -379,46 +315,32 @@ def test_register_very_long_username( def test_register_with_unicode_characters( self, api_client, config_test_data, schema_validator ): - """ - Test registration with unicode characters in fields. - - Tests internationalization support. - """ payload = { "username": "用户名测试", "email": config_test_data.get("email", "test@example.com"), - "password": config_test_data.get("password", "P@ssw0rd!") + "password": config_test_data.get("password", "P@ssw0rd!"), } - + response = api_client.post(ENDPOINT, json=payload) - + assert response.status_code in [201, 400], ( f"Expected 201 or 400 for unicode username, got {response.status_code}. " f"Response: {response.text}" ) - def test_register_content_type_validation( - self, api_client, config_test_data - ): - """ - Test registration with incorrect content type. - - API should require application/json content type. - """ + def test_register_content_type_validation(self, api_client, config_test_data): payload = { "username": config_test_data.get("username", "testuser"), "email": config_test_data.get("email", "test@example.com"), - "password": config_test_data.get("password", "P@ssw0rd!") + "password": config_test_data.get("password", "P@ssw0rd!"), } - - # Send with wrong content type header + response = api_client.post( ENDPOINT, data=json.dumps(payload), - headers={"Content-Type": "text/plain"} + headers={"Content-Type": "text/plain"}, ) - - # Should return 400 or 415 (Unsupported Media Type) + assert response.status_code in [400, 415], ( f"Expected 400 or 415 for wrong content type, got {response.status_code}. " f"Response: {response.text}"