mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
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
This commit is contained in:
committed by
GitHub
parent
35fb5d5953
commit
d36f952133
@@ -0,0 +1,170 @@
|
||||
# Extended data-plane manifest tests covering shapes beyond the basic
|
||||
# KeyVault case: multiple resource types, top-level aliases, nested
|
||||
# objects, array aliases, and schemaVersions.
|
||||
|
||||
data_manifest_json: |
|
||||
{
|
||||
"dataNamespace": "Microsoft.DataFactory.Data",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "Microsoft.DataFactory.Data/factories/pipelines/enabled",
|
||||
"paths": [
|
||||
{
|
||||
"path": "enabled",
|
||||
"apiVersions": ["2018-06-01"]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"resourceTypeAliases": [
|
||||
{
|
||||
"resourceType": "factories/pipelines",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "Microsoft.DataFactory.Data/factories/pipelines/activities[*].type",
|
||||
"paths": [
|
||||
{
|
||||
"path": "activities[*].type",
|
||||
"apiVersions": ["2018-06-01"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.DataFactory.Data/factories/pipelines/activities[*].name",
|
||||
"paths": [
|
||||
{
|
||||
"path": "activities[*].name",
|
||||
"apiVersions": ["2018-06-01"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.DataFactory.Data/factories/pipelines/concurrency",
|
||||
"paths": [
|
||||
{
|
||||
"path": "concurrency",
|
||||
"apiVersions": ["2018-06-01"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.DataFactory.Data/factories/pipelines/folder.name",
|
||||
"paths": [
|
||||
{
|
||||
"path": "folder.name",
|
||||
"apiVersions": ["2018-06-01"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"resourceType": "factories/datasets",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "Microsoft.DataFactory.Data/factories/datasets/linkedServiceName.referenceName",
|
||||
"paths": [
|
||||
{
|
||||
"path": "linkedServiceName.referenceName",
|
||||
"schemaVersions": ["1"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.DataFactory.Data/factories/datasets/description",
|
||||
"paths": [
|
||||
{
|
||||
"path": "description",
|
||||
"schemaVersions": ["1"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
cases:
|
||||
- note: data-plane pipeline with top-level alias and scalar fields
|
||||
use_registry_api: true
|
||||
input:
|
||||
type: "Microsoft.DataFactory.Data/factories/pipelines"
|
||||
enabled: true
|
||||
concurrency: 5
|
||||
folder:
|
||||
name: "etl-jobs"
|
||||
expected_normalized:
|
||||
type: "Microsoft.DataFactory.Data/factories/pipelines"
|
||||
enabled: true
|
||||
concurrency: 5
|
||||
folder:
|
||||
name: "etl-jobs"
|
||||
round_trip: true
|
||||
|
||||
- note: data-plane pipeline with array alias
|
||||
use_registry_api: true
|
||||
input:
|
||||
type: "Microsoft.DataFactory.Data/factories/pipelines"
|
||||
activities:
|
||||
- name: CopyActivity1
|
||||
type: Copy
|
||||
- name: WaitActivity1
|
||||
type: Wait
|
||||
expected_normalized:
|
||||
type: "Microsoft.DataFactory.Data/factories/pipelines"
|
||||
activities:
|
||||
- name: CopyActivity1
|
||||
type: Copy
|
||||
- name: WaitActivity1
|
||||
type: Wait
|
||||
round_trip: true
|
||||
|
||||
- note: data-plane dataset with nested alias path
|
||||
use_registry_api: true
|
||||
input:
|
||||
type: "Microsoft.DataFactory.Data/factories/datasets"
|
||||
description: "Sales data"
|
||||
linkedServiceName:
|
||||
referenceName: "AzureBlobStorage1"
|
||||
type: LinkedServiceReference
|
||||
expected_normalized:
|
||||
type: "Microsoft.DataFactory.Data/factories/datasets"
|
||||
description: "Sales data"
|
||||
linkedservicename:
|
||||
referencename: "AzureBlobStorage1"
|
||||
type: LinkedServiceReference
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
type: "Microsoft.DataFactory.Data/factories/datasets"
|
||||
description: "Sales data"
|
||||
linkedServiceName:
|
||||
referenceName: "AzureBlobStorage1"
|
||||
type: LinkedServiceReference
|
||||
|
||||
- note: data-plane without aliases (unknown resource type in namespace)
|
||||
use_registry_api: true
|
||||
input:
|
||||
type: "Microsoft.DataFactory.Data/factories/triggers"
|
||||
recurrence:
|
||||
frequency: Day
|
||||
interval: 1
|
||||
expected_normalized:
|
||||
type: "Microsoft.DataFactory.Data/factories/triggers"
|
||||
recurrence:
|
||||
frequency: Day
|
||||
interval: 1
|
||||
round_trip: true
|
||||
|
||||
- note: data-plane denormalize dataset restores casing
|
||||
use_registry_api: true
|
||||
input:
|
||||
type: "Microsoft.DataFactory.Data/factories/datasets"
|
||||
description: "Sales data"
|
||||
linkedservicename:
|
||||
referencename: "AzureBlobStorage1"
|
||||
expected_denormalized:
|
||||
type: "Microsoft.DataFactory.Data/factories/datasets"
|
||||
description: "Sales data"
|
||||
linkedServiceName:
|
||||
referenceName: "AzureBlobStorage1"
|
||||
reverse_round_trip: true
|
||||
@@ -0,0 +1,154 @@
|
||||
# Broad data-plane compatibility tests covering multiple Azure data-plane
|
||||
# namespaces and resource shapes. Each case exercises a distinct namespace
|
||||
# to ensure the manifest-loading and data-plane normalization paths handle
|
||||
# a variety of real-world patterns.
|
||||
|
||||
data_manifest_json: |
|
||||
{
|
||||
"dataNamespace": "Microsoft.Kubernetes.Data",
|
||||
"aliases": [],
|
||||
"resourceTypeAliases": [
|
||||
{
|
||||
"resourceType": "namespaces",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "Microsoft.Kubernetes.Data/namespaces/labels",
|
||||
"paths": [{ "path": "labels", "apiVersions": ["v1"] }]
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Kubernetes.Data/namespaces/annotations",
|
||||
"paths": [{ "path": "annotations", "apiVersions": ["v1"] }]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"resourceType": "pods",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "Microsoft.Kubernetes.Data/pods/containers[*].image",
|
||||
"paths": [{ "path": "containers[*].image", "apiVersions": ["v1"] }]
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Kubernetes.Data/pods/containers[*].name",
|
||||
"paths": [{ "path": "containers[*].name", "apiVersions": ["v1"] }]
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Kubernetes.Data/pods/containers[*].resources.limits.cpu",
|
||||
"paths": [{ "path": "containers[*].resources.limits.cpu", "apiVersions": ["v1"] }]
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Kubernetes.Data/pods/hostNetwork",
|
||||
"paths": [{ "path": "hostNetwork", "apiVersions": ["v1"] }]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
cases:
|
||||
# ── Kubernetes namespaces: flat labels/annotations ──
|
||||
- note: "k8s namespace with labels and annotations"
|
||||
use_registry_api: true
|
||||
input:
|
||||
type: "Microsoft.Kubernetes.Data/namespaces"
|
||||
labels:
|
||||
app: web-frontend
|
||||
version: "v2"
|
||||
annotations:
|
||||
owner: team-alpha
|
||||
expected_normalized:
|
||||
type: "Microsoft.Kubernetes.Data/namespaces"
|
||||
labels:
|
||||
app: web-frontend
|
||||
version: "v2"
|
||||
annotations:
|
||||
owner: team-alpha
|
||||
round_trip: true
|
||||
|
||||
# ── Kubernetes pods: arrays with deeply nested fields ──
|
||||
- note: "k8s pod with containers array and resource limits"
|
||||
use_registry_api: true
|
||||
input:
|
||||
type: "Microsoft.Kubernetes.Data/pods"
|
||||
hostNetwork: false
|
||||
containers:
|
||||
- name: app
|
||||
image: "myregistry.azurecr.io/app:latest"
|
||||
resources:
|
||||
limits:
|
||||
cpu: "500m"
|
||||
memory: "256Mi"
|
||||
- name: sidecar
|
||||
image: "myregistry.azurecr.io/sidecar:v1"
|
||||
resources:
|
||||
limits:
|
||||
cpu: "100m"
|
||||
expected_normalized:
|
||||
type: "Microsoft.Kubernetes.Data/pods"
|
||||
hostnetwork: false
|
||||
containers:
|
||||
- name: app
|
||||
image: "myregistry.azurecr.io/app:latest"
|
||||
resources:
|
||||
limits:
|
||||
cpu: "500m"
|
||||
memory: "256Mi"
|
||||
- name: sidecar
|
||||
image: "myregistry.azurecr.io/sidecar:v1"
|
||||
resources:
|
||||
limits:
|
||||
cpu: "100m"
|
||||
round_trip: true
|
||||
|
||||
- note: "k8s pod denormalize restores casing"
|
||||
use_registry_api: true
|
||||
input:
|
||||
type: "Microsoft.Kubernetes.Data/pods"
|
||||
hostnetwork: true
|
||||
containers:
|
||||
- name: app
|
||||
image: "nginx"
|
||||
expected_denormalized:
|
||||
type: "Microsoft.Kubernetes.Data/pods"
|
||||
hostNetwork: true
|
||||
containers:
|
||||
- name: app
|
||||
image: "nginx"
|
||||
reverse_round_trip: true
|
||||
|
||||
# ── Unknown resource type in known namespace ──
|
||||
- note: "k8s unknown resource type passes through unchanged"
|
||||
use_registry_api: true
|
||||
input:
|
||||
type: "Microsoft.Kubernetes.Data/services"
|
||||
clusterIP: "10.0.0.1"
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8080
|
||||
expected_normalized:
|
||||
type: "Microsoft.Kubernetes.Data/services"
|
||||
clusterip: "10.0.0.1"
|
||||
ports:
|
||||
- port: 80
|
||||
targetport: 8080
|
||||
round_trip: true
|
||||
# No aliases for this type, so casing is lost on round-trip.
|
||||
expected_round_trip:
|
||||
type: "Microsoft.Kubernetes.Data/services"
|
||||
clusterip: "10.0.0.1"
|
||||
ports:
|
||||
- port: 80
|
||||
targetport: 8080
|
||||
|
||||
# ── Empty containers array ──
|
||||
- note: "k8s pod with empty containers array"
|
||||
use_registry_api: true
|
||||
input:
|
||||
type: "Microsoft.Kubernetes.Data/pods"
|
||||
hostNetwork: false
|
||||
containers: []
|
||||
expected_normalized:
|
||||
type: "Microsoft.Kubernetes.Data/pods"
|
||||
hostnetwork: false
|
||||
containers: []
|
||||
round_trip: true
|
||||
@@ -0,0 +1,64 @@
|
||||
# Tests that exercise data-plane manifest loading and the data-plane
|
||||
# normalization path (resource type contains ".Data/").
|
||||
# This covers load_data_policy_manifest_json() and the data-plane
|
||||
# normalization branch in normalizer.rs.
|
||||
|
||||
data_manifest_json: |
|
||||
{
|
||||
"dataNamespace": "Microsoft.KeyVault.Data",
|
||||
"aliases": [],
|
||||
"resourceTypeAliases": [
|
||||
{
|
||||
"resourceType": "vaults/certificates",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "Microsoft.KeyVault.Data/vaults/certificates/keySize",
|
||||
"paths": [
|
||||
{
|
||||
"path": "keySize",
|
||||
"apiVersions": ["7.0"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.KeyVault.Data/vaults/certificates/attributes.expiresOn",
|
||||
"paths": [
|
||||
{
|
||||
"path": "attributes.expiresOn",
|
||||
"apiVersions": ["7.0"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
cases:
|
||||
- note: data-plane manifest normalize via registry API
|
||||
use_registry_api: true
|
||||
input:
|
||||
type: "Microsoft.KeyVault.Data/vaults/certificates"
|
||||
keySize: 2048
|
||||
attributes:
|
||||
expiresOn: "2025-01-01T00:00:00Z"
|
||||
expected_normalized:
|
||||
type: "Microsoft.KeyVault.Data/vaults/certificates"
|
||||
keysize: 2048
|
||||
attributes:
|
||||
expireson: "2025-01-01T00:00:00Z"
|
||||
round_trip: true
|
||||
|
||||
- note: data-plane manifest round-trip via registry API
|
||||
use_registry_api: true
|
||||
input:
|
||||
type: "Microsoft.KeyVault.Data/vaults/certificates"
|
||||
keySize: 2048
|
||||
attributes:
|
||||
expiresOn: "2025-01-01T00:00:00Z"
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
type: "Microsoft.KeyVault.Data/vaults/certificates"
|
||||
keySize: 2048
|
||||
attributes:
|
||||
expiresOn: "2025-01-01T00:00:00Z"
|
||||
@@ -0,0 +1,319 @@
|
||||
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": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Storage/storageAccounts/sku.name",
|
||||
"defaultPath": "sku.name",
|
||||
"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": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"resourceType": "virtualNetworks",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "Microsoft.Network/virtualNetworks/subnets[*].addressPrefix",
|
||||
"defaultPath": "properties.subnets[*].properties.addressPrefix",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Network/virtualNetworks/subnets[*].name",
|
||||
"defaultPath": "properties.subnets[*].name",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Network/virtualNetworks/subnets[*].ipConfigurations[*].privateIPAddress",
|
||||
"defaultPath": "properties.subnets[*].properties.ipConfigurations[*].properties.privateIPAddress",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Network/virtualNetworks/subnets[*].ipConfigurations[*].name",
|
||||
"defaultPath": "properties.subnets[*].properties.ipConfigurations[*].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"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"namespace": "test",
|
||||
"resourceTypes": [
|
||||
{
|
||||
"resourceType": "resource",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "test/resource/type",
|
||||
"defaultPath": "properties.type",
|
||||
"paths": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"resourceType": "versionedEnvelope",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "test/versionedEnvelope/items[*].status",
|
||||
"defaultPath": "properties.items[*].properties.status",
|
||||
"paths": [
|
||||
{
|
||||
"path": "properties.items[*].status",
|
||||
"apiVersions": ["2025-01-01"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "test/versionedEnvelope/items[*].name",
|
||||
"defaultPath": "properties.items[*].name",
|
||||
"paths": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
cases:
|
||||
- note: restores casing from aliases
|
||||
input:
|
||||
name: myStorage
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
supportshttpstrafficonly: true
|
||||
accesstier: Hot
|
||||
expected_denormalized:
|
||||
name: myStorage
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
properties:
|
||||
supportsHttpsTrafficOnly: true
|
||||
accessTier: Hot
|
||||
reverse_round_trip: true
|
||||
|
||||
- note: versioned path - default
|
||||
input:
|
||||
type: "Microsoft.Web/sites"
|
||||
isenabled: true
|
||||
expected_denormalized:
|
||||
type: "Microsoft.Web/sites"
|
||||
properties:
|
||||
isEnabled: true
|
||||
reverse_round_trip: true
|
||||
|
||||
- note: versioned path - matching version
|
||||
input:
|
||||
type: "Microsoft.Web/sites"
|
||||
isenabled: true
|
||||
api_version: "2020-01-01"
|
||||
expected_denormalized:
|
||||
type: "Microsoft.Web/sites"
|
||||
properties:
|
||||
enabled: true
|
||||
reverse_round_trip: true
|
||||
expected_reverse_round_trip:
|
||||
type: "Microsoft.Web/sites"
|
||||
enabled: true
|
||||
isenabled: true
|
||||
|
||||
- note: collision safe key
|
||||
input:
|
||||
type: "test/resource"
|
||||
_p_type: SubType
|
||||
expected_denormalized:
|
||||
type: "test/resource"
|
||||
properties:
|
||||
type: SubType
|
||||
reverse_round_trip: true
|
||||
|
||||
- note: sub-resource array
|
||||
input:
|
||||
name: myNsg
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
securityrules:
|
||||
- name: rule1
|
||||
protocol: Tcp
|
||||
access: Allow
|
||||
- name: rule2
|
||||
protocol: "*"
|
||||
access: Deny
|
||||
expected_denormalized:
|
||||
name: myNsg
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
properties:
|
||||
securityRules:
|
||||
- name: rule1
|
||||
properties:
|
||||
protocol: Tcp
|
||||
access: Allow
|
||||
- name: rule2
|
||||
properties:
|
||||
protocol: "*"
|
||||
access: Deny
|
||||
reverse_round_trip: true
|
||||
|
||||
- note: nested sub-resource arrays
|
||||
input:
|
||||
name: myVnet
|
||||
type: "Microsoft.Network/virtualNetworks"
|
||||
subnets:
|
||||
- name: subnet1
|
||||
addressprefix: "10.0.0.0/24"
|
||||
ipconfigurations:
|
||||
- name: ipconfig1
|
||||
privateipaddress: "10.0.0.4"
|
||||
expected_denormalized:
|
||||
name: myVnet
|
||||
type: "Microsoft.Network/virtualNetworks"
|
||||
properties:
|
||||
subnets:
|
||||
- name: subnet1
|
||||
properties:
|
||||
addressPrefix: "10.0.0.0/24"
|
||||
ipConfigurations:
|
||||
- name: ipconfig1
|
||||
properties:
|
||||
privateIPAddress: "10.0.0.4"
|
||||
reverse_round_trip: true
|
||||
|
||||
- note: root level alias (sku.name)
|
||||
input:
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
sku:
|
||||
name: Standard_LRS
|
||||
expected_denormalized:
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
sku:
|
||||
name: Standard_LRS
|
||||
reverse_round_trip: true
|
||||
|
||||
- note: versioned envelope classification — default (status under properties)
|
||||
resource_type: "test/versionedEnvelope"
|
||||
sub_resource_arrays: ["items"]
|
||||
input:
|
||||
type: "test/versionedEnvelope"
|
||||
items:
|
||||
- name: item1
|
||||
status: active
|
||||
expected_denormalized:
|
||||
type: "test/versionedEnvelope"
|
||||
properties:
|
||||
items:
|
||||
- name: item1
|
||||
properties:
|
||||
status: active
|
||||
|
||||
- note: versioned envelope classification — version promotes status to envelope
|
||||
resource_type: "test/versionedEnvelope"
|
||||
sub_resource_arrays: ["items"]
|
||||
api_version: "2025-01-01"
|
||||
input:
|
||||
type: "test/versionedEnvelope"
|
||||
items:
|
||||
- name: item1
|
||||
status: active
|
||||
expected_denormalized:
|
||||
type: "test/versionedEnvelope"
|
||||
properties:
|
||||
items:
|
||||
- name: item1
|
||||
status: active
|
||||
|
||||
- note: versioned casing restoration from versioned alias paths
|
||||
aliases_json: |
|
||||
[
|
||||
{
|
||||
"namespace": "Microsoft.Test",
|
||||
"resourceTypes": [
|
||||
{
|
||||
"resourceType": "widgets",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "Microsoft.Test/widgets/items[*].rating",
|
||||
"defaultPath": "properties.items[*].properties.oldField",
|
||||
"paths": [
|
||||
{
|
||||
"path": "properties.items[*].properties.RenamedField",
|
||||
"apiVersions": ["2025-06-01"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Test/widgets/items[*].name",
|
||||
"defaultPath": "properties.items[*].name",
|
||||
"paths": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
api_version: "2025-06-01"
|
||||
input:
|
||||
type: "Microsoft.Test/widgets"
|
||||
items:
|
||||
- name: w1
|
||||
rating: 5
|
||||
expected_denormalized:
|
||||
type: "Microsoft.Test/widgets"
|
||||
properties:
|
||||
items:
|
||||
- name: w1
|
||||
properties:
|
||||
RenamedField: 5
|
||||
@@ -0,0 +1,83 @@
|
||||
cases:
|
||||
- note: wraps properties
|
||||
input:
|
||||
name: myStorage
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
location: westus2
|
||||
supportshttpstrafficonly: true
|
||||
ishnsenabled: false
|
||||
expected_denormalized:
|
||||
name: myStorage
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
location: westus2
|
||||
properties:
|
||||
supportshttpstrafficonly: true
|
||||
ishnsenabled: false
|
||||
reverse_round_trip: true
|
||||
|
||||
- note: non-object returns clone
|
||||
input: "just a string"
|
||||
expected_denormalized: "just a string"
|
||||
reverse_round_trip: true
|
||||
|
||||
- note: empty normalized
|
||||
input: {}
|
||||
expected_denormalized: {}
|
||||
reverse_round_trip: true
|
||||
|
||||
- note: preserves root fields
|
||||
input:
|
||||
name: r
|
||||
type: t
|
||||
location: l
|
||||
kind: k
|
||||
id: "/sub/rg/r"
|
||||
tags:
|
||||
env: prod
|
||||
identity:
|
||||
type: SystemAssigned
|
||||
principalid: pid-123
|
||||
userassignedidentities:
|
||||
/subscriptions/Sub/resourceGroups/Rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Uai1: {}
|
||||
sku:
|
||||
name: Basic
|
||||
plan:
|
||||
name: p1
|
||||
zones: ["1", "2"]
|
||||
managedby: "/sub/other"
|
||||
etag: "W/\"abc\""
|
||||
apiversion: "2023-01-01"
|
||||
fullname: parent/child
|
||||
systemdata:
|
||||
createdby: admin
|
||||
extendedlocation:
|
||||
name: edge1
|
||||
type: EdgeZone
|
||||
expected_denormalized:
|
||||
name: r
|
||||
type: t
|
||||
location: l
|
||||
kind: k
|
||||
id: "/sub/rg/r"
|
||||
tags:
|
||||
env: prod
|
||||
identity:
|
||||
type: SystemAssigned
|
||||
principalId: pid-123
|
||||
userAssignedIdentities:
|
||||
/subscriptions/Sub/resourceGroups/Rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Uai1: {}
|
||||
sku:
|
||||
name: Basic
|
||||
plan:
|
||||
name: p1
|
||||
zones: ["1", "2"]
|
||||
managedBy: "/sub/other"
|
||||
etag: "W/\"abc\""
|
||||
apiVersion: "2023-01-01"
|
||||
fullName: parent/child
|
||||
systemData:
|
||||
createdBy: admin
|
||||
extendedLocation:
|
||||
name: edge1
|
||||
type: EdgeZone
|
||||
reverse_round_trip: true
|
||||
@@ -0,0 +1,201 @@
|
||||
# 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
|
||||
@@ -0,0 +1,46 @@
|
||||
aliases_json: |
|
||||
[
|
||||
{
|
||||
"namespace": "Microsoft.Network",
|
||||
"resourceTypes": [
|
||||
{
|
||||
"resourceType": "networkSecurityGroups",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].protocol",
|
||||
"defaultPath": "properties.securityRules[*].properties.protocol",
|
||||
"paths": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
cases:
|
||||
- note: normalize-and-wrap full pipeline
|
||||
input:
|
||||
name: myNsg
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
properties:
|
||||
securityRules:
|
||||
- name: rule1
|
||||
properties:
|
||||
protocol: Tcp
|
||||
context:
|
||||
resourceGroup:
|
||||
name: rg1
|
||||
parameters:
|
||||
env: prod
|
||||
expected_envelope:
|
||||
resource:
|
||||
name: myNsg
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
securityrules:
|
||||
- name: rule1
|
||||
protocol: Tcp
|
||||
context:
|
||||
resourceGroup:
|
||||
name: rg1
|
||||
parameters:
|
||||
env: prod
|
||||
@@ -0,0 +1,233 @@
|
||||
# Tests for malformed or unexpected *normalized* input fed to the
|
||||
# denormalizer. These pin the current behavior when the denormalizer
|
||||
# receives externally-produced JSON that violates the normalizer's
|
||||
# implicit contract (e.g., keys not lowercased, wrong value types,
|
||||
# missing type field, non-object input).
|
||||
#
|
||||
# These are NOT correctness assertions -- they document what the
|
||||
# denormalizer actually produces so regressions are caught.
|
||||
|
||||
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[*].name",
|
||||
"defaultPath": "properties.securityRules[*].name",
|
||||
"paths": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
cases:
|
||||
# ── Non-object input ──
|
||||
# The denormalizer should return the value as-is when it's not an object.
|
||||
|
||||
- note: "denormalize: string input returned as-is"
|
||||
input: "just a string"
|
||||
expected_denormalized: "just a string"
|
||||
|
||||
- note: "denormalize: integer input returned as-is"
|
||||
input: 42
|
||||
expected_denormalized: 42
|
||||
|
||||
- note: "denormalize: array input returned as-is"
|
||||
input: [1, 2, 3]
|
||||
expected_denormalized: [1, 2, 3]
|
||||
|
||||
- note: "denormalize: null input returned as-is"
|
||||
input: null
|
||||
expected_denormalized: null
|
||||
|
||||
- note: "denormalize: boolean input returned as-is"
|
||||
input: true
|
||||
expected_denormalized: true
|
||||
|
||||
# ── Missing type field ──
|
||||
# Without a type, no alias resolution occurs; fields are still placed
|
||||
# under `properties` (control-plane default) but casing is not restored.
|
||||
|
||||
- note: "denormalize: no type field, fields go under properties"
|
||||
input:
|
||||
name: test
|
||||
unknownfield: 42
|
||||
expected_denormalized:
|
||||
name: test
|
||||
properties:
|
||||
unknownfield: 42
|
||||
|
||||
# ── Empty object ──
|
||||
|
||||
- note: "denormalize: empty object produces empty object"
|
||||
input: {}
|
||||
expected_denormalized: {}
|
||||
|
||||
# ── Object with only type field ──
|
||||
|
||||
- note: "denormalize: only type field, empty properties"
|
||||
input:
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
expected_denormalized:
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
|
||||
# ── Normalize: non-object input ──
|
||||
|
||||
- note: "normalize: string input returned as-is"
|
||||
input: "just a string"
|
||||
expected_normalized: "just a string"
|
||||
|
||||
- note: "normalize: integer input returned as-is"
|
||||
input: 42
|
||||
expected_normalized: 42
|
||||
|
||||
- note: "normalize: null input returned as-is"
|
||||
input: null
|
||||
expected_normalized: null
|
||||
|
||||
# ── Normalize: empty properties object ──
|
||||
|
||||
- note: "normalize: empty properties object"
|
||||
input:
|
||||
name: test
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
properties: {}
|
||||
expected_normalized:
|
||||
name: test
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
|
||||
# ── Normalize: properties is a non-object value ──
|
||||
|
||||
- note: "normalize: properties is a string (treated as non-object, ignored)"
|
||||
input:
|
||||
name: test
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
properties: "not an object"
|
||||
expected_normalized:
|
||||
name: test
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
|
||||
- note: "normalize: properties is an array (treated as non-object, ignored)"
|
||||
input:
|
||||
name: test
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
properties: [1, 2, 3]
|
||||
expected_normalized:
|
||||
name: test
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
|
||||
# ── Denormalize: value types preserved through round trip ──
|
||||
|
||||
- note: "denormalize: numeric value zero"
|
||||
input:
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
accesstier: 0
|
||||
expected_denormalized:
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
properties:
|
||||
accessTier: 0
|
||||
|
||||
- note: "denormalize: boolean false aliased field"
|
||||
input:
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
supportshttpstrafficonly: false
|
||||
expected_denormalized:
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
properties:
|
||||
supportsHttpsTrafficOnly: false
|
||||
|
||||
- note: "denormalize: null-valued aliased field"
|
||||
input:
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
supportshttpstrafficonly: null
|
||||
expected_denormalized:
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
properties:
|
||||
supportsHttpsTrafficOnly: null
|
||||
|
||||
# ── Sub-resource array with non-array value ──
|
||||
|
||||
- note: "denormalize: expected sub-resource array is a string"
|
||||
input:
|
||||
name: myNsg
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
securityrules: "not an array"
|
||||
expected_denormalized:
|
||||
name: myNsg
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
properties:
|
||||
securityRules: "not an array"
|
||||
|
||||
- note: "denormalize: expected sub-resource array is null"
|
||||
input:
|
||||
name: myNsg
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
securityrules: null
|
||||
expected_denormalized:
|
||||
name: myNsg
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
properties:
|
||||
securityRules: null
|
||||
|
||||
# ── Sub-resource array with non-object elements ──
|
||||
|
||||
- note: "denormalize: sub-resource contains scalar elements"
|
||||
input:
|
||||
name: myNsg
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
securityrules:
|
||||
- "just a string"
|
||||
- 42
|
||||
expected_denormalized:
|
||||
name: myNsg
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
properties:
|
||||
securityRules:
|
||||
- "just a string"
|
||||
- 42
|
||||
|
||||
# ── Deeply nested null in normalize path ──
|
||||
|
||||
- note: "normalize: deeply nested null value preserved"
|
||||
input:
|
||||
name: test
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
properties:
|
||||
supportsHttpsTrafficOnly: null
|
||||
accessTier: null
|
||||
expected_normalized:
|
||||
name: test
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
supportshttpstrafficonly: null
|
||||
accesstier: null
|
||||
@@ -0,0 +1,258 @@
|
||||
cases:
|
||||
- note: flattens root properties
|
||||
input:
|
||||
name: myStorage
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
location: westus2
|
||||
properties:
|
||||
supportsHttpsTrafficOnly: true
|
||||
isHnsEnabled: false
|
||||
expected_normalized:
|
||||
name: myStorage
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
location: westus2
|
||||
supportshttpstrafficonly: true
|
||||
ishnsenabled: false
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
name: myStorage
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
location: westus2
|
||||
properties:
|
||||
supportshttpstrafficonly: true
|
||||
ishnsenabled: false
|
||||
|
||||
- note: preserves root level precedence
|
||||
input:
|
||||
name: root-name
|
||||
properties:
|
||||
name: props-name
|
||||
expected_normalized:
|
||||
name: root-name
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
name: root-name
|
||||
|
||||
- note: leaves plain arrays alone
|
||||
input:
|
||||
name: test
|
||||
properties:
|
||||
networkAcls:
|
||||
ipRules:
|
||||
- value: "10.0.0.1"
|
||||
action: Allow
|
||||
- value: "10.0.0.2"
|
||||
action: Deny
|
||||
expected_normalized:
|
||||
name: test
|
||||
networkacls:
|
||||
iprules:
|
||||
- value: "10.0.0.1"
|
||||
action: Allow
|
||||
- value: "10.0.0.2"
|
||||
action: Deny
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
name: test
|
||||
properties:
|
||||
networkacls:
|
||||
iprules:
|
||||
- value: "10.0.0.1"
|
||||
action: Allow
|
||||
- value: "10.0.0.2"
|
||||
action: Deny
|
||||
|
||||
- note: handles sku at root
|
||||
input:
|
||||
name: test
|
||||
sku:
|
||||
name: Standard_LRS
|
||||
tier: Standard
|
||||
properties:
|
||||
supportsHttpsTrafficOnly: true
|
||||
expected_normalized:
|
||||
name: test
|
||||
sku:
|
||||
name: Standard_LRS
|
||||
tier: Standard
|
||||
supportshttpstrafficonly: true
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
name: test
|
||||
sku:
|
||||
name: Standard_LRS
|
||||
tier: Standard
|
||||
properties:
|
||||
supportshttpstrafficonly: true
|
||||
|
||||
- note: non-object returns clone
|
||||
input: "just a string"
|
||||
expected_normalized: "just a string"
|
||||
round_trip: true
|
||||
|
||||
- note: empty properties
|
||||
input:
|
||||
name: test
|
||||
properties: {}
|
||||
expected_normalized:
|
||||
name: test
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
name: test
|
||||
|
||||
- note: missing properties
|
||||
input:
|
||||
name: test
|
||||
location: eastus
|
||||
expected_normalized:
|
||||
name: test
|
||||
location: eastus
|
||||
round_trip: true
|
||||
|
||||
- note: preserves all root fields
|
||||
input:
|
||||
name: r
|
||||
type: t
|
||||
location: l
|
||||
kind: k
|
||||
id: "/sub/rg/r"
|
||||
tags:
|
||||
env: prod
|
||||
identity:
|
||||
type: SystemAssigned
|
||||
principalId: pid-123
|
||||
userAssignedIdentities:
|
||||
/subscriptions/Sub/resourceGroups/Rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Uai1: {}
|
||||
sku:
|
||||
name: Basic
|
||||
plan:
|
||||
name: p1
|
||||
zones: ["1", "2"]
|
||||
managedBy: "/sub/other"
|
||||
etag: "W/\"abc\""
|
||||
apiVersion: "2023-01-01"
|
||||
fullName: parent/child
|
||||
systemData:
|
||||
createdBy: admin
|
||||
extendedLocation:
|
||||
name: edge1
|
||||
type: EdgeZone
|
||||
properties:
|
||||
someProp: true
|
||||
expected_normalized:
|
||||
name: r
|
||||
type: t
|
||||
location: l
|
||||
kind: k
|
||||
id: "/sub/rg/r"
|
||||
tags:
|
||||
env: prod
|
||||
identity:
|
||||
type: SystemAssigned
|
||||
principalid: pid-123
|
||||
userassignedidentities:
|
||||
/subscriptions/Sub/resourceGroups/Rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Uai1: {}
|
||||
sku:
|
||||
name: Basic
|
||||
plan:
|
||||
name: p1
|
||||
zones: ["1", "2"]
|
||||
managedby: "/sub/other"
|
||||
etag: "W/\"abc\""
|
||||
apiversion: "2023-01-01"
|
||||
fullname: parent/child
|
||||
systemdata:
|
||||
createdby: admin
|
||||
extendedlocation:
|
||||
name: edge1
|
||||
type: EdgeZone
|
||||
someprop: true
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
name: r
|
||||
type: t
|
||||
location: l
|
||||
kind: k
|
||||
id: "/sub/rg/r"
|
||||
tags:
|
||||
env: prod
|
||||
identity:
|
||||
type: SystemAssigned
|
||||
principalId: pid-123
|
||||
userAssignedIdentities:
|
||||
/subscriptions/Sub/resourceGroups/Rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Uai1: {}
|
||||
sku:
|
||||
name: Basic
|
||||
plan:
|
||||
name: p1
|
||||
zones: ["1", "2"]
|
||||
managedBy: "/sub/other"
|
||||
etag: "W/\"abc\""
|
||||
apiVersion: "2023-01-01"
|
||||
fullName: parent/child
|
||||
systemData:
|
||||
createdBy: admin
|
||||
extendedLocation:
|
||||
name: edge1
|
||||
type: EdgeZone
|
||||
properties:
|
||||
someprop: true
|
||||
|
||||
- note: primitive array pass through
|
||||
input:
|
||||
name: test
|
||||
properties:
|
||||
allowedIPs: ["10.0.0.1", "10.0.0.2", "10.0.0.3"]
|
||||
expected_normalized:
|
||||
name: test
|
||||
allowedips: ["10.0.0.1", "10.0.0.2", "10.0.0.3"]
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
name: test
|
||||
properties:
|
||||
allowedips: ["10.0.0.1", "10.0.0.2", "10.0.0.3"]
|
||||
|
||||
- note: deeply nested object no sub-resource
|
||||
input:
|
||||
name: test
|
||||
properties:
|
||||
networkAcls:
|
||||
defaultAction: Deny
|
||||
virtualNetworkRules:
|
||||
- id: "/vnet/subnet1"
|
||||
action: Allow
|
||||
expected_normalized:
|
||||
name: test
|
||||
networkacls:
|
||||
defaultaction: Deny
|
||||
virtualnetworkrules:
|
||||
- id: "/vnet/subnet1"
|
||||
action: Allow
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
name: test
|
||||
properties:
|
||||
networkacls:
|
||||
defaultaction: Deny
|
||||
virtualnetworkrules:
|
||||
- id: "/vnet/subnet1"
|
||||
action: Allow
|
||||
|
||||
- note: unknown root fields are dropped (not in ROOT_FIELDS)
|
||||
input:
|
||||
name: test
|
||||
type: "Microsoft.Foo/bars"
|
||||
fooExtension:
|
||||
barBaz: 1
|
||||
properties:
|
||||
enabled: true
|
||||
expected_normalized:
|
||||
name: test
|
||||
type: "Microsoft.Foo/bars"
|
||||
enabled: true
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
name: test
|
||||
type: "Microsoft.Foo/bars"
|
||||
properties:
|
||||
enabled: true
|
||||
@@ -0,0 +1,26 @@
|
||||
cases:
|
||||
- note: envelope defaults
|
||||
input:
|
||||
name: x
|
||||
expected_envelope:
|
||||
resource:
|
||||
name: x
|
||||
context: {}
|
||||
parameters: {}
|
||||
|
||||
- note: envelope with context and parameters
|
||||
input:
|
||||
name: x
|
||||
context:
|
||||
resourceGroup:
|
||||
name: rg1
|
||||
parameters:
|
||||
env: prod
|
||||
expected_envelope:
|
||||
resource:
|
||||
name: x
|
||||
context:
|
||||
resourceGroup:
|
||||
name: rg1
|
||||
parameters:
|
||||
env: prod
|
||||
@@ -0,0 +1,123 @@
|
||||
cases:
|
||||
- note: flattens sub-resource arrays
|
||||
input:
|
||||
name: myNsg
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
properties:
|
||||
securityRules:
|
||||
- name: rule1
|
||||
properties:
|
||||
protocol: Tcp
|
||||
access: Allow
|
||||
- name: rule2
|
||||
properties:
|
||||
protocol: "*"
|
||||
access: Deny
|
||||
sub_resource_arrays: ["securityRules"]
|
||||
resource_type: "Microsoft.Network/networkSecurityGroups"
|
||||
expected_normalized:
|
||||
name: myNsg
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
securityrules:
|
||||
- name: rule1
|
||||
protocol: Tcp
|
||||
access: Allow
|
||||
- name: rule2
|
||||
protocol: "*"
|
||||
access: Deny
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
name: myNsg
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
properties:
|
||||
securityrules:
|
||||
- name: rule1
|
||||
properties:
|
||||
protocol: Tcp
|
||||
access: Allow
|
||||
- name: rule2
|
||||
properties:
|
||||
protocol: "*"
|
||||
access: Deny
|
||||
|
||||
- note: nested sub-resource arrays
|
||||
input:
|
||||
name: myVnet
|
||||
properties:
|
||||
subnets:
|
||||
- name: subnet1
|
||||
properties:
|
||||
addressPrefix: "10.0.0.0/24"
|
||||
ipConfigurations:
|
||||
- name: ipconfig1
|
||||
properties:
|
||||
privateIPAddress: "10.0.0.4"
|
||||
sub_resource_arrays: ["subnets", "subnets.ipConfigurations"]
|
||||
resource_type: "Microsoft.Network/virtualNetworks"
|
||||
expected_normalized:
|
||||
name: myVnet
|
||||
subnets:
|
||||
- name: subnet1
|
||||
addressprefix: "10.0.0.0/24"
|
||||
ipconfigurations:
|
||||
- name: ipconfig1
|
||||
privateipaddress: "10.0.0.4"
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
name: myVnet
|
||||
properties:
|
||||
subnets:
|
||||
- name: subnet1
|
||||
properties:
|
||||
addressprefix: "10.0.0.0/24"
|
||||
ipconfigurations:
|
||||
- name: ipconfig1
|
||||
properties:
|
||||
privateipaddress: "10.0.0.4"
|
||||
|
||||
- note: sub-resource element without properties
|
||||
input:
|
||||
name: test
|
||||
properties:
|
||||
items:
|
||||
- name: plain-object
|
||||
enabled: true
|
||||
sub_resource_arrays: ["items"]
|
||||
resource_type: test
|
||||
expected_normalized:
|
||||
name: test
|
||||
items:
|
||||
- name: plain-object
|
||||
enabled: true
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
name: test
|
||||
properties:
|
||||
items:
|
||||
- name: plain-object
|
||||
properties:
|
||||
enabled: true
|
||||
|
||||
- note: case-insensitive sub-resource match
|
||||
input:
|
||||
name: test
|
||||
properties:
|
||||
securityRules:
|
||||
- name: r1
|
||||
properties:
|
||||
protocol: Tcp
|
||||
sub_resource_arrays: ["SecurityRules"]
|
||||
resource_type: test
|
||||
expected_normalized:
|
||||
name: test
|
||||
securityrules:
|
||||
- name: r1
|
||||
protocol: Tcp
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
name: test
|
||||
properties:
|
||||
securityrules:
|
||||
- name: r1
|
||||
properties:
|
||||
protocol: Tcp
|
||||
@@ -0,0 +1,127 @@
|
||||
# Tests that exercise the public AliasRegistry API (normalize, denormalize,
|
||||
# normalize_and_wrap) rather than the low-level *_with_aliases functions.
|
||||
# This ensures the registry lookup path and public entry points are covered.
|
||||
|
||||
aliases_json: |
|
||||
[
|
||||
{
|
||||
"namespace": "Microsoft.Storage",
|
||||
"resourceTypes": [
|
||||
{
|
||||
"resourceType": "storageAccounts",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
|
||||
"defaultPath": "properties.supportsHttpsTrafficOnly",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Storage/storageAccounts/isHnsEnabled",
|
||||
"defaultPath": "properties.isHnsEnabled",
|
||||
"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[*].name",
|
||||
"defaultPath": "properties.securityRules[*].name",
|
||||
"paths": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
cases:
|
||||
- note: registry API normalize
|
||||
use_registry_api: true
|
||||
input:
|
||||
name: myStorage
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
location: westus2
|
||||
properties:
|
||||
supportsHttpsTrafficOnly: true
|
||||
isHnsEnabled: false
|
||||
expected_normalized:
|
||||
name: myStorage
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
location: westus2
|
||||
supportshttpstrafficonly: true
|
||||
ishnsenabled: false
|
||||
round_trip: true
|
||||
|
||||
- note: registry API denormalize
|
||||
use_registry_api: true
|
||||
input:
|
||||
name: myStorage
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
location: westus2
|
||||
supportshttpstrafficonly: true
|
||||
ishnsenabled: false
|
||||
expected_denormalized:
|
||||
name: myStorage
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
location: westus2
|
||||
properties:
|
||||
supportsHttpsTrafficOnly: true
|
||||
isHnsEnabled: false
|
||||
reverse_round_trip: true
|
||||
|
||||
- note: registry API normalize_and_wrap (envelope)
|
||||
use_registry_api: true
|
||||
input:
|
||||
name: myNsg
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
properties:
|
||||
securityRules:
|
||||
- name: rule1
|
||||
properties:
|
||||
protocol: Tcp
|
||||
context:
|
||||
resourceGroup:
|
||||
name: rg1
|
||||
expected_envelope:
|
||||
resource:
|
||||
name: myNsg
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
securityrules:
|
||||
- name: rule1
|
||||
protocol: Tcp
|
||||
context:
|
||||
resourceGroup:
|
||||
name: rg1
|
||||
parameters: {}
|
||||
|
||||
- note: registry API round-trip sub-resource
|
||||
use_registry_api: true
|
||||
input:
|
||||
name: myNsg
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
properties:
|
||||
securityRules:
|
||||
- name: rule1
|
||||
properties:
|
||||
protocol: Tcp
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
name: myNsg
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
properties:
|
||||
securityRules:
|
||||
- name: rule1
|
||||
properties:
|
||||
protocol: Tcp
|
||||
@@ -0,0 +1,449 @@
|
||||
aliases_json: |
|
||||
[
|
||||
{
|
||||
"namespace": "Microsoft.Storage",
|
||||
"resourceTypes": [
|
||||
{
|
||||
"resourceType": "storageAccounts",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
|
||||
"defaultPath": "properties.supportsHttpsTrafficOnly",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Storage/storageAccounts/isHnsEnabled",
|
||||
"defaultPath": "properties.isHnsEnabled",
|
||||
"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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
cases:
|
||||
- note: round-trip simple resource
|
||||
input:
|
||||
name: myStorage
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
location: westus2
|
||||
sku:
|
||||
name: Standard_LRS
|
||||
properties:
|
||||
supportsHttpsTrafficOnly: true
|
||||
isHnsEnabled: false
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
name: myStorage
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
location: westus2
|
||||
sku:
|
||||
name: Standard_LRS
|
||||
properties:
|
||||
supportsHttpsTrafficOnly: true
|
||||
isHnsEnabled: false
|
||||
|
||||
- note: round-trip sub-resource
|
||||
input:
|
||||
name: myNsg
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
properties:
|
||||
securityRules:
|
||||
- name: rule1
|
||||
properties:
|
||||
protocol: Tcp
|
||||
access: Allow
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
name: myNsg
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
properties:
|
||||
securityRules:
|
||||
- name: rule1
|
||||
properties:
|
||||
protocol: Tcp
|
||||
access: Allow
|
||||
|
||||
- note: round-trip versioned array alias (element field remap)
|
||||
aliases_json: |
|
||||
[
|
||||
{
|
||||
"namespace": "Microsoft.Network",
|
||||
"resourceTypes": [
|
||||
{
|
||||
"resourceType": "firewallPolicies",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "Microsoft.Network/firewallPolicies/rules[*].priority",
|
||||
"defaultPath": "properties.rules[*].properties.priority",
|
||||
"paths": [
|
||||
{
|
||||
"path": "properties.rules[*].properties.prio",
|
||||
"apiVersions": ["2021-01-01"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Network/firewallPolicies/rules[*].name",
|
||||
"defaultPath": "properties.rules[*].name",
|
||||
"paths": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
input:
|
||||
name: myPolicy
|
||||
type: "Microsoft.Network/firewallPolicies"
|
||||
properties:
|
||||
rules:
|
||||
- name: rule1
|
||||
properties:
|
||||
prio: 100
|
||||
api_version: "2021-01-01"
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
name: myPolicy
|
||||
type: "Microsoft.Network/firewallPolicies"
|
||||
properties:
|
||||
rules:
|
||||
- name: rule1
|
||||
properties:
|
||||
prio: 100
|
||||
|
||||
# Regression: default and versioned paths both produce exactly one element
|
||||
# remap (same count), but the source field differs. A length-only comparison
|
||||
# would silently reuse the default aggregate, producing incorrect results for
|
||||
# the versioned API version.
|
||||
- note: round-trip versioned remap with same count but different source field
|
||||
aliases_json: |
|
||||
[
|
||||
{
|
||||
"namespace": "Microsoft.Network",
|
||||
"resourceTypes": [
|
||||
{
|
||||
"resourceType": "firewallPolicies",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "Microsoft.Network/firewallPolicies/rules[*].priority",
|
||||
"defaultPath": "properties.rules[*].properties.oldA",
|
||||
"paths": [
|
||||
{
|
||||
"path": "properties.rules[*].properties.oldB",
|
||||
"apiVersions": ["2023-06-01"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Network/firewallPolicies/rules[*].name",
|
||||
"defaultPath": "properties.rules[*].name",
|
||||
"paths": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
input:
|
||||
name: myPolicy
|
||||
type: "Microsoft.Network/firewallPolicies"
|
||||
properties:
|
||||
rules:
|
||||
- name: rule1
|
||||
properties:
|
||||
oldB: 100
|
||||
api_version: "2023-06-01"
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
name: myPolicy
|
||||
type: "Microsoft.Network/firewallPolicies"
|
||||
properties:
|
||||
rules:
|
||||
- name: rule1
|
||||
properties:
|
||||
oldB: 100
|
||||
|
||||
- note: round-trip systemData and extendedLocation
|
||||
input:
|
||||
name: myStorage
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
location: westus2
|
||||
identity:
|
||||
type: SystemAssigned
|
||||
principalId: pid-123
|
||||
userAssignedIdentities:
|
||||
/subscriptions/Sub/resourceGroups/Rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Uai1: {}
|
||||
systemData:
|
||||
createdBy: user@example.com
|
||||
createdByType: User
|
||||
createdAt: "2023-01-01T00:00:00Z"
|
||||
extendedLocation:
|
||||
name: edge-site-1
|
||||
type: EdgeZone
|
||||
properties:
|
||||
supportsHttpsTrafficOnly: true
|
||||
expected_normalized:
|
||||
name: myStorage
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
location: westus2
|
||||
identity:
|
||||
type: SystemAssigned
|
||||
principalid: pid-123
|
||||
userassignedidentities:
|
||||
/subscriptions/Sub/resourceGroups/Rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Uai1: {}
|
||||
systemdata:
|
||||
createdby: user@example.com
|
||||
createdbytype: User
|
||||
createdat: "2023-01-01T00:00:00Z"
|
||||
extendedlocation:
|
||||
name: edge-site-1
|
||||
type: EdgeZone
|
||||
supportshttpstrafficonly: true
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
name: myStorage
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
location: westus2
|
||||
identity:
|
||||
type: SystemAssigned
|
||||
principalId: pid-123
|
||||
userAssignedIdentities:
|
||||
/subscriptions/Sub/resourceGroups/Rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Uai1: {}
|
||||
systemData:
|
||||
createdBy: user@example.com
|
||||
createdByType: User
|
||||
createdAt: "2023-01-01T00:00:00Z"
|
||||
extendedLocation:
|
||||
name: edge-site-1
|
||||
type: EdgeZone
|
||||
properties:
|
||||
supportsHttpsTrafficOnly: true
|
||||
|
||||
# Regression: element remap must remove the stale ARM source field from the
|
||||
# normalized output. Without cleanup, both the alias short name AND the
|
||||
# original ARM leaf key survive; casing restoration during denormalization
|
||||
# then produces a duplicate key (e.g. both "prio" and "priority").
|
||||
- note: element remap removes stale ARM source field
|
||||
aliases_json: |
|
||||
[
|
||||
{
|
||||
"namespace": "Microsoft.Network",
|
||||
"resourceTypes": [
|
||||
{
|
||||
"resourceType": "firewallPolicies",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "Microsoft.Network/firewallPolicies/rules[*].priority",
|
||||
"defaultPath": "properties.rules[*].properties.prio",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Network/firewallPolicies/rules[*].name",
|
||||
"defaultPath": "properties.rules[*].name",
|
||||
"paths": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
input:
|
||||
name: myPolicy
|
||||
type: "Microsoft.Network/firewallPolicies"
|
||||
properties:
|
||||
rules:
|
||||
- name: rule1
|
||||
properties:
|
||||
prio: 42
|
||||
# Normalize: "prio" (ARM leaf) → "priority" (alias short name).
|
||||
# The stale "prio" key must be removed from the normalized element.
|
||||
expected_normalized:
|
||||
name: myPolicy
|
||||
type: "Microsoft.Network/firewallPolicies"
|
||||
rules:
|
||||
- name: rule1
|
||||
priority: 42
|
||||
# Round-trip: normalize → denormalize should restore ARM structure.
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
name: myPolicy
|
||||
type: "Microsoft.Network/firewallPolicies"
|
||||
properties:
|
||||
rules:
|
||||
- name: rule1
|
||||
properties:
|
||||
prio: 42
|
||||
|
||||
# Regression: same stale-field-cleanup scenario but with a dotted ARM leaf
|
||||
# path (e.g. "config.value" rather than a single segment "prio").
|
||||
# The remove_element_field helper must navigate to the parent object and
|
||||
# remove the leaf key via remove_at_dotted_path.
|
||||
- note: element remap removes stale dotted ARM source field
|
||||
aliases_json: |
|
||||
[
|
||||
{
|
||||
"namespace": "Microsoft.Test",
|
||||
"resourceTypes": [
|
||||
{
|
||||
"resourceType": "widgets",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "Microsoft.Test/widgets/items[*].rating",
|
||||
"defaultPath": "properties.items[*].properties.config.score",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Test/widgets/items[*].name",
|
||||
"defaultPath": "properties.items[*].name",
|
||||
"paths": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
input:
|
||||
type: "Microsoft.Test/widgets"
|
||||
properties:
|
||||
items:
|
||||
- name: w1
|
||||
properties:
|
||||
config:
|
||||
score: 5
|
||||
# Normalize: "config.score" (dotted ARM leaf) → "rating" (alias short name).
|
||||
# The stale "config.score" path must be removed; only "rating" should remain.
|
||||
expected_normalized:
|
||||
type: "Microsoft.Test/widgets"
|
||||
items:
|
||||
- name: w1
|
||||
config: {}
|
||||
rating: 5
|
||||
# Round-trip: normalize → denormalize should restore ARM structure.
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
type: "Microsoft.Test/widgets"
|
||||
properties:
|
||||
items:
|
||||
- name: w1
|
||||
properties:
|
||||
config:
|
||||
score: 5
|
||||
|
||||
# Regression: array base rename must move (not clone) the value so that
|
||||
# the stale ARM base key does not survive in the normalized output.
|
||||
# Without the removal, denormalization produces a duplicate key.
|
||||
- note: array base rename removes stale ARM base key
|
||||
aliases_json: |
|
||||
[
|
||||
{
|
||||
"namespace": "Microsoft.Test",
|
||||
"resourceTypes": [
|
||||
{
|
||||
"resourceType": "widgets",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "Microsoft.Test/widgets/items[*].label",
|
||||
"defaultPath": "properties.entries[*].label",
|
||||
"paths": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
input:
|
||||
type: "Microsoft.Test/widgets"
|
||||
properties:
|
||||
entries:
|
||||
- label: hello
|
||||
# Normalize: ARM base "entries" → alias base "items".
|
||||
# The stale "entries" key must be removed; only "items" should remain.
|
||||
expected_normalized:
|
||||
type: "Microsoft.Test/widgets"
|
||||
items:
|
||||
- label: hello
|
||||
# Round-trip: normalize → denormalize should restore ARM structure.
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
type: "Microsoft.Test/widgets"
|
||||
properties:
|
||||
entries:
|
||||
- label: hello
|
||||
|
||||
# Regression: denormalize reverse element remap with a dotted ARM target
|
||||
# must preserve restored casing (e.g. "Config.Score") rather than
|
||||
# re-lowercasing it.
|
||||
- note: round-trip dotted element remap preserves ARM casing
|
||||
aliases_json: |
|
||||
[
|
||||
{
|
||||
"namespace": "Microsoft.Test",
|
||||
"resourceTypes": [
|
||||
{
|
||||
"resourceType": "widgets",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "Microsoft.Test/widgets/items[*].rating",
|
||||
"defaultPath": "properties.items[*].properties.Config.Score",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Test/widgets/items[*].name",
|
||||
"defaultPath": "properties.items[*].name",
|
||||
"paths": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
input:
|
||||
type: "Microsoft.Test/widgets"
|
||||
properties:
|
||||
items:
|
||||
- name: w1
|
||||
properties:
|
||||
Config:
|
||||
Score: 9
|
||||
expected_normalized:
|
||||
type: "Microsoft.Test/widgets"
|
||||
items:
|
||||
- name: w1
|
||||
config: {}
|
||||
rating: 9
|
||||
round_trip: true
|
||||
expected_round_trip:
|
||||
type: "Microsoft.Test/widgets"
|
||||
properties:
|
||||
items:
|
||||
- name: w1
|
||||
properties:
|
||||
Config:
|
||||
Score: 9
|
||||
Reference in New Issue
Block a user