From a83a74386a87f4546d46c9afa74d8e6a846afd86 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Tue, 10 Feb 2026 01:32:17 +0100 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=A7=AA=20Add=20additional=20fixture?= =?UTF-8?q?=20tests=20for=20schemas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/schema/__snapshots__/test_schema.ambr | 415 ++++++++++++++++++++ tests/schema/fixtures/config.yml | 101 +++++ tests/schema/fixtures/fields.yml | 91 +++++ tests/schema/fixtures/network.yml | 116 ++++++ tests/schema/fixtures/unevaluated.yml | 174 ++++++++ 5 files changed, 897 insertions(+) diff --git a/tests/schema/__snapshots__/test_schema.ambr b/tests/schema/__snapshots__/test_schema.ambr index 635a9b109..96b39cdfb 100644 --- a/tests/schema/__snapshots__/test_schema.ambr +++ b/tests/schema/__snapshots__/test_schema.ambr @@ -575,9 +575,31 @@ {"properties":{"efforts":{"minimum":15,"type":"string"}},"type":"object"} ''' # --- +# name: test_schema_config[network_direct_and_links_field_merge_rejected] + ''' + Schemas entry 'impl-links-schema[0]' is not valid: + Additional properties are not allowed ('tests' was unexpected) + + Failed validating "additionalProperties" in schema["properties"]["validate"]["$ref"]["properties"]["network"]["additionalProperties"]["$ref"] + + On instance["validate"]["network"]["links"]: + {"tests":{"contains":{"local":{"properties":{"type":{"const":"test"}}}},"minContains":1},"type":"array"} + ''' +# --- # name: test_schema_config[network_link_not_exist] "Schema '[0]' defines an unknown network link type 'links2'." # --- +# name: test_schema_config[network_mixed_links_and_direct_rejected] + ''' + Schemas entry 'mixed-format-schema[0]' is not valid: + Additional properties are not allowed ('derived_from' was unexpected) + + Failed validating "additionalProperties" in schema["properties"]["validate"]["$ref"]["properties"]["network"]["additionalProperties"]["$ref"] + + On instance["validate"]["network"]["links"]: + {"derived_from":{"contains":{"local":{"properties":{"type":{"const":"spec","type":"string"}}}},"minContains":99},"type":"array"} + ''' +# --- # name: test_schema_config[ref_error] "Reference 'not-exist' not found in definitions." # --- @@ -3377,6 +3399,19 @@ }), }) # --- +# name: test_schemas[schema/fixtures/fields-coerce_to_integer_invalid_chars] + ''' + /index.rst:1: WARNING: Need could not be created: Field 'complexity' is invalid: Cannot convert '4a' to integer [needs.create_need] + + ''' +# --- +# name: test_schemas[schema/fixtures/fields-coerce_to_integer_invalid_chars].1 + dict({ + 'validated_needs_count': 0, + 'validation_warnings': dict({ + }), + }) +# --- # name: test_schemas[schema/fixtures/fields-coerce_to_number] '' # --- @@ -3488,6 +3523,74 @@ }), }) # --- +# name: test_schemas[schema/fixtures/fields-fields-integer_maximum_violation] + ''' + ERROR: Need 'IMPL_1' has schema violations: + Severity: violation + Field: complexity + Need path: IMPL_1 + Schema path: fields > schema > properties > complexity > maximum + Schema message: 15 is greater than the maximum of 10 [sn_schema_violation.field_fail] + + ''' +# --- +# name: test_schemas[schema/fixtures/fields-fields-integer_maximum_violation].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + 'IMPL_1': list([ + dict({ + 'children': list([ + ]), + 'details': dict({ + 'field': 'complexity', + 'need_path': 'IMPL_1', + 'schema_path': 'fields > schema > properties > complexity > maximum', + 'severity': 'violation', + 'validation_msg': '15 is greater than the maximum of 10', + }), + 'log_lvl': 'error', + 'subtype': 'field_fail', + 'type': 'sn_schema_violation', + }), + ]), + }), + }) +# --- +# name: test_schemas[schema/fixtures/fields-integer_minimum_violation] + ''' + ERROR: Need 'IMPL_1' has schema violations: + Severity: violation + Field: complexity + Need path: IMPL_1 + Schema path: fields > schema > properties > complexity > minimum + Schema message: 0 is less than the minimum of 1 [sn_schema_violation.field_fail] + + ''' +# --- +# name: test_schemas[schema/fixtures/fields-integer_minimum_violation].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + 'IMPL_1': list([ + dict({ + 'children': list([ + ]), + 'details': dict({ + 'field': 'complexity', + 'need_path': 'IMPL_1', + 'schema_path': 'fields > schema > properties > complexity > minimum', + 'severity': 'violation', + 'validation_msg': '0 is less than the minimum of 1', + }), + 'log_lvl': 'error', + 'subtype': 'field_fail', + 'type': 'sn_schema_violation', + }), + ]), + }), + }) +# --- # name: test_schemas[schema/fixtures/fields-integer_multiple_of] '' # --- @@ -3532,6 +3635,40 @@ }), }) # --- +# name: test_schemas[schema/fixtures/fields-integer_type_injection] + ''' + ERROR: Need 'IMPL_1' has schema violations: + Severity: violation + Field: priority + Need path: IMPL_1 + Schema path: [0] > local > properties > priority > minimum + Schema message: 3 is less than the minimum of 5 [sn_schema_violation.local_fail] + + ''' +# --- +# name: test_schemas[schema/fixtures/fields-integer_type_injection].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + 'IMPL_1': list([ + dict({ + 'children': list([ + ]), + 'details': dict({ + 'field': 'priority', + 'need_path': 'IMPL_1', + 'schema_path': '[0] > local > properties > priority > minimum', + 'severity': 'violation', + 'validation_msg': '3 is less than the minimum of 5', + }), + 'log_lvl': 'error', + 'subtype': 'local_fail', + 'type': 'sn_schema_violation', + }), + ]), + }), + }) +# --- # name: test_schemas[schema/fixtures/fields-no_schema] '' # --- @@ -3542,6 +3679,74 @@ }), }) # --- +# name: test_schemas[schema/fixtures/fields-number_maximum_violation] + ''' + ERROR: Need 'IMPL_1' has schema violations: + Severity: violation + Field: completion_percentage + Need path: IMPL_1 + Schema path: fields > schema > properties > completion_percentage > maximum + Schema message: 890.89 is greater than the maximum of 100.0 [sn_schema_violation.field_fail] + + ''' +# --- +# name: test_schemas[schema/fixtures/fields-number_maximum_violation].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + 'IMPL_1': list([ + dict({ + 'children': list([ + ]), + 'details': dict({ + 'field': 'completion_percentage', + 'need_path': 'IMPL_1', + 'schema_path': 'fields > schema > properties > completion_percentage > maximum', + 'severity': 'violation', + 'validation_msg': '890.89 is greater than the maximum of 100.0', + }), + 'log_lvl': 'error', + 'subtype': 'field_fail', + 'type': 'sn_schema_violation', + }), + ]), + }), + }) +# --- +# name: test_schemas[schema/fixtures/fields-number_minimum_violation] + ''' + ERROR: Need 'IMPL_1' has schema violations: + Severity: violation + Field: completion_percentage + Need path: IMPL_1 + Schema path: fields > schema > properties > completion_percentage > minimum + Schema message: 1.3 is less than the minimum of 10.0 [sn_schema_violation.field_fail] + + ''' +# --- +# name: test_schemas[schema/fixtures/fields-number_minimum_violation].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + 'IMPL_1': list([ + dict({ + 'children': list([ + ]), + 'details': dict({ + 'field': 'completion_percentage', + 'need_path': 'IMPL_1', + 'schema_path': 'fields > schema > properties > completion_percentage > minimum', + 'severity': 'violation', + 'validation_msg': '1.3 is less than the minimum of 10.0', + }), + 'log_lvl': 'error', + 'subtype': 'field_fail', + 'type': 'sn_schema_violation', + }), + ]), + }), + }) +# --- # name: test_schemas[schema/fixtures/fields-number_multiple_of] '' # --- @@ -4173,6 +4378,102 @@ }), }) # --- +# name: test_schemas[schema/fixtures/network-direct_link_type_contains_error] + ''' + ERROR: Need 'IMPL_1' has schema violations: + Severity: violation + Need path: IMPL_1 > derived_from + Schema path: impl-derived_from-spec[0] > validate > network > derived_from > contains + Schema message: Too few valid links of type 'derived_from' (0 < 1) / nok: REQ_WRONG + + Details for REQ_WRONG + Field: type + Need path: IMPL_1 > derived_from > REQ_WRONG + Schema path: impl-derived_from-spec[0] > validate > network > derived_from > contains > local > properties > type > const + Schema message: "spec" was expected [sn_schema_violation.network_contains_too_few] + + ''' +# --- +# name: test_schemas[schema/fixtures/network-direct_link_type_contains_error].1 + dict({ + 'validated_needs_count': 2, + 'validation_warnings': dict({ + 'IMPL_1': list([ + dict({ + 'children': list([ + dict({ + 'need_id': 'REQ_WRONG', + 'warning': dict({ + 'children': list([ + ]), + 'details': dict({ + 'field': 'type', + 'need_path': 'IMPL_1 > derived_from > REQ_WRONG', + 'schema_path': 'impl-derived_from-spec[0] > validate > network > derived_from > contains > local > properties > type > const', + 'validation_msg': '"spec" was expected', + }), + 'subtype': 'network_local_fail', + }), + }), + ]), + 'details': dict({ + 'need_path': 'IMPL_1 > derived_from', + 'schema_path': 'impl-derived_from-spec[0] > validate > network > derived_from > contains', + 'severity': 'violation', + 'validation_msg': "Too few valid links of type 'derived_from' (0 < 1) / nok: REQ_WRONG", + }), + 'log_lvl': 'error', + 'subtype': 'network_contains_too_few', + 'type': 'sn_schema_violation', + }), + ]), + }), + }) +# --- +# name: test_schemas[schema/fixtures/network-direct_link_type_contains_pass] + '' +# --- +# name: test_schemas[schema/fixtures/network-direct_link_type_contains_pass].1 + dict({ + 'validated_needs_count': 2, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/network-direct_link_type_items_error] + ''' + ERROR: Need 'SPEC_WRONG' has schema violations: + Severity: violation + Field: type + Need path: IMPL_1 > implements > SPEC_WRONG + Schema path: impl-implements-req[0] > validate > network > implements > items > local > properties > type > const + Schema message: "req" was expected [sn_schema_violation.network_local_fail] + + ''' +# --- +# name: test_schemas[schema/fixtures/network-direct_link_type_items_error].1 + dict({ + 'validated_needs_count': 2, + 'validation_warnings': dict({ + 'IMPL_1': list([ + dict({ + 'children': list([ + ]), + 'details': dict({ + 'field': 'type', + 'need_path': 'IMPL_1 > implements > SPEC_WRONG', + 'schema_path': 'impl-implements-req[0] > validate > network > implements > items > local > properties > type > const', + 'severity': 'violation', + 'validation_msg': '"req" was expected', + }), + 'log_lvl': 'error', + 'subtype': 'network_local_fail', + 'type': 'sn_schema_violation', + }), + ]), + }), + }) +# --- # name: test_schemas[schema/fixtures/network-link_chain_hop_1_min_contains_error] ''' ERROR: Need 'IMPL_SAFE' has schema violations: @@ -4893,6 +5194,120 @@ }), }) # --- +# name: test_schemas[schema/fixtures/unevaluated-core_fields_default_values] + '' +# --- +# name: test_schemas[schema/fixtures/unevaluated-core_fields_default_values].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/unevaluated-core_fields_non_default] + ''' + ERROR: Need 'IMPL_1' has schema violations: + Severity: violation + Need path: IMPL_1 + Schema path: [0] > local > unevaluatedProperties + Schema message: Unevaluated properties are not allowed ('priority' was unexpected) [sn_schema_violation.local_fail] + + ''' +# --- +# name: test_schemas[schema/fixtures/unevaluated-core_fields_non_default].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + 'IMPL_1': list([ + dict({ + 'children': list([ + ]), + 'details': dict({ + 'need_path': 'IMPL_1', + 'schema_path': '[0] > local > unevaluatedProperties', + 'severity': 'violation', + 'validation_msg': "Unevaluated properties are not allowed ('priority' was unexpected)", + }), + 'log_lvl': 'error', + 'subtype': 'local_fail', + 'type': 'sn_schema_violation', + }), + ]), + }), + }) +# --- +# name: test_schemas[schema/fixtures/unevaluated-core_fields_unevaluated] + '' +# --- +# name: test_schemas[schema/fixtures/unevaluated-core_fields_unevaluated].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/unevaluated-empty_links_reduced] + '' +# --- +# name: test_schemas[schema/fixtures/unevaluated-empty_links_reduced].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/unevaluated-extra_field_default_empty] + '' +# --- +# name: test_schemas[schema/fixtures/unevaluated-extra_field_default_empty].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/unevaluated-non_empty_links_kept] + ''' + ERROR: Need 'IMPL_1' has schema violations: + Severity: violation + Need path: IMPL_1 + Schema path: [0] > local > unevaluatedProperties + Schema message: Unevaluated properties are not allowed ('links' was unexpected) [sn_schema_violation.local_fail] + + ''' +# --- +# name: test_schemas[schema/fixtures/unevaluated-non_empty_links_kept].1 + dict({ + 'validated_needs_count': 2, + 'validation_warnings': dict({ + 'IMPL_1': list([ + dict({ + 'children': list([ + ]), + 'details': dict({ + 'need_path': 'IMPL_1', + 'schema_path': '[0] > local > unevaluatedProperties', + 'severity': 'violation', + 'validation_msg': "Unevaluated properties are not allowed ('links' was unexpected)", + }), + 'log_lvl': 'error', + 'subtype': 'local_fail', + 'type': 'sn_schema_violation', + }), + ]), + }), + }) +# --- +# name: test_schemas[schema/fixtures/unevaluated-schema_properties_whitelist] + '' +# --- +# name: test_schemas[schema/fixtures/unevaluated-schema_properties_whitelist].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + }), + }) +# --- # name: test_schemas[schema/fixtures/unevaluated-unevaluated_allof_error] ''' ERROR: Need 'IMPL_1' has schema violations: diff --git a/tests/schema/fixtures/config.yml b/tests/schema/fixtures/config.yml index 9b7eee869..017d87ce6 100644 --- a/tests/schema/fixtures/config.yml +++ b/tests/schema/fixtures/config.yml @@ -502,3 +502,104 @@ schemas_validate_pattern_relative_subroutine_error: properties: id: pattern: "^test(?+1)$" + +network_mixed_links_and_direct_rejected: + conf: | + extensions = ["sphinx_needs"] + needs_from_toml = "ubproject.toml" + ubproject: | + needs.schema_definitions_from_json = "schemas.json" + + [[needs.extra_links]] + option = "derived_from" + schemas: + $defs: + type-spec: + properties: + type: + const: spec + type-impl: + properties: + type: + const: impl + schemas: + - id: "mixed-format-schema" + message: Schema that mixes network.links and direct network. - MUST be rejected + select: + $ref: '#/$defs/type-impl' + validate: + network: + links: + derived_from: + contains: + local: + $ref: '#/$defs/type-spec' + minContains: 99 + derived_from: + contains: + local: + $ref: '#/$defs/type-spec' + minContains: 1 + rst: | + .. spec:: target specification + :id: SPEC_1 + + .. impl:: implementation with one derived_from link + :id: IMPL_1 + :derived_from: SPEC_1 + +network_direct_and_links_field_merge_rejected: + conf: | + extensions = ["sphinx_needs"] + needs_from_toml = "ubproject.toml" + ubproject: | + needs.schema_definitions_from_json = "schemas.json" + + [[needs.extra_links]] + option = "derived_from" + + [[needs.extra_links]] + option = "tests" + schemas: + $defs: + type-spec: + properties: + type: + const: spec + type-test: + properties: + type: + const: test + type-impl: + properties: + type: + const: impl + schemas: + - id: "impl-links-schema" + message: impl must derive from spec and be tested + select: + $ref: '#/$defs/type-impl' + validate: + network: + links: + tests: + contains: + local: + $ref: '#/$defs/type-test' + minContains: 1 + derived_from: + contains: + local: + $ref: '#/$defs/type-spec' + minContains: 1 + rst: | + .. spec:: specification + :id: SPEC_1 + + .. test:: test case + :id: TEST_1 + + .. impl:: implementation + :id: IMPL_1 + :derived_from: SPEC_1 + :tests: TEST_1 diff --git a/tests/schema/fixtures/fields.yml b/tests/schema/fixtures/fields.yml index a051db61a..24ee14264 100644 --- a/tests/schema/fixtures/fields.yml +++ b/tests/schema/fixtures/fields.yml @@ -775,3 +775,94 @@ tags_enum: .. impl:: title :id: IMPL_1 :tags: a,b,c + +integer_minimum_violation: + conf: | + extensions = ["sphinx_needs"] + needs_from_toml = "ubproject.toml" + ubproject: | + [needs.fields.complexity] + schema.type = "integer" + schema.minimum = 1 + schema.maximum = 10 + rst: | + .. impl:: title + :id: IMPL_1 + :complexity: 0 + +fields-integer_maximum_violation: + conf: | + extensions = ["sphinx_needs"] + needs_from_toml = "ubproject.toml" + ubproject: | + [needs.fields.complexity] + schema.type = "integer" + schema.minimum = 1 + schema.maximum = 10 + rst: | + .. impl:: title + :id: IMPL_1 + :complexity: 15 + +number_minimum_violation: + conf: | + extensions = ["sphinx_needs"] + needs_from_toml = "ubproject.toml" + ubproject: | + [needs.fields.completion_percentage] + schema.type = "number" + schema.minimum = 10.0 + schema.maximum = 100.0 + rst: | + .. impl:: title + :id: IMPL_1 + :completion_percentage: 1.3 + +number_maximum_violation: + conf: | + extensions = ["sphinx_needs"] + needs_from_toml = "ubproject.toml" + ubproject: | + [needs.fields.completion_percentage] + schema.type = "number" + schema.minimum = 0.0 + schema.maximum = 100.0 + rst: | + .. impl:: title + :id: IMPL_1 + :completion_percentage: 890.89 + +coerce_to_integer_invalid_chars: + conf: | + extensions = ["sphinx_needs"] + needs_from_toml = "ubproject.toml" + ubproject: | + [needs.fields.complexity] + schema.type = "integer" + rst: | + .. impl:: title + :id: IMPL_1 + :complexity: 4a + +integer_type_injection: + conf: | + extensions = ["sphinx_needs"] + needs_from_toml = "ubproject.toml" + ubproject: | + needs.schema_definitions_from_json = "schemas.json" + + [needs.fields.priority] + nullable = true + schema.type = "integer" + schemas: + $defs: [] + schemas: + - validate: + local: + properties: + priority: + minimum: 5 + rst: | + .. impl:: title + :id: IMPL_1 + :priority: 3 diff --git a/tests/schema/fixtures/network.yml b/tests/schema/fixtures/network.yml index 2eb166790..b61a907ea 100644 --- a/tests/schema/fixtures/network.yml +++ b/tests/schema/fixtures/network.yml @@ -670,3 +670,119 @@ link_name_contains: "properties": "type": "const": "spec" + +direct_link_type_contains_pass: + conf: | + extensions = ["sphinx_needs"] + needs_from_toml = "ubproject.toml" + ubproject: | + needs.schema_definitions_from_json = "schemas.json" + + [[needs.extra_links]] + option = "derived_from" + schemas: + $defs: + type-spec: + properties: + type: + const: spec + type-impl: + properties: + type: + const: impl + schemas: + - id: "impl-derived_from-spec" + message: impl must derive from spec + select: + $ref: '#/$defs/type-impl' + validate: + network: + derived_from: + contains: + local: + $ref: '#/$defs/type-spec' + minContains: 1 + rst: | + .. spec:: specification + :id: SPEC_1 + + .. impl:: implementation + :id: IMPL_1 + :derived_from: SPEC_1 + +direct_link_type_contains_error: + conf: | + extensions = ["sphinx_needs"] + needs_from_toml = "ubproject.toml" + ubproject: | + needs.schema_definitions_from_json = "schemas.json" + + [[needs.extra_links]] + option = "derived_from" + schemas: + $defs: + type-spec: + properties: + type: + const: spec + type-impl: + properties: + type: + const: impl + schemas: + - id: "impl-derived_from-spec" + message: impl must derive from spec + select: + $ref: '#/$defs/type-impl' + validate: + network: + derived_from: + contains: + local: + $ref: '#/$defs/type-spec' + minContains: 1 + rst: | + .. req:: wrong target type (req instead of spec) + :id: REQ_WRONG + + .. impl:: implementation that links to wrong type + :id: IMPL_1 + :derived_from: REQ_WRONG + +direct_link_type_items_error: + conf: | + extensions = ["sphinx_needs"] + needs_from_toml = "ubproject.toml" + ubproject: | + needs.schema_definitions_from_json = "schemas.json" + + [[needs.extra_links]] + option = "implements" + schemas: + $defs: + type-req: + properties: + type: + const: req + type-impl: + properties: + type: + const: impl + schemas: + - id: "impl-implements-req" + message: impl must implement req + select: + $ref: '#/$defs/type-impl' + validate: + network: + implements: + items: + local: + $ref: '#/$defs/type-req' + rst: | + .. spec:: wrong type for implements target + :id: SPEC_WRONG + + .. impl:: implementation + :id: IMPL_1 + :implements: SPEC_WRONG diff --git a/tests/schema/fixtures/unevaluated.yml b/tests/schema/fixtures/unevaluated.yml index 115e734fa..dd090ea61 100644 --- a/tests/schema/fixtures/unevaluated.yml +++ b/tests/schema/fixtures/unevaluated.yml @@ -46,3 +46,177 @@ unevaluated_allof_error: unevaluatedProperties: false allOf: - properties: {"comment": {}} + +core_fields_unevaluated: + conf: | + extensions = ["sphinx_needs"] + needs_from_toml = "ubproject.toml" + ubproject: | + needs.schema_definitions_from_json = "schemas.json" + schemas: + schemas: + - validate: + local: + properties: + id: + type: string + type: + type: string + status: + type: string + unevaluatedProperties: false + rst: | + .. impl:: title + :id: IMPL_1 + :status: approved + +# Test: Core fields with defaults should be reduced away +# This validates that title/status/tags are reduced when they have default values +core_fields_default_values: + conf: | + extensions = ["sphinx_needs"] + needs_from_toml = "ubproject.toml" + ubproject: | + needs.schema_definitions_from_json = "schemas.json" + schemas: + schemas: + - validate: + local: + properties: + id: + type: string + type: + type: string + unevaluatedProperties: false + rst: | + .. impl:: My Title + :id: IMPL_1 + +# Test: Extra fields with non-default values should be kept and can cause unevaluatedProperties error +# Extra fields are kept if they differ from default, regardless of schema_properties +# This triggers reduce_need() because unevaluatedProperties is present +core_fields_non_default: + conf: | + extensions = ["sphinx_needs"] + needs_from_toml = "ubproject.toml" + ubproject: | + needs.schema_definitions_from_json = "schemas.json" + + [needs.fields.priority] + nullable = true + schemas: + schemas: + - validate: + local: + properties: + id: + type: string + type: + type: string + unevaluatedProperties: false + rst: | + .. impl:: title + :id: IMPL_1 + :priority: high + +# Test: Extra fields with default empty string should be reduced +extra_field_default_empty: + conf: | + extensions = ["sphinx_needs"] + needs_from_toml = "ubproject.toml" + ubproject: | + needs.schema_definitions_from_json = "schemas.json" + + [needs.fields.asil] + nullable = true + [needs.fields.comment] + nullable = true + schemas: + schemas: + - validate: + local: + properties: + id: + type: string + type: + type: string + asil: + type: string + unevaluatedProperties: false + rst: | + .. impl:: title + :id: IMPL_1 + :asil: QM + +# Test: Empty links array should be reduced (not cause unevaluated error) +empty_links_reduced: + conf: | + extensions = ["sphinx_needs"] + needs_from_toml = "ubproject.toml" + ubproject: | + needs.schema_definitions_from_json = "schemas.json" + schemas: + schemas: + - validate: + local: + properties: + id: + type: string + type: + type: string + unevaluatedProperties: false + rst: | + .. impl:: title + :id: IMPL_1 + +# Test: Non-empty links should be kept and cause unevaluated error if not in schema +non_empty_links_kept: + conf: | + extensions = ["sphinx_needs"] + needs_from_toml = "ubproject.toml" + ubproject: | + needs.schema_definitions_from_json = "schemas.json" + schemas: + schemas: + - validate: + local: + properties: + id: + type: string + type: + type: string + unevaluatedProperties: false + rst: | + .. spec:: target + :id: SPEC_1 + + .. impl:: title + :id: IMPL_1 + :links: SPEC_1 + +# Test: Schema properties whitelist - keep field only if in schema +schema_properties_whitelist: + conf: | + extensions = ["sphinx_needs"] + needs_from_toml = "ubproject.toml" + ubproject: | + needs.schema_definitions_from_json = "schemas.json" + + [needs.fields.priority] + nullable = true + schemas: + schemas: + - validate: + local: + properties: + id: + type: string + type: + type: string + priority: + enum: ["low", "medium", "high"] + unevaluatedProperties: false + rst: | + .. impl:: title + :id: IMPL_1 + :priority: high From f4895e618d2675e5a94fcbe99bafefb6dc047bd2 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Tue, 10 Feb 2026 01:37:31 +0100 Subject: [PATCH 2/4] update --- tests/schema/__snapshots__/test_schema.ambr | 4 ++-- tests/schema/fixtures/fields.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/schema/__snapshots__/test_schema.ambr b/tests/schema/__snapshots__/test_schema.ambr index 96b39cdfb..bf409856d 100644 --- a/tests/schema/__snapshots__/test_schema.ambr +++ b/tests/schema/__snapshots__/test_schema.ambr @@ -3523,7 +3523,7 @@ }), }) # --- -# name: test_schemas[schema/fixtures/fields-fields-integer_maximum_violation] +# name: test_schemas[schema/fixtures/fields-integer_maximum_violation] ''' ERROR: Need 'IMPL_1' has schema violations: Severity: violation @@ -3534,7 +3534,7 @@ ''' # --- -# name: test_schemas[schema/fixtures/fields-fields-integer_maximum_violation].1 +# name: test_schemas[schema/fixtures/fields-integer_maximum_violation].1 dict({ 'validated_needs_count': 1, 'validation_warnings': dict({ diff --git a/tests/schema/fixtures/fields.yml b/tests/schema/fixtures/fields.yml index 24ee14264..3f9eaddb4 100644 --- a/tests/schema/fixtures/fields.yml +++ b/tests/schema/fixtures/fields.yml @@ -790,7 +790,7 @@ integer_minimum_violation: :id: IMPL_1 :complexity: 0 -fields-integer_maximum_violation: +integer_maximum_violation: conf: | extensions = ["sphinx_needs"] needs_from_toml = "ubproject.toml" From ae0f323489aa0d23dde164b5a837a15cd0605f18 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 12 Feb 2026 15:42:56 +0100 Subject: [PATCH 3/4] multiple_errors_per_need --- tests/schema/fixtures/fields.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/schema/fixtures/fields.yml b/tests/schema/fixtures/fields.yml index 3f9eaddb4..c3df9bb8f 100644 --- a/tests/schema/fixtures/fields.yml +++ b/tests/schema/fixtures/fields.yml @@ -866,3 +866,22 @@ integer_type_injection: .. impl:: title :id: IMPL_1 :priority: 3 + +multiple_errors_per_need: + conf: | + extensions = ["sphinx_needs"] + needs_from_toml = "ubproject.toml" + ubproject: | + [needs.fields.priority] + schema.type = "integer" + schema.minimum = 1 + schema.maximum = 5 + + [needs.fields.severity] + schema.type = "string" + schema.enum = ["low", "medium", "high"] + rst: | + .. impl:: title + :id: IMPL_1 + :priority: 8 + :severity: critical From c6350cd899dbd307a2e6ddc5195f6fc3d075d8f8 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 12 Feb 2026 15:44:23 +0100 Subject: [PATCH 4/4] Update test_schema.ambr --- tests/schema/__snapshots__/test_schema.ambr | 54 +++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/schema/__snapshots__/test_schema.ambr b/tests/schema/__snapshots__/test_schema.ambr index b84893223..703aff02b 100644 --- a/tests/schema/__snapshots__/test_schema.ambr +++ b/tests/schema/__snapshots__/test_schema.ambr @@ -3674,6 +3674,60 @@ }), }) # --- +# name: test_schemas[schema/fixtures/fields-multiple_errors_per_need] + ''' + ERROR: Need 'IMPL_1' has schema violations: + Severity: violation + Field: priority + Need path: IMPL_1 + Schema path: fields > schema > properties > priority > maximum + Schema message: 8 is greater than the maximum of 5 [sn_schema_violation.field_fail] + ERROR: Need 'IMPL_1' has schema violations: + Severity: violation + Field: severity + Need path: IMPL_1 + Schema path: fields > schema > properties > severity > enum + Schema message: "critical" is not one of "low", "medium" or "high" [sn_schema_violation.field_fail] + + ''' +# --- +# name: test_schemas[schema/fixtures/fields-multiple_errors_per_need].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + 'IMPL_1': list([ + dict({ + 'children': list([ + ]), + 'details': dict({ + 'field': 'priority', + 'need_path': 'IMPL_1', + 'schema_path': 'fields > schema > properties > priority > maximum', + 'severity': 'violation', + 'validation_msg': '8 is greater than the maximum of 5', + }), + 'log_lvl': 'error', + 'subtype': 'field_fail', + 'type': 'sn_schema_violation', + }), + dict({ + 'children': list([ + ]), + 'details': dict({ + 'field': 'severity', + 'need_path': 'IMPL_1', + 'schema_path': 'fields > schema > properties > severity > enum', + 'severity': 'violation', + 'validation_msg': '"critical" is not one of "low", "medium" or "high"', + }), + 'log_lvl': 'error', + 'subtype': 'field_fail', + 'type': 'sn_schema_violation', + }), + ]), + }), + }) +# --- # name: test_schemas[schema/fixtures/fields-no_schema] '' # ---