# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

# Implicit allOf Test Suite
# When a field with [*] appears in a condition outside count, Azure Policy
# applies implicit allOf semantics: ALL elements must satisfy the operator.
# Empty arrays evaluate to true (vacuous truth); missing arrays evaluate to
# false because the field is not iterable and the condition is not met.

cases:
  # =========================================================================
  # Basic allOf — all elements match
  # =========================================================================

  - note: allof_equals_all_match
    policy_rule: |
      {
        "if": {
          "field": "items[*].status",
          "equals": "active"
        },
        "then": { "effect": "deny" }
      }
    resource:
      items:
        - { "status": "active" }
        - { "status": "active" }
        - { "status": "active" }
    want_effect: "deny"

  - note: allof_equals_one_mismatch
    policy_rule: |
      {
        "if": {
          "field": "items[*].status",
          "equals": "active"
        },
        "then": { "effect": "deny" }
      }
    resource:
      items:
        - { "status": "active" }
        - { "status": "inactive" }
        - { "status": "active" }
    want_undefined: true

  # =========================================================================
  # Empty array → true (vacuous truth)
  # =========================================================================

  - note: allof_empty_array_is_true
    policy_rule: |
      {
        "if": {
          "field": "items[*].status",
          "equals": "active"
        },
        "then": { "effect": "deny" }
      }
    resource:
      items: []
    want_effect: "deny"

  # =========================================================================
  # Missing array → false (not iterable → condition not met)
  # Azure Policy: field[*] on null/missing/non-array → false
  # =========================================================================

  - note: allof_missing_array_is_false
    policy_rule: |
      {
        "if": {
          "field": "items[*].status",
          "equals": "active"
        },
        "then": { "effect": "deny" }
      }
    resource:
      type: "some.type"
    want_undefined: true

  # =========================================================================
  # No suffix — elements compared directly
  # =========================================================================

  - note: allof_no_suffix_all_match
    policy_rule: |
      {
        "if": {
          "field": "ports[*]",
          "equals": 443
        },
        "then": { "effect": "deny" }
      }
    resource:
      ports: [443, 443, 443]
    want_effect: "deny"

  - note: allof_no_suffix_one_mismatch
    policy_rule: |
      {
        "if": {
          "field": "ports[*]",
          "equals": 443
        },
        "then": { "effect": "deny" }
      }
    resource:
      ports: [443, 80, 443]
    want_undefined: true

  # =========================================================================
  # Dotted prefix before [*]
  # =========================================================================

  - note: allof_dotted_prefix
    policy_rule: |
      {
        "if": {
          "field": "properties.rules[*].enabled",
          "equals": true
        },
        "then": { "effect": "deny" }
      }
    resource:
      properties:
        rules:
          - { "enabled": true }
          - { "enabled": true }
    want_effect: "deny"

  - note: allof_dotted_prefix_mismatch
    policy_rule: |
      {
        "if": {
          "field": "properties.rules[*].enabled",
          "equals": true
        },
        "then": { "effect": "deny" }
      }
    resource:
      properties:
        rules:
          - { "enabled": true }
          - { "enabled": false }
    want_undefined: true

  # =========================================================================
  # Various operators
  # =========================================================================

  - note: allof_not_equals
    policy_rule: |
      {
        "if": {
          "field": "items[*].value",
          "notEquals": "bad"
        },
        "then": { "effect": "deny" }
      }
    resource:
      items:
        - { "value": "good" }
        - { "value": "fine" }
    want_effect: "deny"

  - note: allof_not_equals_one_matches
    policy_rule: |
      {
        "if": {
          "field": "items[*].value",
          "notEquals": "bad"
        },
        "then": { "effect": "deny" }
      }
    resource:
      items:
        - { "value": "good" }
        - { "value": "bad" }
    want_undefined: true

  - note: allof_greater
    policy_rule: |
      {
        "if": {
          "field": "scores[*]",
          "greater": 50
        },
        "then": { "effect": "deny" }
      }
    resource:
      scores: [80, 90, 100]
    want_effect: "deny"

  - note: allof_greater_one_fails
    policy_rule: |
      {
        "if": {
          "field": "scores[*]",
          "greater": 50
        },
        "then": { "effect": "deny" }
      }
    resource:
      scores: [80, 30, 100]
    want_undefined: true

  - note: allof_in_operator
    policy_rule: |
      {
        "if": {
          "field": "regions[*]",
          "in": ["eastus", "westus", "centralus"]
        },
        "then": { "effect": "deny" }
      }
    resource:
      regions: ["eastus", "westus"]
    want_effect: "deny"

  - note: allof_in_operator_one_outside
    policy_rule: |
      {
        "if": {
          "field": "regions[*]",
          "in": ["eastus", "westus", "centralus"]
        },
        "then": { "effect": "deny" }
      }
    resource:
      regions: ["eastus", "northeurope"]
    want_undefined: true

  - note: allof_contains
    policy_rule: |
      {
        "if": {
          "field": "tags[*]",
          "contains": "prod"
        },
        "then": { "effect": "deny" }
      }
    resource:
      tags: ["prod-web", "prod-api"]
    want_effect: "deny"

  - note: allof_like
    policy_rule: |
      {
        "if": {
          "field": "names[*]",
          "like": "vm-*"
        },
        "then": { "effect": "deny" }
      }
    resource:
      names: ["vm-001", "vm-002"]
    want_effect: "deny"

  - note: allof_exists_true
    policy_rule: |
      {
        "if": {
          "field": "items[*].name",
          "exists": true
        },
        "then": { "effect": "deny" }
      }
    resource:
      items:
        - { "name": "a" }
        - { "name": "b" }
    want_effect: "deny"

  - note: allof_exists_false_missing_field
    policy_rule: |
      {
        "if": {
          "field": "items[*].name",
          "exists": true
        },
        "then": { "effect": "deny" }
      }
    resource:
      items:
        - { "name": "a" }
        - { "other": "b" }
    want_undefined: true

  # -- exists false with [*] ------------------------------------------------

  - note: allof_exists_false_all_present
    # All elements have the field → "exists false" is false for each → compliant
    policy_rule: |
      {
        "if": {
          "field": "items[*].name",
          "exists": "false"
        },
        "then": { "effect": "deny" }
      }
    resource:
      items:
        - { "name": "a" }
        - { "name": "b" }
    want_undefined: true

  - note: allof_exists_false_all_missing
    # No element has the field → "exists false" is true for each → non-compliant
    policy_rule: |
      {
        "if": {
          "field": "items[*].name",
          "exists": "false"
        },
        "then": { "effect": "deny" }
      }
    resource:
      items:
        - { "other": "x" }
        - { "other": "y" }
    want_effect: "deny"

  - note: allof_exists_false_mixed
    # Mixed — one has field, one doesn't → not all satisfy → compliant
    policy_rule: |
      {
        "if": {
          "field": "items[*].name",
          "exists": "false"
        },
        "then": { "effect": "deny" }
      }
    resource:
      items:
        - { "name": "a" }
        - { "other": "b" }
    want_undefined: true

  - note: allof_exists_true_null_values
    # Null values → "exists true" is false (null = not defined) → compliant
    policy_rule: |
      {
        "if": {
          "field": "items[*].name",
          "exists": "true"
        },
        "then": { "effect": "deny" }
      }
    resource:
      items:
        - { "name": null }
        - { "name": null }
    want_undefined: true

  - note: allof_exists_false_null_values
    # Null values → "exists false" is true (null = not defined) → non-compliant
    policy_rule: |
      {
        "if": {
          "field": "items[*].name",
          "exists": "false"
        },
        "then": { "effect": "deny" }
      }
    resource:
      items:
        - { "name": null }
        - { "name": null }
    want_effect: "deny"

  - note: allof_exists_false_empty_array
    # Empty array → vacuous truth → non-compliant
    policy_rule: |
      {
        "if": {
          "field": "items[*].name",
          "exists": "false"
        },
        "then": { "effect": "deny" }
      }
    resource:
      items: []
    want_effect: "deny"

  # =========================================================================
  # Real-world pattern: not + [*] + notEquals
  # "at least one element equals X" via double negation
  # =========================================================================

  - note: allof_not_notequals_pattern_match
    policy_rule: |
      {
        "if": {
          "not": {
            "field": "ports[*]",
            "notEquals": "*"
          }
        },
        "then": { "effect": "deny" }
      }
    resource:
      ports: ["80", "*", "443"]
    want_effect: "deny"

  - note: allof_not_notequals_pattern_no_match
    policy_rule: |
      {
        "if": {
          "not": {
            "field": "ports[*]",
            "notEquals": "*"
          }
        },
        "then": { "effect": "deny" }
      }
    resource:
      ports: ["80", "443"]
    want_undefined: true

  # =========================================================================
  # Doubly-nested [*] — allOf over nested arrays
  # =========================================================================

  - note: allof_doubly_nested_all_match
    policy_rule: |
      {
        "if": {
          "field": "groups[*].tags[*]",
          "notEquals": "deprecated"
        },
        "then": { "effect": "deny" }
      }
    resource:
      groups:
        - tags: ["prod", "web"]
        - tags: ["staging", "api"]
    want_effect: "deny"

  - note: allof_doubly_nested_one_fails
    policy_rule: |
      {
        "if": {
          "field": "groups[*].tags[*]",
          "notEquals": "deprecated"
        },
        "then": { "effect": "deny" }
      }
    resource:
      groups:
        - tags: ["prod", "web"]
        - tags: ["deprecated", "api"]
    want_undefined: true

  - note: allof_doubly_nested_with_suffix
    policy_rule: |
      {
        "if": {
          "field": "groups[*].members[*].role",
          "equals": "admin"
        },
        "then": { "effect": "deny" }
      }
    resource:
      groups:
        - members:
            - { "role": "admin" }
            - { "role": "admin" }
        - members:
            - { "role": "admin" }
    want_effect: "deny"

  - note: allof_doubly_nested_with_suffix_fail
    policy_rule: |
      {
        "if": {
          "field": "groups[*].members[*].role",
          "equals": "admin"
        },
        "then": { "effect": "deny" }
      }
    resource:
      groups:
        - members:
            - { "role": "admin" }
        - members:
            - { "role": "viewer" }
    want_undefined: true

  # =========================================================================
  # Doubly-nested — inner array missing/empty
  # =========================================================================

  - note: allof_doubly_nested_inner_empty
    policy_rule: |
      {
        "if": {
          "field": "groups[*].tags[*]",
          "equals": "ok"
        },
        "then": { "effect": "deny" }
      }
    resource:
      groups:
        - tags: []
        - tags: []
    want_effect: "deny"

  - note: allof_doubly_nested_inner_missing
    # Inner [*] on missing field → false (not iterable), outer Every fails
    policy_rule: |
      {
        "if": {
          "field": "groups[*].tags[*]",
          "equals": "ok"
        },
        "then": { "effect": "deny" }
      }
    resource:
      groups:
        - other: "no tags"
        - other: "also no tags"
    want_undefined: true

  # =========================================================================
  # Single element — trivially allOf
  # =========================================================================

  - note: allof_single_element_match
    policy_rule: |
      {
        "if": {
          "field": "items[*].v",
          "equals": 42
        },
        "then": { "effect": "deny" }
      }
    resource:
      items:
        - { "v": 42 }
    want_effect: "deny"

  - note: allof_single_element_no_match
    policy_rule: |
      {
        "if": {
          "field": "items[*].v",
          "equals": 42
        },
        "then": { "effect": "deny" }
      }
    resource:
      items:
        - { "v": 99 }
    want_undefined: true
