Repository: schemathesis/schemathesis

Issue:
[BUG]
### Checklist

- [x] I checked the [FAQ section](https://schemathesis.readthedocs.io/en/stable/faq/) of the documentation
- [x] I looked for similar issues in the [issue tracker](https://github.com/schemathesis/schemathesis/issues)
- [x] I am using the latest version of Schemathesis

### Describe the bug

Tries to use invalid examples on the `requestBody` when having `allOf` schema with a required field inside.

### To Reproduce

🚨 **Mandatory** 🚨: Steps to reproduce the behavior:

```
❯ uvx schemathesis run allof.yaml --url http://localhost:8080 --mode positive
Schemathesis v4.5.3
━━━━━━━━━━━━━━━━━━━

 ✅  Loaded specification from allof.yaml (in 0.23s)

     Base URL:         http://localhost:8080
     Specification:    Open API 3.0.3
     Operations:       1 selected / 1 total

 ✅  API capabilities:

     Supports NULL byte in headers:    ✘

 ❌  Examples (in 0.14s)

     ❌ 1 failed

 ❌  Coverage (in 0.14s)

     ❌ 1 failed

 ✅  Fuzzing (in 1.04s)

     ✅ 1 passed

================================================================================== FAILURES ==================================================================================
_______________________________________________________________________________ POST /resource _______________________________________________________________________________
1. Test Case ID: d2Lm83

- API rejected schema-compliant request

    Valid data should have been accepted
    Expected: 2xx, 401, 403, 404, 5xx

- Undocumented HTTP status code

    Received: 400
    Documented: 204

[400] Bad Request:

    `{"error":"name is required"}`

Reproduce with:

    curl -X POST -H 'Content-Type: application/json' -d '{"choice": "option2"}' http://localhost:8080/resource

================================================================================== SUMMARY ===================================================================================

API Operations:
  Selected: 1/1
  Tested: 1

Test Phases:
  ❌ Examples
  ❌ Coverage
  ✅ Fuzzing
  ⏭  Stateful (not applicable)

Failures:
  ❌ API rejected schema-compliant request: 1
  ❌ Undocumented HTTP status code: 1

Test cases:
  103 generated, 1 found 2 unique failures

Seed: 65429241656916511108634453427875109206

============================================================================ 2 failures in 1.36s 
```

Please include a minimal API schema causing this issue:

```yaml
openapi: "3.0.3"
info:
  version: 1.0.0
  title: Resources
paths:
  /resource:
    post:
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/resource"
      responses:
        "204":
          description: Done
components:
  schemas:
    resource:
      type: object
      allOf:
        - $ref: "#/components/schemas/base_resource"
      properties:
        choice:
          $ref: "#/components/schemas/choice"
      example:
        name: example-name
        choice: option2

    base_resource:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          minLength: 1
      example:
        name: example-name

    choice:
      type: string
      enum:
        - option1
        - option2
        - option3
      example: option2

```

Application:
```js
const express = require('express')
const app = express()
const port = 8080

app.use(express.json());

app.post('/resource', (req, res) => {
  if (!req.body || !('name' in req.body)) {
    return res.status(400).json({ error: 'name is required' })
  }
  res.status(204).contentType('application/json').send('')
})

app.listen(port, () => {
  console.log(`App listening on port ${port}`)
})
```

### Expected behavior

I would expect it to only use the example of `#/components/schemas/resource` but instead it tried using the one coming from `choice`.

### Environment

```
- OS: Linux
- Python version: uvx 0.9.5 / Python 3.13.7
- Schemathesis version: 4.5.3
- Spec version: 3.0.3
```

### Additional context

Include any other relevant details, like logs or screenshots.

Discussion:

Fix (patch):
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 272c4243e7..8e6a4ac100 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
 
 ## [Unreleased](https://github.com/schemathesis/schemathesis/compare/v4.5.3...HEAD) - TBD
 
+### :bug: Fixed
+
+- Invalid examples extracted from schemas with `allOf` and required fields. [#3333](https://github.com/schemathesis/schemathesis/issues/3333)
+
 ## [4.5.3](https://github.com/schemathesis/schemathesis/compare/v4.5.2...v4.5.3) - 2025-11-17
 
 ### :bug: Fixed
diff --git a/src/schemathesis/specs/openapi/examples.py b/src/schemathesis/specs/openapi/examples.py
index 8c6f447b80..7cf7931ccb 100644
--- a/src/schemathesis/specs/openapi/examples.py
+++ b/src/schemathesis/specs/openapi/examples.py
@@ -395,21 +395,24 @@ def extract_from_schema(
     except InfiniteRecursiveReference:
         return
 
-    # If schema has allOf, we need to get merged properties from allOf items
-    # to extract property-level examples from all schemas, not just the parent
+    # If schema has allOf, we need to get merged properties and required fields from allOf items
+    # This handles cases where parent has properties alongside allOf
     properties_to_process = schema.get("properties", {})
+    required = schema.get("required", [])
+
     if "allOf" in schema and "properties" in schema:
-        # Get the merged allOf schema which includes properties from all allOf items
+        # Get the merged allOf schema which includes properties and required fields from all allOf items
         for expanded_schema, _ in _expand_subschemas(schema=schema, resolver=resolver, reference_path=current_path):
             if expanded_schema is not schema and isinstance(expanded_schema, dict):
-                # This is the merged allOf result with combined properties
+                # This is the merged allOf result with combined properties and required fields
                 if "properties" in expanded_schema:
                     properties_to_process = expanded_schema["properties"]
+                if "required" in expanded_schema:
+                    required = expanded_schema["required"]
                 break
 
     if properties_to_process:
         variants = {}
-        required = schema.get("required", [])
         to_generate: dict[str, Any] = {}
 
         for name, subschema in list(properties_to_process.items()):
@@ -452,25 +455,29 @@ def extract_from_schema(
                 variants[name] = values
 
         if variants:
-            config = operation.schema.config.generation_for(operation=operation, phase="examples")
-            for name, subschema in to_generate.items():
-                if name in variants:
-                    # Generated by one of `anyOf` or similar sub-schemas
-                    continue
-                if bundle_storage is not None:
-                    subschema = dict(subschema)
-                    subschema[BUNDLE_STORAGE_KEY] = bundle_storage
-                generated = _generate_single_example(subschema, config)
-                variants[name] = [generated]
-
-            # Calculate the maximum number of examples any property has
-            total_combos = max(len(examples) for examples in variants.values())
-            # Evenly distribute examples by cycling through them
-            for idx in range(total_combos):
-                yield {
-                    name: next(islice(cycle(property_variants), idx, None))
-                    for name, property_variants in variants.items()
-                }
+            # Check if all required fields will be present in the generated examples
+            all_required_covered = all(field in variants or field in to_generate for field in required)
+
+            if all_required_covered:
+                config = operation.schema.config.generation_for(operation=operation, phase="examples")
+                for name, subschema in to_generate.items():
+                    if name in variants:
+                        # Generated by one of `anyOf` or similar sub-schemas
+                        continue
+                    if bundle_storage is not None:
+                        subschema = dict(subschema)
+                        subschema[BUNDLE_STORAGE_KEY] = bundle_storage
+                    generated = _generate_single_example(subschema, config)
+                    variants[name] = [generated]
+
+                # Calculate the maximum number of examples any property has
+                total_combos = max(len(examples) for examples in variants.values())
+                # Evenly distribute examples by cycling through them
+                for idx in range(total_combos):
+                    yield {
+                        name: next(islice(cycle(property_variants), idx, None))
+                        for name, property_variants in variants.items()
+                    }
 
     elif "items" in schema and isinstance(schema["items"], dict):
         # Each inner value should be wrapped in an array


Test changes:
diff --git a/test/specs/openapi/test_examples.py b/test/specs/openapi/test_examples.py
index 08b9c07124..c8955306d0 100644
--- a/test/specs/openapi/test_examples.py
+++ b/test/specs/openapi/test_examples.py
@@ -13,6 +13,7 @@
 from schemathesis.generation.hypothesis import examples
 from schemathesis.specs.openapi.adapter.parameters import parameters_to_json_schema
 from schemathesis.specs.openapi.examples import (
+    BodyExample,
     ParameterExample,
     extract_from_schemas,
     extract_inner_examples,
@@ -2268,3 +2269,81 @@ def test_nested_allof_with_property_refs():
     schema = schemathesis.openapi.from_dict(raw_schema)
     operation = schema["/items"]["PUT"]
     assert operation.get_strategies_from_examples() == []
+
+
+def test_allof_with_required_field_should_not_use_incomplete_property_examples(ctx):
+    # GH-3333
+    raw_schema = ctx.openapi.build_schema(
+        {
+            "/resource": {
+                "post": {
+                    "requestBody": {
+                        "required": True,
+                        "content": {"application/json": {"schema": {"$ref": "#/components/schemas/resource"}}},
+                    },
+                    "responses": {"204": {"description": "Done"}},
+                }
+            }
+        },
+        version="3.0.3",
+        components={
+            "schemas": {
+                "resource": {
+                    "type": "object",
+                    "allOf": [{"$ref": "#/components/schemas/base_resource"}],
+                    "properties": {"choice": {"$ref": "#/components/schemas/choice"}},
+                    "example": {
+                        "name": "example-name",
+                        "choice": "option2",
+                    },
+                },
+                "base_resource": {
+                    "type": "object",
+                    "required": ["name"],
+                    "properties": {
+                        "name": {
+                            "type": "string",
+                            "minLength": 1,
+                        }
+                    },
+                    "example": {"name": "example-name"},
+                },
+                "choice": {
+                    "type": "string",
+                    "enum": ["option1", "option2", "option3"],
+                    "example": "option2",
+                },
+            }
+        },
+    )
+
+    schema = schemathesis.openapi.from_dict(raw_schema)
+    operation = schema["/resource"]["POST"]
+
+    extracted = list(extract_from_schemas(operation))
+
+    assert len(extracted) == 1
+    example = extracted[0]
+    assert isinstance(example, BodyExample)
+
+    body_alternative = list(operation.body)[0]
+    body_schema = body_alternative.optimized_schema
+
+    validation_error = None
+    try:
+        jsonschema.validate(example.value, body_schema)
+    except jsonschema.ValidationError as e:
+        validation_error = e
+
+    assert validation_error is None, (
+        f"Example {example.value} is invalid (missing required 'name' from allOf). "
+        f"Property-level examples should not be extracted when they violate schema constraints."
+    )
+
+    strategies = operation.get_strategies_from_examples()
+    for strategy in strategies:
+        case = examples.generate_one(strategy)
+        try:
+            jsonschema.validate(case.body, body_schema)
+        except jsonschema.ValidationError as e:
+            pytest.fail(f"Generated invalid case {case.body}: {e}")