Files
regorus/tests/azure_policy/normalization/cases/edge_cases.yaml
Anand Krishnamoorthi d36f952133 feat(azure-policy): add alias normalization and denormalization (#635)
* feat: add Azure Policy alias normalization/denormalization

Add normalizer and denormalizer for ARM JSON resources, enabling Azure
Policy alias short names to become direct paths into a flat structure.

- Normalizer: flattens properties wrappers, lowercases keys, resolves
  per-alias versioned ARM paths, handles sub-resource array flattening,
  element-level field remaps, and array base renames
- Denormalizer: reverses all transformations with casing restoration
- AliasRegistry: loads production alias catalogs and data policy manifests
- Types: serde deserialization for ARM provider alias formats
- YAML test suite: 13 test files covering normalize, denormalize, round-trip,
  data-plane, edge cases, malformed input, sub-resources, and registry API
- Benchmark suite for normalization performance

* feat: add FFI and C# bindings for alias normalization

- FFI: alias_registry.rs with C-compatible API for loading catalogs,
  normalizing resources, and denormalizing back to ARM JSON
- C#: AliasRegistry wrapper class with NativeMethods P/Invoke bindings
  and integration tests
- Updated Cargo.lock files for new serde_json dependency
2026-03-30 18:44:36 -05:00

202 lines
5.7 KiB
YAML

# Tests pinning behavior for ambiguous or malformed inputs that could
# be produced by external callers rather than the normalizer itself.
#
# The normalizer / denormalizer make assumptions about their input shape
# (e.g., keys are fully lowercased after normalization). These tests
# document what happens when those assumptions are violated, without
# asserting the behavior is "correct" per se — rather, they pin it so
# regressions are detected.
aliases_json: |
[
{
"namespace": "Microsoft.Storage",
"resourceTypes": [
{
"resourceType": "storageAccounts",
"aliases": [
{
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
"defaultPath": "properties.supportsHttpsTrafficOnly",
"paths": []
},
{
"name": "Microsoft.Storage/storageAccounts/accessTier",
"defaultPath": "properties.accessTier",
"paths": []
}
]
}
]
},
{
"namespace": "Microsoft.Network",
"resourceTypes": [
{
"resourceType": "networkSecurityGroups",
"aliases": [
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].protocol",
"defaultPath": "properties.securityRules[*].properties.protocol",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].access",
"defaultPath": "properties.securityRules[*].properties.access",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].name",
"defaultPath": "properties.securityRules[*].name",
"paths": []
}
]
}
]
},
{
"namespace": "Microsoft.Web",
"resourceTypes": [
{
"resourceType": "sites",
"aliases": [
{
"name": "Microsoft.Web/sites/isEnabled",
"defaultPath": "properties.isEnabled",
"paths": [
{
"path": "properties.enabled",
"apiVersions": ["2020-01-01"]
}
]
}
]
}
]
}
]
cases:
# ── Normalize: ARM input with both root and properties having same field ──
- note: "normalize: both root 'name' and properties.name (root wins)"
input:
name: root-name
type: "Microsoft.Storage/storageAccounts"
properties:
name: props-name
accessTier: Hot
expected_normalized:
name: root-name
type: "Microsoft.Storage/storageAccounts"
accesstier: Hot
# ── Normalize: extra fields not in alias catalog or ROOT_FIELDS ──
- note: "normalize: extra non-aliased properties are kept (lowercased)"
input:
name: test
type: "Microsoft.Storage/storageAccounts"
properties:
supportsHttpsTrafficOnly: true
customUnknownField: 42
expected_normalized:
name: test
type: "Microsoft.Storage/storageAccounts"
supportshttpstrafficonly: true
customunknownfield: 42
# ── Denormalize: input has mixed casing (externally constructed) ──
- note: "denormalize: mixed-case keys still resolve via aliases"
input:
name: test
type: "Microsoft.Storage/storageAccounts"
SupportsHttpsTrafficOnly: true
expected_denormalized:
name: test
type: "Microsoft.Storage/storageAccounts"
properties:
supportsHttpsTrafficOnly: true
# ── Denormalize: input has extra fields not in alias catalog ──
- note: "denormalize: unknown fields go under properties (control-plane)"
input:
name: test
type: "Microsoft.Storage/storageAccounts"
unknownfield: 123
accesstier: Hot
expected_denormalized:
name: test
type: "Microsoft.Storage/storageAccounts"
properties:
unknownfield: 123
accessTier: Hot
# ── Denormalize with versioned path: both alias-named and ARM-named present ──
- note: "denormalize: both isenabled and enabled present, alias wins"
input:
type: "Microsoft.Web/sites"
isenabled: true
enabled: false
api_version: "2020-01-01"
expected_denormalized:
type: "Microsoft.Web/sites"
properties:
enabled: true
- note: "denormalize: both isenabled and enabled present, default path"
input:
type: "Microsoft.Web/sites"
isenabled: true
enabled: false
expected_denormalized:
type: "Microsoft.Web/sites"
properties:
isEnabled: true
enabled: false
# ── Normalize: ARM resource with empty type ──
- note: "normalize: empty type field, no alias match"
input:
name: test
type: ""
properties:
foo: bar
expected_normalized:
name: test
type: ""
foo: bar
# ── Normalize: ARM resource with null properties ──
- note: "normalize: null properties value"
input:
name: test
properties: null
expected_normalized:
name: test
# ── Denormalize: sub-resource array with elements already containing properties ──
- note: "denormalize: elements already have properties wrapper (double-wrap)"
input:
name: myNsg
type: "Microsoft.Network/networkSecurityGroups"
securityrules:
- name: rule1
properties:
protocol: Tcp
expected_denormalized:
name: myNsg
type: "Microsoft.Network/networkSecurityGroups"
properties:
securityRules:
- name: rule1
properties:
properties:
protocol: Tcp