mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
feat: add Azure Policy builtins with YAML test suite (#630)
* feat: add Azure Policy builtins with YAML test suite Implement ARM template functions for Azure Policy evaluation: Builtins: - String: indexOf, lastIndexOf, trim, format, split, startsWith, endsWith, padLeft, concat, replace, toLower, toUpper, substring, guid, uniqueString - DateTime: dateTimeAdd, dateTimeFromEpoch, dateTimeToEpoch, addDays - Collection: intersection, union, take, skip, first, last, min, max, range, items, tryGet, tryIndexFromEnd, empty, array, createObject - Encoding: base64, base64ToString, base64ToJson, uri, uriComponent, uriComponentToString, dataUri, dataUriToString - Numeric: int, float, intDiv, intMod - Misc: json, join, bool, string, coalesce, if, getParameter, resolveField - Logic: logicAll, logicAny Key implementation details: - Unicode case-insensitive search via ICU4X case folding with single-pass fold_with_char_map() for indexOf/lastIndexOf - .NET composite formatting (System.String.Format) with alignment, standard and custom datetime format specifiers, numeric format specifiers - DateTime round-trip preserves input shape (Z vs +00:00, T vs space, fractional seconds) when no explicit output format is supplied - Zero-cost as_str() helper borrows directly from Value::String(Rc<str>) - BTreeSet<&Value> in array union avoids redundant cloning Test suite: - 53 YAML test files exercising all builtins via direct BUILTINS registry - Coverage for edge cases: empty inputs, Unicode, fractional seconds, invalid alignment, unknown format specifiers, RFC3339 offset shapes Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com> * fix: address PR review comments - Fix percent_encode to only uppercase hex digits, not entire string - Remove guid/uniqueString (unsupported); delete custom SHA-1 impl - Replace unwrap_or(0) with proper error in format placeholder parsing - Hoist CaseMapper into static CaseMapperBorrowed for zero per-call overhead - Pre-allocate Vec in range() with_capacity - Update bindings/ffi and bindings/ruby Cargo.lock - Fix uri_component test expectations for correct case preservation Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com> * fix: address second round of PR review comments - float(): return Undefined when as_f64() fails instead of leaking the original non-f64 representation - createObject(): reject odd number of arguments with an error (ARM-template parity) - format(): error on unknown numeric format specifiers instead of silently passing through (matches .NET FormatException behavior) - format(): cap alignment width at 10,000 to prevent DoS from user-controlled format strings like {0,1000000000} - Add YAML test cases for all new error behaviors Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com> * fix: address third round of PR review comments - percent_decode: reject incomplete % escapes (e.g. "%", "%2") instead of treating them as literal characters - parse_iso8601_duration: reject leftover digits without a unit designator at T boundary and end-of-input (e.g. "P1", "P1T2H") - yaml_to_value: panic on unsupported YAML numeric representations instead of silently mapping to Null - Revert unused src/languages/mod.rs changes (module is defined inline in lib.rs) Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com> * fix: add missing edge-case tests and fix empty-delimiter panic - fn_split: return input as single-element array for empty string delimiter instead of panicking (Rust's str::split("") panics) - format: add test for F3 higher precision ({0:F3} + 1.23456 → 1.235) - format: add test for N2 float with thousands separator - format: add test for negative index error ({-1}) - split: add test for empty-string delimiter - uri: add tests for query string and fragment in relative URI - createObject: add test for non-string (numeric) keys Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com> * fix: address fourth round of PR review comments - Add MAX_VARIADIC_ARGS (64) constant for variadic builtin arity instead of registering with 0 (logic_all, logic_any, min, max, format, intersection, union, coalesce, createObject); set dateTimeAdd to exact arity 3 - Switch indexOf/lastIndexOf to UTF-16 code-unit indices to match .NET String.IndexOf semantics (track ch.len_utf16() in fold_with_char_map, use encode_utf16().count() for empty-needle lastIndexOf) - Use DateTime::<Utc>::from_timestamp for explicit timezone type - Remove stale docs/azure-policy/casing.md link from module doc - Fix misleading comment in want_error test branch (code bails on Undefined, not accepts it) Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com> --------- Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
f69974dc1b
commit
5b60daabd9
@@ -0,0 +1,46 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.add_days
|
||||
builtin: azure.policy.fn.add_days
|
||||
|
||||
cases:
|
||||
- note: add_one_day
|
||||
args: ["2024-01-15T12:00:00Z", 1]
|
||||
want: "2024-01-16T12:00:00Z"
|
||||
|
||||
- note: add_zero_days
|
||||
args: ["2024-01-15T12:00:00Z", 0]
|
||||
want: "2024-01-15T12:00:00Z"
|
||||
|
||||
- note: subtract_one_day
|
||||
args: ["2024-01-15T12:00:00Z", -1]
|
||||
want: "2024-01-14T12:00:00Z"
|
||||
|
||||
- note: add_thirty_days
|
||||
args: ["2024-01-01T00:00:00Z", 30]
|
||||
want: "2024-01-31T00:00:00Z"
|
||||
|
||||
- note: add_days_across_month
|
||||
args: ["2024-01-31T00:00:00Z", 1]
|
||||
want: "2024-02-01T00:00:00Z"
|
||||
|
||||
- note: add_days_leap_year
|
||||
args: ["2024-02-28T00:00:00Z", 1]
|
||||
want: "2024-02-29T00:00:00Z"
|
||||
|
||||
- note: add_days_non_leap_year
|
||||
args: ["2023-02-28T00:00:00Z", 1]
|
||||
want: "2023-03-01T00:00:00Z"
|
||||
|
||||
- note: invalid_base_datetime
|
||||
args: ["not-a-date", 5]
|
||||
want_undefined: true
|
||||
|
||||
- note: non_integer_days
|
||||
args: ["2024-01-15T12:00:00Z", "abc"]
|
||||
want_undefined: true
|
||||
|
||||
- note: add_365_days
|
||||
args: ["2024-01-01T00:00:00Z", 365]
|
||||
want: "2024-12-31T00:00:00Z"
|
||||
@@ -0,0 +1,34 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.array
|
||||
builtin: azure.policy.fn.array
|
||||
|
||||
cases:
|
||||
- note: array_from_int
|
||||
args: [42]
|
||||
want: [42]
|
||||
|
||||
- note: array_from_string
|
||||
args: ["hello"]
|
||||
want: ["hello"]
|
||||
|
||||
- note: array_from_bool
|
||||
args: [true]
|
||||
want: [true]
|
||||
|
||||
- note: array_from_null
|
||||
args: [null]
|
||||
want: [null]
|
||||
|
||||
- note: array_from_array
|
||||
args: [[1, 2, 3]]
|
||||
want: [1, 2, 3]
|
||||
|
||||
- note: array_from_empty_array
|
||||
args: [[]]
|
||||
want: []
|
||||
|
||||
- note: array_from_object
|
||||
args: [{"a": 1}]
|
||||
want: [{"a": 1}]
|
||||
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.base64 and azure.policy.fn.base64_to_string
|
||||
builtin: azure.policy.fn.base64
|
||||
|
||||
cases:
|
||||
- note: base64_encode_hello
|
||||
args: ["hello"]
|
||||
want: "aGVsbG8="
|
||||
|
||||
- note: base64_encode_empty
|
||||
args: [""]
|
||||
want: ""
|
||||
|
||||
- note: base64_encode_single_char
|
||||
args: ["a"]
|
||||
want: "YQ=="
|
||||
|
||||
- note: base64_encode_two_chars
|
||||
args: ["ab"]
|
||||
want: "YWI="
|
||||
|
||||
- note: base64_encode_three_chars
|
||||
args: ["abc"]
|
||||
want: "YWJj"
|
||||
|
||||
- note: base64_encode_json_object
|
||||
args: ["{\"key\":\"value\"}"]
|
||||
want: "eyJrZXkiOiJ2YWx1ZSJ9"
|
||||
|
||||
- note: base64_encode_numbers
|
||||
args: ["12345"]
|
||||
want: "MTIzNDU="
|
||||
|
||||
- note: base64_encode_special_chars
|
||||
args: ["hello world!"]
|
||||
want: "aGVsbG8gd29ybGQh"
|
||||
@@ -0,0 +1,39 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.base64_to_json
|
||||
builtin: azure.policy.fn.base64_to_json
|
||||
|
||||
cases:
|
||||
- note: base64_to_json_object
|
||||
args: ["eyJrZXkiOiJ2YWx1ZSJ9"]
|
||||
want:
|
||||
key: "value"
|
||||
|
||||
- note: base64_to_json_array
|
||||
args: ["WzEsMiwzXQ=="]
|
||||
want: [1, 2, 3]
|
||||
|
||||
- note: base64_to_json_string
|
||||
args: ["ImhlbGxvIg=="]
|
||||
want: "hello"
|
||||
|
||||
- note: base64_to_json_number
|
||||
args: ["NDI="]
|
||||
want: 42
|
||||
|
||||
- note: base64_to_json_null
|
||||
args: ["bnVsbA=="]
|
||||
want_null: true
|
||||
|
||||
- note: base64_to_json_bool
|
||||
args: ["dHJ1ZQ=="]
|
||||
want: true
|
||||
|
||||
- note: base64_to_json_invalid_base64
|
||||
args: ["!!!"]
|
||||
want_undefined: true
|
||||
|
||||
- note: base64_to_json_invalid_json
|
||||
args: ["bm90LWpzb24="]
|
||||
want_undefined: true
|
||||
@@ -0,0 +1,30 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.base64_to_string
|
||||
builtin: azure.policy.fn.base64_to_string
|
||||
|
||||
cases:
|
||||
- note: base64_decode_hello
|
||||
args: ["aGVsbG8="]
|
||||
want: "hello"
|
||||
|
||||
- note: base64_decode_empty
|
||||
args: [""]
|
||||
want: ""
|
||||
|
||||
- note: base64_decode_single_char
|
||||
args: ["YQ=="]
|
||||
want: "a"
|
||||
|
||||
- note: base64_decode_no_padding
|
||||
args: ["YWJj"]
|
||||
want: "abc"
|
||||
|
||||
- note: base64_decode_json
|
||||
args: ["eyJrZXkiOiJ2YWx1ZSJ9"]
|
||||
want: "{\"key\":\"value\"}"
|
||||
|
||||
- note: base64_decode_invalid
|
||||
args: ["!!!"]
|
||||
want_undefined: true
|
||||
@@ -0,0 +1,70 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.bool
|
||||
builtin: azure.policy.fn.bool
|
||||
|
||||
cases:
|
||||
# ── From booleans ───────────────────────────────────────────────────
|
||||
- note: bool_from_true
|
||||
args: [true]
|
||||
want: true
|
||||
|
||||
- note: bool_from_false
|
||||
args: [false]
|
||||
want: false
|
||||
|
||||
# ── From strings ────────────────────────────────────────────────────
|
||||
- note: bool_from_string_true
|
||||
args: ["true"]
|
||||
want: true
|
||||
|
||||
- note: bool_from_string_false
|
||||
args: ["false"]
|
||||
want: false
|
||||
|
||||
- note: bool_from_string_TRUE
|
||||
args: ["TRUE"]
|
||||
want: true
|
||||
|
||||
- note: bool_from_string_False
|
||||
args: ["False"]
|
||||
want: false
|
||||
|
||||
- note: bool_from_string_1
|
||||
args: ["1"]
|
||||
want: true
|
||||
|
||||
- note: bool_from_string_0
|
||||
args: ["0"]
|
||||
want: false
|
||||
|
||||
- note: bool_from_string_invalid
|
||||
args: ["yes"]
|
||||
want_undefined: true
|
||||
|
||||
- note: bool_from_string_empty
|
||||
args: [""]
|
||||
want_undefined: true
|
||||
|
||||
# ── From numbers ────────────────────────────────────────────────────
|
||||
- note: bool_from_number_1
|
||||
args: [1]
|
||||
want: true
|
||||
|
||||
- note: bool_from_number_0
|
||||
args: [0]
|
||||
want: false
|
||||
|
||||
- note: bool_from_number_negative
|
||||
args: [-1]
|
||||
want: true
|
||||
|
||||
- note: bool_from_number_42
|
||||
args: [42]
|
||||
want: true
|
||||
|
||||
# ── Other types ─────────────────────────────────────────────────────
|
||||
- note: bool_from_null
|
||||
args: [null]
|
||||
want_undefined: true
|
||||
@@ -0,0 +1,42 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.coalesce
|
||||
builtin: azure.policy.fn.coalesce
|
||||
|
||||
cases:
|
||||
- note: coalesce_first_non_null
|
||||
args: [null, null, "hello"]
|
||||
want: "hello"
|
||||
|
||||
- note: coalesce_first_is_value
|
||||
args: ["first", "second"]
|
||||
want: "first"
|
||||
|
||||
- note: coalesce_all_null
|
||||
args: [null, null, null]
|
||||
want_null: true
|
||||
|
||||
- note: coalesce_number
|
||||
args: [null, 42]
|
||||
want: 42
|
||||
|
||||
- note: coalesce_false_is_not_null
|
||||
args: [null, false, "hello"]
|
||||
want: false
|
||||
|
||||
- note: coalesce_zero_is_not_null
|
||||
args: [null, 0, "hello"]
|
||||
want: 0
|
||||
|
||||
- note: coalesce_empty_string_is_not_null
|
||||
args: [null, "", "hello"]
|
||||
want: ""
|
||||
|
||||
- note: coalesce_single_value
|
||||
args: [42]
|
||||
want: 42
|
||||
|
||||
- note: coalesce_single_null
|
||||
args: [null]
|
||||
want_null: true
|
||||
@@ -0,0 +1,34 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.create_object
|
||||
builtin: azure.policy.fn.create_object
|
||||
|
||||
cases:
|
||||
- note: create_object_single_pair
|
||||
args: ["name", "Alice"]
|
||||
want: {"name": "Alice"}
|
||||
|
||||
- note: create_object_two_pairs
|
||||
args: ["name", "Alice", "age", 30]
|
||||
want: {"age": 30, "name": "Alice"}
|
||||
|
||||
- note: create_object_mixed_types
|
||||
args: ["flag", true, "count", 5, "label", "test"]
|
||||
want: {"count": 5, "flag": true, "label": "test"}
|
||||
|
||||
- note: create_object_empty
|
||||
args: []
|
||||
want: {}
|
||||
|
||||
- note: create_object_nested_value
|
||||
args: ["data", {"inner": "value"}]
|
||||
want: {"data": {"inner": "value"}}
|
||||
|
||||
- note: create_object_odd_args_error
|
||||
args: ["key1", "val1", "key2"]
|
||||
want_error: "expected an even number of arguments"
|
||||
|
||||
- note: create_object_numeric_key
|
||||
args: [1, "one", 2, "two"]
|
||||
want: {1: "one", 2: "two"}
|
||||
@@ -0,0 +1,18 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.data_uri and azure.policy.fn.data_uri_to_string
|
||||
builtin: azure.policy.fn.data_uri
|
||||
|
||||
cases:
|
||||
- note: data_uri_hello
|
||||
args: ["hello"]
|
||||
want: "data:text/plain;charset=utf8;base64,aGVsbG8="
|
||||
|
||||
- note: data_uri_empty
|
||||
args: [""]
|
||||
want: "data:text/plain;charset=utf8;base64,"
|
||||
|
||||
- note: data_uri_json
|
||||
args: ["{\"key\":\"value\"}"]
|
||||
want: "data:text/plain;charset=utf8;base64,eyJrZXkiOiJ2YWx1ZSJ9"
|
||||
@@ -0,0 +1,18 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.data_uri_to_string
|
||||
builtin: azure.policy.fn.data_uri_to_string
|
||||
|
||||
cases:
|
||||
- note: data_uri_to_string_hello
|
||||
args: ["data:text/plain;charset=utf8;base64,aGVsbG8="]
|
||||
want: "hello"
|
||||
|
||||
- note: data_uri_to_string_json
|
||||
args: ["data:text/plain;charset=utf8;base64,eyJrZXkiOiJ2YWx1ZSJ9"]
|
||||
want: "{\"key\":\"value\"}"
|
||||
|
||||
- note: data_uri_to_string_invalid
|
||||
args: ["not-a-data-uri"]
|
||||
want_undefined: true
|
||||
@@ -0,0 +1,161 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.date_time_add
|
||||
builtin: azure.policy.fn.date_time_add
|
||||
|
||||
cases:
|
||||
- note: add_one_day
|
||||
args: ["2024-01-15T12:00:00Z", "P1D"]
|
||||
want: "2024-01-16T12:00:00Z"
|
||||
|
||||
- note: add_one_hour
|
||||
args: ["2024-01-15T12:00:00Z", "PT1H"]
|
||||
want: "2024-01-15T13:00:00Z"
|
||||
|
||||
- note: add_thirty_minutes
|
||||
args: ["2024-01-15T12:00:00Z", "PT30M"]
|
||||
want: "2024-01-15T12:30:00Z"
|
||||
|
||||
- note: add_negative_one_day
|
||||
args: ["2024-01-15T12:00:00Z", "-P1D"]
|
||||
want: "2024-01-14T12:00:00Z"
|
||||
|
||||
- note: add_complex_duration
|
||||
args: ["2024-01-15T12:00:00Z", "P1DT2H30M"]
|
||||
want: "2024-01-16T14:30:00Z"
|
||||
|
||||
- note: add_one_year_approx
|
||||
args: ["2024-01-01T00:00:00Z", "P1Y"]
|
||||
want: "2024-12-31T00:00:00Z"
|
||||
|
||||
- note: add_zero_duration
|
||||
args: ["2024-06-15T10:30:00Z", "PT0S"]
|
||||
want: "2024-06-15T10:30:00Z"
|
||||
|
||||
- note: invalid_base_datetime
|
||||
args: ["not-a-date", "P1D"]
|
||||
want_undefined: true
|
||||
|
||||
- note: invalid_duration
|
||||
args: ["2024-01-15T12:00:00Z", "invalid"]
|
||||
want_undefined: true
|
||||
|
||||
- note: datetime_without_timezone
|
||||
args: ["2024-01-15T12:00:00", "P1D"]
|
||||
want: "2024-01-16T12:00:00"
|
||||
|
||||
- note: add_one_week
|
||||
args: ["2024-01-15T00:00:00Z", "P1W"]
|
||||
want: "2024-01-22T00:00:00Z"
|
||||
|
||||
- note: space_separated_datetime
|
||||
args: ["2020-04-07 14:55:59", "P3D"]
|
||||
want: "2020-04-10 14:55:59"
|
||||
|
||||
- note: space_separated_datetime_with_z
|
||||
args: ["2020-04-07 14:55:59Z", "P3D"]
|
||||
want: "2020-04-10 14:55:59Z"
|
||||
|
||||
- note: space_separated_datetime_with_offset
|
||||
args: ["2020-04-07 14:55:59+05:30", "P1D"]
|
||||
want: "2020-04-08 14:55:59+05:30"
|
||||
|
||||
- note: output_format_date_only
|
||||
args: ["2020-04-07T14:55:59Z", "P3Y2M", "yyyy-MM-dd"]
|
||||
want: "2023-06-06"
|
||||
|
||||
- note: output_format_custom
|
||||
args: ["2024-01-15T12:00:00Z", "PT1H", "yyyy-MM-dd HH:mm:ss"]
|
||||
want: "2024-01-15 13:00:00"
|
||||
|
||||
- note: output_format_with_k_utc
|
||||
args: ["2024-01-15T12:00:00Z", "P0D", "yyyy-MM-ddTHH:mm:ssK"]
|
||||
want: "2024-01-15T12:00:00Z"
|
||||
|
||||
# Standard .NET format specifiers
|
||||
- note: output_standard_format_d
|
||||
args: ["2024-01-15T12:30:00Z", "P1D", "d"]
|
||||
want: "01/16/2024"
|
||||
|
||||
- note: output_standard_format_G
|
||||
args: ["2024-01-15T12:30:00Z", "PT1H", "G"]
|
||||
want: "01/15/2024 13:30:00"
|
||||
|
||||
- note: output_standard_format_o
|
||||
args: ["2024-01-15T12:00:00Z", "P0D", "o"]
|
||||
want: "2024-01-15T12:00:00.0000000Z"
|
||||
|
||||
- note: output_standard_format_o_with_nanos
|
||||
args: ["2024-01-15T12:00:00.123456700Z", "P0D", "o"]
|
||||
want: "2024-01-15T12:00:00.1234567Z"
|
||||
|
||||
- note: output_standard_format_u
|
||||
args: ["2024-01-15T12:00:00Z", "P0D", "u"]
|
||||
want: "2024-01-15 12:00:00Z"
|
||||
|
||||
- note: output_standard_format_u_with_offset
|
||||
args: ["2024-01-15T12:00:00+05:30", "P0D", "u"]
|
||||
want: "2024-01-15 06:30:00Z"
|
||||
|
||||
# Fractional seconds preservation (default round-trip, no explicit format)
|
||||
- note: roundtrip_iso_no_tz_with_frac
|
||||
args: ["2024-01-15T12:00:00.123", "P0D"]
|
||||
want: "2024-01-15T12:00:00.123"
|
||||
|
||||
- note: roundtrip_iso_no_tz_with_frac_add
|
||||
args: ["2024-01-15T12:00:00.500", "P1D"]
|
||||
want: "2024-01-16T12:00:00.500"
|
||||
|
||||
- note: roundtrip_space_no_tz_with_frac
|
||||
args: ["2024-01-15 12:00:00.456", "P0D"]
|
||||
want: "2024-01-15 12:00:00.456"
|
||||
|
||||
- note: roundtrip_space_z_with_frac
|
||||
args: ["2024-01-15 12:00:00.789Z", "P0D"]
|
||||
want: "2024-01-15 12:00:00.789Z"
|
||||
|
||||
- note: roundtrip_space_offset_with_frac
|
||||
args: ["2024-01-15 12:00:00.123+05:30", "P0D"]
|
||||
want: "2024-01-15 12:00:00.123+05:30"
|
||||
|
||||
- note: roundtrip_no_frac_stays_clean
|
||||
args: ["2024-01-15T12:00:00", "P0D"]
|
||||
want: "2024-01-15T12:00:00"
|
||||
|
||||
- note: roundtrip_rfc3339_utc_with_frac
|
||||
args: ["2024-01-15T12:00:00.123Z", "P0D"]
|
||||
want: "2024-01-15T12:00:00.123Z"
|
||||
|
||||
- note: roundtrip_rfc3339_offset_with_frac
|
||||
args: ["2024-01-15T12:00:00.456+05:30", "P0D"]
|
||||
want: "2024-01-15T12:00:00.456+05:30"
|
||||
|
||||
- note: roundtrip_rfc3339_utc_no_frac_stays_clean
|
||||
args: ["2024-01-15T12:00:00Z", "P0D"]
|
||||
want: "2024-01-15T12:00:00Z"
|
||||
|
||||
- note: roundtrip_rfc3339_explicit_zero_offset
|
||||
args: ["2024-01-15T12:00:00+00:00", "P0D"]
|
||||
want: "2024-01-15T12:00:00+00:00"
|
||||
|
||||
- note: roundtrip_rfc3339_explicit_zero_offset_with_frac
|
||||
args: ["2024-01-15T12:00:00.500+00:00", "P0D"]
|
||||
want: "2024-01-15T12:00:00.500+00:00"
|
||||
|
||||
# Unknown single-char format specifier is a real error
|
||||
- note: unknown_format_specifier_q
|
||||
args: ["2024-01-15T12:00:00Z", "P0D", "q"]
|
||||
want_error: "unrecognised standard format specifier 'q'"
|
||||
|
||||
- note: output_standard_format_U
|
||||
args: ["2024-01-15T12:00:00+05:30", "P0D", "U"]
|
||||
want: "Monday, 15 January 2024 06:30:00"
|
||||
|
||||
- note: output_standard_format_s
|
||||
args: ["2024-01-15T12:00:00Z", "P0D", "s"]
|
||||
want: "2024-01-15T12:00:00"
|
||||
|
||||
- note: output_standard_format_R
|
||||
args: ["2024-01-15T12:00:00+05:30", "P0D", "R"]
|
||||
want: "Mon, 15 Jan 2024 06:30:00 GMT"
|
||||
@@ -0,0 +1,30 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.date_time_from_epoch
|
||||
builtin: azure.policy.fn.date_time_from_epoch
|
||||
|
||||
cases:
|
||||
- note: unix_epoch_zero
|
||||
args: [0]
|
||||
want: "1970-01-01T00:00:00Z"
|
||||
|
||||
- note: specific_timestamp
|
||||
args: [1705320000]
|
||||
want: "2024-01-15T12:00:00Z"
|
||||
|
||||
- note: negative_timestamp
|
||||
args: [-86400]
|
||||
want: "1969-12-31T00:00:00Z"
|
||||
|
||||
- note: max_reasonable_timestamp
|
||||
args: [4102444800]
|
||||
want: "2100-01-01T00:00:00Z"
|
||||
|
||||
- note: one_second_after_epoch
|
||||
args: [1]
|
||||
want: "1970-01-01T00:00:01Z"
|
||||
|
||||
- note: non_number_arg
|
||||
args: ["hello"]
|
||||
want_undefined: true
|
||||
@@ -0,0 +1,46 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.date_time_to_epoch
|
||||
builtin: azure.policy.fn.date_time_to_epoch
|
||||
|
||||
cases:
|
||||
- note: epoch_zero
|
||||
args: ["1970-01-01T00:00:00Z"]
|
||||
want: 0
|
||||
|
||||
- note: specific_datetime
|
||||
args: ["2024-01-15T12:00:00Z"]
|
||||
want: 1705320000
|
||||
|
||||
- note: datetime_with_offset
|
||||
args: ["2024-01-15T13:00:00+01:00"]
|
||||
want: 1705320000
|
||||
|
||||
- note: before_epoch
|
||||
args: ["1969-12-31T00:00:00Z"]
|
||||
want: -86400
|
||||
|
||||
- note: invalid_datetime
|
||||
args: ["not-a-date"]
|
||||
want_undefined: true
|
||||
|
||||
- note: non_string_arg
|
||||
args: [12345]
|
||||
want_undefined: true
|
||||
|
||||
- note: datetime_without_tz
|
||||
args: ["2024-01-15T12:00:00"]
|
||||
want: 1705320000
|
||||
|
||||
- note: space_separated_datetime
|
||||
args: ["2024-01-15 12:00:00"]
|
||||
want: 1705320000
|
||||
|
||||
- note: space_separated_datetime_with_z
|
||||
args: ["2024-01-15 12:00:00Z"]
|
||||
want: 1705320000
|
||||
|
||||
- note: space_separated_datetime_with_offset
|
||||
args: ["2024-01-15 13:00:00+01:00"]
|
||||
want: 1705320000
|
||||
@@ -0,0 +1,55 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.empty
|
||||
builtin: azure.policy.fn.empty
|
||||
|
||||
cases:
|
||||
# ── Strings ─────────────────────────────────────────────────────────
|
||||
- note: empty_string_true
|
||||
args: [""]
|
||||
want: true
|
||||
|
||||
- note: empty_string_false
|
||||
args: ["hello"]
|
||||
want: false
|
||||
|
||||
- note: empty_string_space
|
||||
args: [" "]
|
||||
want: false
|
||||
|
||||
# ── Arrays ──────────────────────────────────────────────────────────
|
||||
- note: empty_array_true
|
||||
args: [[]]
|
||||
want: true
|
||||
|
||||
- note: empty_array_false
|
||||
args: [[1, 2]]
|
||||
want: false
|
||||
|
||||
# ── Objects ─────────────────────────────────────────────────────────
|
||||
- note: empty_object_true
|
||||
args: [{}]
|
||||
want: true
|
||||
|
||||
- note: empty_object_false
|
||||
args: [{"a": 1}]
|
||||
want: false
|
||||
|
||||
# ── Null / special values ───────────────────────────────────────────
|
||||
- note: empty_null
|
||||
args: [null]
|
||||
want: true
|
||||
|
||||
# ── Numbers and bools (not empty) ──────────────────────────────────
|
||||
- note: empty_number
|
||||
args: [0]
|
||||
want: false
|
||||
|
||||
- note: empty_bool_false
|
||||
args: [false]
|
||||
want: false
|
||||
|
||||
- note: empty_bool_true
|
||||
args: [true]
|
||||
want: false
|
||||
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.ends_with
|
||||
builtin: azure.policy.fn.ends_with
|
||||
|
||||
cases:
|
||||
- note: ends_with_true
|
||||
args: ["hello world", "world"]
|
||||
want: true
|
||||
|
||||
- note: ends_with_false
|
||||
args: ["hello world", "hello"]
|
||||
want: false
|
||||
|
||||
- note: ends_with_case_insensitive
|
||||
args: ["Hello World", "WORLD"]
|
||||
want: true
|
||||
|
||||
- note: ends_with_empty_needle
|
||||
args: ["hello", ""]
|
||||
want: true
|
||||
|
||||
- note: ends_with_empty_haystack
|
||||
args: ["", "hello"]
|
||||
want: false
|
||||
|
||||
- note: ends_with_both_empty
|
||||
args: ["", ""]
|
||||
want: true
|
||||
|
||||
- note: ends_with_exact_match
|
||||
args: ["hello", "hello"]
|
||||
want: true
|
||||
|
||||
- note: ends_with_longer_needle
|
||||
args: ["hi", "hello"]
|
||||
want: false
|
||||
@@ -0,0 +1,44 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.first
|
||||
builtin: azure.policy.fn.first
|
||||
|
||||
cases:
|
||||
# ── Arrays ──────────────────────────────────────────────────────────
|
||||
- note: first_array_single
|
||||
args: [[42]]
|
||||
want: 42
|
||||
|
||||
- note: first_array_multiple
|
||||
args: [[10, 20, 30]]
|
||||
want: 10
|
||||
|
||||
- note: first_array_strings
|
||||
args: [["alpha", "beta", "gamma"]]
|
||||
want: "alpha"
|
||||
|
||||
- note: first_array_mixed
|
||||
args: [[true, 1, "x"]]
|
||||
want: true
|
||||
|
||||
- note: first_array_empty
|
||||
args: [[]]
|
||||
want_null: true
|
||||
|
||||
# ── Strings ─────────────────────────────────────────────────────────
|
||||
- note: first_string
|
||||
args: ["hello"]
|
||||
want: "h"
|
||||
|
||||
- note: first_string_single_char
|
||||
args: ["x"]
|
||||
want: "x"
|
||||
|
||||
- note: first_string_empty
|
||||
args: [""]
|
||||
want: ""
|
||||
|
||||
- note: first_string_unicode
|
||||
args: ["über"]
|
||||
want: "ü"
|
||||
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.float
|
||||
builtin: azure.policy.fn.float
|
||||
|
||||
cases:
|
||||
- note: float_from_int
|
||||
args: [42]
|
||||
want: 42.0
|
||||
|
||||
- note: float_from_float
|
||||
args: [3.14]
|
||||
want: 3.14
|
||||
|
||||
- note: float_from_zero
|
||||
args: [0]
|
||||
want: 0.0
|
||||
|
||||
- note: float_from_negative
|
||||
args: [-5]
|
||||
want: -5.0
|
||||
|
||||
- note: float_from_string
|
||||
args: ["3.14"]
|
||||
want: 3.14
|
||||
|
||||
- note: float_from_string_int
|
||||
args: ["42"]
|
||||
want: 42.0
|
||||
|
||||
- note: float_from_string_invalid
|
||||
args: ["hello"]
|
||||
want_undefined: true
|
||||
|
||||
- note: float_from_null
|
||||
args: [null]
|
||||
want_undefined: true
|
||||
@@ -0,0 +1,131 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.format
|
||||
builtin: azure.policy.fn.format
|
||||
|
||||
cases:
|
||||
- note: format_single_placeholder
|
||||
args: ["Hello {0}!", "world"]
|
||||
want: "Hello world!"
|
||||
|
||||
- note: format_multiple_placeholders
|
||||
args: ["{0} is {1}", "sky", "blue"]
|
||||
want: "sky is blue"
|
||||
|
||||
- note: format_repeated_placeholder
|
||||
args: ["{0} and {0}", "hello"]
|
||||
want: "hello and hello"
|
||||
|
||||
- note: format_number_arg
|
||||
args: ["Count: {0}", 42]
|
||||
want: "Count: 42"
|
||||
|
||||
- note: format_bool_arg
|
||||
args: ["Value: {0}", true]
|
||||
want: "Value: True"
|
||||
|
||||
- note: format_no_placeholders
|
||||
args: ["no placeholders"]
|
||||
want: "no placeholders"
|
||||
|
||||
- note: format_three_args
|
||||
args: ["{0}-{1}-{2}", "a", "b", "c"]
|
||||
want: "a-b-c"
|
||||
|
||||
- note: format_missing_placeholder_error
|
||||
args: ["{0} and {1}", "only_first"]
|
||||
want_error: "placeholder {1} references argument index 1"
|
||||
|
||||
- note: format_empty_template
|
||||
args: [""]
|
||||
want: ""
|
||||
|
||||
- note: format_escaped_braces
|
||||
args: ["{{literal braces}}"]
|
||||
want: "{literal braces}"
|
||||
|
||||
- note: format_mixed_escaped_and_placeholder
|
||||
args: ["{{{0}}}", "value"]
|
||||
want: "{value}"
|
||||
|
||||
- note: format_alignment_right
|
||||
args: ["{0,10}", "hi"]
|
||||
want: " hi"
|
||||
|
||||
- note: format_alignment_left
|
||||
args: ["{0,-10}", "hi"]
|
||||
want: "hi "
|
||||
|
||||
- note: format_numeric_fixed_point
|
||||
args: ["{0:F2}", 3.14159]
|
||||
want: "3.14"
|
||||
|
||||
- note: format_numeric_with_thousands
|
||||
args: ["{0:N0}", 1234567]
|
||||
want: "1,234,567"
|
||||
|
||||
- note: format_float_with_thousands
|
||||
args: ["{0:N2}", 1234.5678]
|
||||
want: "1,234.57"
|
||||
|
||||
- note: format_hex_upper
|
||||
args: ["{0:X}", 255]
|
||||
want: "FF"
|
||||
|
||||
- note: format_hex_lower_padded
|
||||
args: ["{0:x4}", 255]
|
||||
want: "00ff"
|
||||
|
||||
- note: format_decimal_padded
|
||||
args: ["{0:D5}", 42]
|
||||
want: "00042"
|
||||
|
||||
- note: format_percent
|
||||
args: ["{0:P1}", 0.1234]
|
||||
want: "12.3 %"
|
||||
|
||||
- note: format_unmatched_closing_brace
|
||||
args: ["hello } world"]
|
||||
want_error: "unmatched closing brace"
|
||||
|
||||
- note: format_invalid_placeholder_no_index
|
||||
args: ["{abc}"]
|
||||
want_error: "invalid placeholder"
|
||||
|
||||
- note: format_unmatched_opening_brace
|
||||
args: ["{0", "value"]
|
||||
want_error: "unmatched opening brace"
|
||||
|
||||
- note: format_alignment_non_ascii
|
||||
args: ["{0,6}", "café"]
|
||||
want: " café"
|
||||
|
||||
# Invalid alignment clauses (must be rejected)
|
||||
- note: format_alignment_empty
|
||||
args: ["{0,}", "hi"]
|
||||
want_error: "empty alignment value"
|
||||
|
||||
- note: format_alignment_non_numeric
|
||||
args: ["{0,abc}", "hi"]
|
||||
want_error: "invalid alignment"
|
||||
|
||||
- note: format_alignment_trailing_junk
|
||||
args: ["{0, 1x}", "hi"]
|
||||
want_error: "invalid alignment"
|
||||
|
||||
- note: format_unknown_numeric_specifier_error
|
||||
args: ["{0:Z}", 42]
|
||||
want_error: "invalid numeric format specifier"
|
||||
|
||||
- note: format_alignment_width_exceeds_max
|
||||
args: ["{0,1000000000}", "hi"]
|
||||
want_error: "exceeds maximum allowed"
|
||||
|
||||
- note: format_fixed_point_higher_precision
|
||||
args: ["{0:F3}", 1.23456]
|
||||
want: "1.235"
|
||||
|
||||
- note: format_negative_index_error
|
||||
args: ["{-1}", "value"]
|
||||
want_error: "invalid placeholder"
|
||||
@@ -0,0 +1,46 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.get_parameter
|
||||
builtin: azure.policy.get_parameter
|
||||
|
||||
cases:
|
||||
- note: param_from_user_params
|
||||
args:
|
||||
- allowedLocations: ["eastus", "westus"]
|
||||
- allowedLocations: ["centralus"]
|
||||
- "allowedLocations"
|
||||
want: ["eastus", "westus"]
|
||||
|
||||
- note: param_falls_back_to_defaults
|
||||
args:
|
||||
- {}
|
||||
- effect: "Deny"
|
||||
- "effect"
|
||||
want: "Deny"
|
||||
|
||||
- note: param_not_in_either
|
||||
args:
|
||||
- {}
|
||||
- {}
|
||||
- "missing"
|
||||
want_undefined: true
|
||||
|
||||
- note: param_override_default
|
||||
args:
|
||||
- maxAge: 90
|
||||
- maxAge: 365
|
||||
- "maxAge"
|
||||
want: 90
|
||||
|
||||
- note: param_with_nested_value
|
||||
args:
|
||||
- config:
|
||||
enabled: true
|
||||
threshold: 50
|
||||
- config:
|
||||
enabled: false
|
||||
- "config"
|
||||
want:
|
||||
enabled: true
|
||||
threshold: 50
|
||||
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.if (ARM template if() function)
|
||||
builtin: azure.policy.if
|
||||
|
||||
cases:
|
||||
- note: condition_true_returns_when_true
|
||||
args: [true, "yes", "no"]
|
||||
want: "yes"
|
||||
|
||||
- note: condition_false_returns_when_false
|
||||
args: [false, "yes", "no"]
|
||||
want: "no"
|
||||
|
||||
- note: true_with_numbers
|
||||
args: [true, 1, 0]
|
||||
want: 1
|
||||
|
||||
- note: false_with_numbers
|
||||
args: [false, 1, 0]
|
||||
want: 0
|
||||
|
||||
- note: true_with_arrays
|
||||
args: [true, [1, 2], [3, 4]]
|
||||
want: [1, 2]
|
||||
|
||||
- note: false_with_arrays
|
||||
args: [false, [1, 2], [3, 4]]
|
||||
want: [3, 4]
|
||||
|
||||
- note: true_with_null_branches
|
||||
args: [true, null, "fallback"]
|
||||
want_null: true
|
||||
|
||||
- note: false_with_null_branch
|
||||
args: [false, "value", null]
|
||||
want_null: true
|
||||
@@ -0,0 +1,42 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.index_from_end
|
||||
builtin: azure.policy.fn.index_from_end
|
||||
|
||||
cases:
|
||||
- note: last_element
|
||||
args: [["a", "b", "c", "d"], 1]
|
||||
want: "d"
|
||||
|
||||
- note: second_from_end
|
||||
args: [["a", "b", "c", "d"], 2]
|
||||
want: "c"
|
||||
|
||||
- note: third_from_end
|
||||
args: [["a", "b", "c", "d"], 3]
|
||||
want: "b"
|
||||
|
||||
- note: first_element_via_end
|
||||
args: [["a", "b", "c", "d"], 4]
|
||||
want: "a"
|
||||
|
||||
- note: single_element_array
|
||||
args: [["only"], 1]
|
||||
want: "only"
|
||||
|
||||
- note: index_zero_error
|
||||
args: [["a", "b"], 0]
|
||||
want_error: "out of bounds"
|
||||
|
||||
- note: index_exceeds_length
|
||||
args: [["a", "b"], 3]
|
||||
want_error: "out of bounds"
|
||||
|
||||
- note: non_array_arg
|
||||
args: ["not_an_array", 1]
|
||||
want_undefined: true
|
||||
|
||||
- note: numeric_array
|
||||
args: [[10, 20, 30], 1]
|
||||
want: 30
|
||||
@@ -0,0 +1,54 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.index_of
|
||||
builtin: azure.policy.fn.index_of
|
||||
|
||||
cases:
|
||||
- note: index_of_found
|
||||
args: ["hello world", "world"]
|
||||
want: 6
|
||||
|
||||
- note: index_of_beginning
|
||||
args: ["hello", "hello"]
|
||||
want: 0
|
||||
|
||||
- note: index_of_not_found
|
||||
args: ["hello", "xyz"]
|
||||
want: -1
|
||||
|
||||
- note: index_of_empty_needle
|
||||
args: ["hello", ""]
|
||||
want: 0
|
||||
|
||||
- note: index_of_empty_haystack
|
||||
args: ["", "hello"]
|
||||
want: -1
|
||||
|
||||
- note: index_of_both_empty
|
||||
args: ["", ""]
|
||||
want: 0
|
||||
|
||||
- note: index_of_single_char
|
||||
args: ["abcdef", "d"]
|
||||
want: 3
|
||||
|
||||
- note: index_of_first_occurrence
|
||||
args: ["abcabc", "bc"]
|
||||
want: 1
|
||||
|
||||
- note: index_of_case_insensitive
|
||||
args: ["Hello", "hello"]
|
||||
want: 0
|
||||
|
||||
- note: index_of_case_insensitive_mixed
|
||||
args: ["Hello World", "WORLD"]
|
||||
want: 6
|
||||
|
||||
- note: index_of_unicode_char_index
|
||||
args: ["café latte", "latte"]
|
||||
want: 5
|
||||
|
||||
- note: index_of_unicode_case_fold
|
||||
args: ["Straße", "STRASSE"]
|
||||
want: 0
|
||||
@@ -0,0 +1,57 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.int
|
||||
builtin: azure.policy.fn.int
|
||||
|
||||
cases:
|
||||
# ── From numbers ────────────────────────────────────────────────────
|
||||
- note: int_from_integer
|
||||
args: [42]
|
||||
want: 42
|
||||
|
||||
- note: int_from_negative
|
||||
args: [-7]
|
||||
want: -7
|
||||
|
||||
- note: int_from_zero
|
||||
args: [0]
|
||||
want: 0
|
||||
|
||||
- note: int_from_float_truncates
|
||||
args: [3.9]
|
||||
want: 3
|
||||
|
||||
- note: int_from_negative_float
|
||||
args: [-2.7]
|
||||
want: -2
|
||||
|
||||
# ── From strings ────────────────────────────────────────────────────
|
||||
- note: int_from_string_integer
|
||||
args: ["42"]
|
||||
want: 42
|
||||
|
||||
- note: int_from_string_negative
|
||||
args: ["-10"]
|
||||
want: -10
|
||||
|
||||
- note: int_from_string_float
|
||||
args: ["3.14"]
|
||||
want: 3
|
||||
|
||||
- note: int_from_string_invalid
|
||||
args: ["hello"]
|
||||
want_undefined: true
|
||||
|
||||
- note: int_from_string_empty
|
||||
args: [""]
|
||||
want_undefined: true
|
||||
|
||||
# ── Other types ────────────────────────────────────────────────────
|
||||
- note: int_from_bool
|
||||
args: [true]
|
||||
want_undefined: true
|
||||
|
||||
- note: int_from_null
|
||||
args: [null]
|
||||
want_undefined: true
|
||||
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.int_div
|
||||
builtin: azure.policy.fn.int_div
|
||||
|
||||
cases:
|
||||
- note: int_div_basic
|
||||
args: [10, 3]
|
||||
want: 3
|
||||
|
||||
- note: int_div_exact
|
||||
args: [10, 5]
|
||||
want: 2
|
||||
|
||||
- note: int_div_one
|
||||
args: [7, 1]
|
||||
want: 7
|
||||
|
||||
- note: int_div_negative
|
||||
args: [-10, 3]
|
||||
want: -3
|
||||
|
||||
- note: int_div_both_negative
|
||||
args: [-10, -3]
|
||||
want: 3
|
||||
|
||||
- note: int_div_zero_numerator
|
||||
args: [0, 5]
|
||||
want: 0
|
||||
|
||||
- note: int_div_by_zero
|
||||
args: [10, 0]
|
||||
want_undefined: true
|
||||
|
||||
- note: int_div_large
|
||||
args: [1000000, 7]
|
||||
want: 142857
|
||||
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.int_mod
|
||||
builtin: azure.policy.fn.int_mod
|
||||
|
||||
cases:
|
||||
- note: int_mod_basic
|
||||
args: [10, 3]
|
||||
want: 1
|
||||
|
||||
- note: int_mod_exact
|
||||
args: [10, 5]
|
||||
want: 0
|
||||
|
||||
- note: int_mod_one
|
||||
args: [7, 1]
|
||||
want: 0
|
||||
|
||||
- note: int_mod_negative
|
||||
args: [-10, 3]
|
||||
want: -1
|
||||
|
||||
- note: int_mod_both_negative
|
||||
args: [-10, -3]
|
||||
want: -1
|
||||
|
||||
- note: int_mod_zero_numerator
|
||||
args: [0, 5]
|
||||
want: 0
|
||||
|
||||
- note: int_mod_by_zero
|
||||
args: [10, 0]
|
||||
want_undefined: true
|
||||
|
||||
- note: int_mod_large
|
||||
args: [1000000, 7]
|
||||
want: 1
|
||||
@@ -0,0 +1,58 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.intersection
|
||||
builtin: azure.policy.fn.intersection
|
||||
|
||||
cases:
|
||||
# ── Array intersection ──────────────────────────────────────────────
|
||||
- note: intersection_arrays_overlap
|
||||
args: [[1, 2, 3], [2, 3, 4]]
|
||||
want: [2, 3]
|
||||
|
||||
- note: intersection_arrays_no_overlap
|
||||
args: [[1, 2], [3, 4]]
|
||||
want: []
|
||||
|
||||
- note: intersection_arrays_identical
|
||||
args: [[1, 2, 3], [1, 2, 3]]
|
||||
want: [1, 2, 3]
|
||||
|
||||
- note: intersection_arrays_empty_first
|
||||
args: [[], [1, 2]]
|
||||
want: []
|
||||
|
||||
- note: intersection_arrays_empty_second
|
||||
args: [[1, 2], []]
|
||||
want: []
|
||||
|
||||
- note: intersection_arrays_three
|
||||
args: [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6]]
|
||||
want: [3, 4]
|
||||
|
||||
- note: intersection_arrays_strings
|
||||
args: [["a", "b", "c"], ["b", "c", "d"]]
|
||||
want: ["b", "c"]
|
||||
|
||||
# ── Object intersection ─────────────────────────────────────────────
|
||||
# Azure semantics: a key is kept only when it exists in ALL objects
|
||||
# AND the value is the same across all of them.
|
||||
- note: intersection_objects_same_values
|
||||
args: [{"a": 1, "b": 2, "c": 3}, {"b": 2, "c": 3, "d": 4}]
|
||||
want: {"b": 2, "c": 3}
|
||||
|
||||
- note: intersection_objects_different_values
|
||||
args: [{"a": 1, "b": 2, "c": 3}, {"b": 20, "c": 30, "d": 40}]
|
||||
want: {}
|
||||
|
||||
- note: intersection_objects_no_overlap
|
||||
args: [{"a": 1}, {"b": 2}]
|
||||
want: {}
|
||||
|
||||
- note: intersection_objects_identical
|
||||
args: [{"x": 1}, {"x": 1}]
|
||||
want: {"x": 1}
|
||||
|
||||
- note: intersection_objects_partial_value_match
|
||||
args: [{"a": 1, "b": 2}, {"a": 1, "b": 99}]
|
||||
want: {"a": 1}
|
||||
@@ -0,0 +1,65 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.ip_range_contains
|
||||
builtin: azure.policy.fn.ip_range_contains
|
||||
|
||||
cases:
|
||||
# ── IPv4 ────────────────────────────────────────────────────────────
|
||||
- note: ipv4_contains_ip_true
|
||||
args: ["10.0.0.0/8", "10.1.2.3"]
|
||||
want: true
|
||||
|
||||
- note: ipv4_contains_ip_false
|
||||
args: ["10.0.0.0/8", "192.168.1.1"]
|
||||
want: false
|
||||
|
||||
- note: ipv4_contains_subnet_true
|
||||
args: ["10.0.0.0/8", "10.0.0.0/16"]
|
||||
want: true
|
||||
|
||||
- note: ipv4_contains_subnet_false
|
||||
args: ["10.0.0.0/16", "10.0.0.0/8"]
|
||||
want: false
|
||||
|
||||
- note: ipv4_exact_match
|
||||
args: ["192.168.1.0/24", "192.168.1.100"]
|
||||
want: true
|
||||
|
||||
- note: ipv4_edge_network_address
|
||||
args: ["192.168.1.0/24", "192.168.1.0"]
|
||||
want: true
|
||||
|
||||
- note: ipv4_edge_broadcast
|
||||
args: ["192.168.1.0/24", "192.168.1.255"]
|
||||
want: true
|
||||
|
||||
- note: ipv4_just_outside
|
||||
args: ["192.168.1.0/24", "192.168.2.0"]
|
||||
want: false
|
||||
|
||||
- note: ipv4_slash_32
|
||||
args: ["10.0.0.1/32", "10.0.0.1"]
|
||||
want: true
|
||||
|
||||
- note: ipv4_slash_32_miss
|
||||
args: ["10.0.0.1/32", "10.0.0.2"]
|
||||
want: false
|
||||
|
||||
# ── IPv6 ────────────────────────────────────────────────────────────
|
||||
- note: ipv6_contains_ip_true
|
||||
args: ["fd00::/8", "fd12:3456::1"]
|
||||
want: true
|
||||
|
||||
- note: ipv6_contains_ip_false
|
||||
args: ["fd00::/8", "2001:db8::1"]
|
||||
want: false
|
||||
|
||||
# ── Invalid inputs ──────────────────────────────────────────────────
|
||||
- note: ip_invalid_range
|
||||
args: ["not-a-cidr", "10.0.0.1"]
|
||||
want: false
|
||||
|
||||
- note: ip_invalid_target
|
||||
args: ["10.0.0.0/8", "not-an-ip"]
|
||||
want: false
|
||||
@@ -0,0 +1,40 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.items
|
||||
builtin: azure.policy.fn.items
|
||||
|
||||
cases:
|
||||
- note: items_simple_object
|
||||
args:
|
||||
- name: "test"
|
||||
value: 42
|
||||
want:
|
||||
- key: "name"
|
||||
value: "test"
|
||||
- key: "value"
|
||||
value: 42
|
||||
|
||||
- note: items_single_key
|
||||
args:
|
||||
- a: 1
|
||||
want:
|
||||
- key: "a"
|
||||
value: 1
|
||||
|
||||
- note: items_nested_value
|
||||
args:
|
||||
- x:
|
||||
inner: true
|
||||
want:
|
||||
- key: "x"
|
||||
value:
|
||||
inner: true
|
||||
|
||||
- note: items_non_object
|
||||
args: ["not_an_object"]
|
||||
want_undefined: true
|
||||
|
||||
- note: items_number_arg
|
||||
args: [42]
|
||||
want_undefined: true
|
||||
@@ -0,0 +1,42 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.join
|
||||
builtin: azure.policy.fn.join
|
||||
|
||||
cases:
|
||||
- note: join_strings_comma
|
||||
args: [["a", "b", "c"], ","]
|
||||
want: "a,b,c"
|
||||
|
||||
- note: join_strings_space
|
||||
args: [["hello", "world"], " "]
|
||||
want: "hello world"
|
||||
|
||||
- note: join_single_element
|
||||
args: [["only"], ","]
|
||||
want: "only"
|
||||
|
||||
- note: join_empty_array
|
||||
args: [[], ","]
|
||||
want: ""
|
||||
|
||||
- note: join_empty_delimiter
|
||||
args: [["a", "b", "c"], ""]
|
||||
want: "abc"
|
||||
|
||||
- note: join_with_numbers
|
||||
args: [[1, 2, 3], "-"]
|
||||
want: "1-2-3"
|
||||
|
||||
- note: join_with_booleans
|
||||
args: [[true, false], "|"]
|
||||
want: "true|false"
|
||||
|
||||
- note: join_with_null
|
||||
args: [["a", null, "b"], ","]
|
||||
want: "a,null,b"
|
||||
|
||||
- note: join_multichar_delimiter
|
||||
args: [["x", "y", "z"], " :: "]
|
||||
want: "x :: y :: z"
|
||||
@@ -0,0 +1,51 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.json
|
||||
builtin: azure.policy.fn.json
|
||||
|
||||
cases:
|
||||
- note: parse_object
|
||||
args: ['{"name":"test","value":42}']
|
||||
want:
|
||||
name: test
|
||||
value: 42
|
||||
|
||||
- note: parse_array
|
||||
args: ['[1,2,3]']
|
||||
want: [1, 2, 3]
|
||||
|
||||
- note: parse_string
|
||||
args: ['"hello"']
|
||||
want: "hello"
|
||||
|
||||
- note: parse_number
|
||||
args: ["42"]
|
||||
want: 42
|
||||
|
||||
- note: parse_boolean_true
|
||||
args: ["true"]
|
||||
want: true
|
||||
|
||||
- note: parse_boolean_false
|
||||
args: ["false"]
|
||||
want: false
|
||||
|
||||
- note: parse_null
|
||||
args: ["null"]
|
||||
want_null: true
|
||||
|
||||
- note: parse_nested_object
|
||||
args: ['{"a":{"b":{"c":1}}}']
|
||||
want:
|
||||
a:
|
||||
b:
|
||||
c: 1
|
||||
|
||||
- note: invalid_json
|
||||
args: ["{not valid json}"]
|
||||
want_error: "json()"
|
||||
|
||||
- note: non_string_arg
|
||||
args: [42]
|
||||
want_undefined: true
|
||||
@@ -0,0 +1,40 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.last
|
||||
builtin: azure.policy.fn.last
|
||||
|
||||
cases:
|
||||
# ── Arrays ──────────────────────────────────────────────────────────
|
||||
- note: last_array_single
|
||||
args: [[42]]
|
||||
want: 42
|
||||
|
||||
- note: last_array_multiple
|
||||
args: [[10, 20, 30]]
|
||||
want: 30
|
||||
|
||||
- note: last_array_strings
|
||||
args: [["alpha", "beta", "gamma"]]
|
||||
want: "gamma"
|
||||
|
||||
- note: last_array_empty
|
||||
args: [[]]
|
||||
want_null: true
|
||||
|
||||
# ── Strings ─────────────────────────────────────────────────────────
|
||||
- note: last_string
|
||||
args: ["hello"]
|
||||
want: "o"
|
||||
|
||||
- note: last_string_single_char
|
||||
args: ["x"]
|
||||
want: "x"
|
||||
|
||||
- note: last_string_empty
|
||||
args: [""]
|
||||
want: ""
|
||||
|
||||
- note: last_string_unicode
|
||||
args: ["café"]
|
||||
want: "é"
|
||||
@@ -0,0 +1,34 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.last_index_of
|
||||
builtin: azure.policy.fn.last_index_of
|
||||
|
||||
cases:
|
||||
- note: last_index_of_found
|
||||
args: ["hello world hello", "hello"]
|
||||
want: 12
|
||||
|
||||
- note: last_index_of_single_occurrence
|
||||
args: ["hello world", "world"]
|
||||
want: 6
|
||||
|
||||
- note: last_index_of_not_found
|
||||
args: ["hello", "xyz"]
|
||||
want: -1
|
||||
|
||||
- note: last_index_of_empty_needle
|
||||
args: ["hello", ""]
|
||||
want: 5
|
||||
|
||||
- note: last_index_of_repeated
|
||||
args: ["abcabcabc", "abc"]
|
||||
want: 6
|
||||
|
||||
- note: last_index_of_single_char
|
||||
args: ["abcabc", "c"]
|
||||
want: 5
|
||||
|
||||
- note: last_index_of_case_insensitive
|
||||
args: ["Hello HELLO hello", "hello"]
|
||||
want: 12
|
||||
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.logic_all (ARM template and() function)
|
||||
builtin: azure.policy.logic_all
|
||||
|
||||
cases:
|
||||
- note: all_true
|
||||
args: [true, true, true]
|
||||
want: true
|
||||
|
||||
- note: one_false
|
||||
args: [true, false, true]
|
||||
want: false
|
||||
|
||||
- note: all_false
|
||||
args: [false, false, false]
|
||||
want: false
|
||||
|
||||
- note: single_true
|
||||
args: [true]
|
||||
want: true
|
||||
|
||||
- note: single_false
|
||||
args: [false]
|
||||
want: false
|
||||
|
||||
- note: two_args_both_true
|
||||
args: [true, true]
|
||||
want: true
|
||||
|
||||
- note: two_args_one_false
|
||||
args: [true, false]
|
||||
want: false
|
||||
|
||||
- note: empty_args_vacuously_true
|
||||
args: []
|
||||
want: true
|
||||
@@ -0,0 +1,34 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.logic_any (ARM template or() function)
|
||||
builtin: azure.policy.logic_any
|
||||
|
||||
cases:
|
||||
- note: all_true
|
||||
args: [true, true, true]
|
||||
want: true
|
||||
|
||||
- note: one_true
|
||||
args: [false, true, false]
|
||||
want: true
|
||||
|
||||
- note: all_false
|
||||
args: [false, false, false]
|
||||
want: false
|
||||
|
||||
- note: single_true
|
||||
args: [true]
|
||||
want: true
|
||||
|
||||
- note: single_false
|
||||
args: [false]
|
||||
want: false
|
||||
|
||||
- note: two_args_one_true
|
||||
args: [false, true]
|
||||
want: true
|
||||
|
||||
- note: empty_args_vacuously_false
|
||||
args: []
|
||||
want: false
|
||||
@@ -0,0 +1,42 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.max
|
||||
builtin: azure.policy.fn.max
|
||||
|
||||
cases:
|
||||
- note: max_two_args
|
||||
args: [3, 1]
|
||||
want: 3
|
||||
|
||||
- note: max_three_args
|
||||
args: [5, 2, 8]
|
||||
want: 8
|
||||
|
||||
- note: max_equal_args
|
||||
args: [3, 3, 3]
|
||||
want: 3
|
||||
|
||||
- note: max_negative
|
||||
args: [-5, 0, 5]
|
||||
want: 5
|
||||
|
||||
- note: max_single_arg
|
||||
args: [42]
|
||||
want: 42
|
||||
|
||||
- note: max_array
|
||||
args: [[3, 1, 4, 1, 5]]
|
||||
want: 5
|
||||
|
||||
- note: max_array_single
|
||||
args: [[99]]
|
||||
want: 99
|
||||
|
||||
- note: max_array_negatives
|
||||
args: [[-10, -20, -5]]
|
||||
want: -5
|
||||
|
||||
- note: max_array_empty
|
||||
args: [[]]
|
||||
want_undefined: true
|
||||
@@ -0,0 +1,44 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.min
|
||||
builtin: azure.policy.fn.min
|
||||
|
||||
cases:
|
||||
# ── Multiple arguments ──────────────────────────────────────────────
|
||||
- note: min_two_args
|
||||
args: [3, 1]
|
||||
want: 1
|
||||
|
||||
- note: min_three_args
|
||||
args: [5, 2, 8]
|
||||
want: 2
|
||||
|
||||
- note: min_equal_args
|
||||
args: [3, 3, 3]
|
||||
want: 3
|
||||
|
||||
- note: min_negative
|
||||
args: [-5, 0, 5]
|
||||
want: -5
|
||||
|
||||
- note: min_single_arg
|
||||
args: [42]
|
||||
want: 42
|
||||
|
||||
# ── Array argument ──────────────────────────────────────────────────
|
||||
- note: min_array
|
||||
args: [[3, 1, 4, 1, 5]]
|
||||
want: 1
|
||||
|
||||
- note: min_array_single
|
||||
args: [[99]]
|
||||
want: 99
|
||||
|
||||
- note: min_array_negatives
|
||||
args: [[-10, -20, -5]]
|
||||
want: -20
|
||||
|
||||
- note: min_array_empty
|
||||
args: [[]]
|
||||
want_undefined: true
|
||||
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.pad_left
|
||||
builtin: azure.policy.fn.pad_left
|
||||
|
||||
cases:
|
||||
- note: pad_left_basic
|
||||
args: ["42", 5, "0"]
|
||||
want: "00042"
|
||||
|
||||
- note: pad_left_no_padding_needed
|
||||
args: ["hello", 3, "x"]
|
||||
want: "hello"
|
||||
|
||||
- note: pad_left_exact_width
|
||||
args: ["abc", 3, "x"]
|
||||
want: "abc"
|
||||
|
||||
- note: pad_left_default_space
|
||||
args: ["42", 5, " "]
|
||||
want: " 42"
|
||||
|
||||
- note: pad_left_single_char
|
||||
args: ["x", 5, "-"]
|
||||
want: "----x"
|
||||
|
||||
- note: pad_left_empty_string
|
||||
args: ["", 3, "0"]
|
||||
want: "000"
|
||||
|
||||
- note: pad_left_width_1
|
||||
args: ["abc", 1, "0"]
|
||||
want: "abc"
|
||||
|
||||
- note: pad_left_width_0
|
||||
args: ["abc", 0, "0"]
|
||||
want: "abc"
|
||||
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.range
|
||||
builtin: azure.policy.fn.range
|
||||
|
||||
cases:
|
||||
- note: range_basic
|
||||
args: [0, 5]
|
||||
want: [0, 1, 2, 3, 4]
|
||||
|
||||
- note: range_from_offset
|
||||
args: [3, 4]
|
||||
want: [3, 4, 5, 6]
|
||||
|
||||
- note: range_single
|
||||
args: [0, 1]
|
||||
want: [0]
|
||||
|
||||
- note: range_zero_count
|
||||
args: [5, 0]
|
||||
want: []
|
||||
|
||||
- note: range_negative_start
|
||||
args: [-2, 4]
|
||||
want: [-2, -1, 0, 1]
|
||||
|
||||
- note: range_large_start
|
||||
args: [100, 3]
|
||||
want: [100, 101, 102]
|
||||
|
||||
- note: range_count_exceeds_10000
|
||||
args: [0, 10001]
|
||||
want_error: "exceeds maximum of 10000"
|
||||
|
||||
- note: range_sum_exceeds_i32_max
|
||||
args: [2147483640, 10]
|
||||
want_error: "exceeds maximum of 2147483647"
|
||||
@@ -0,0 +1,49 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.resolve_field
|
||||
builtin: azure.policy.resolve_field
|
||||
|
||||
cases:
|
||||
- note: simple_field
|
||||
args:
|
||||
- name: "myResource"
|
||||
type: "Microsoft.Compute/virtualMachines"
|
||||
- "name"
|
||||
want: "myResource"
|
||||
|
||||
- note: nested_field_with_dot_path
|
||||
args:
|
||||
- properties:
|
||||
securityRules:
|
||||
enabled: true
|
||||
- "properties.securityRules.enabled"
|
||||
want: true
|
||||
|
||||
- note: missing_field
|
||||
args:
|
||||
- name: "myResource"
|
||||
- "nonExistent"
|
||||
want_undefined: true
|
||||
|
||||
- note: deeply_nested
|
||||
args:
|
||||
- a:
|
||||
b:
|
||||
c:
|
||||
d: "deep"
|
||||
- "a.b.c.d"
|
||||
want: "deep"
|
||||
|
||||
- note: top_level_type
|
||||
args:
|
||||
- type: "Microsoft.Storage/storageAccounts"
|
||||
kind: "StorageV2"
|
||||
- "type"
|
||||
want: "Microsoft.Storage/storageAccounts"
|
||||
|
||||
- note: non_string_path
|
||||
args:
|
||||
- name: "test"
|
||||
- 42
|
||||
want_undefined: true
|
||||
@@ -0,0 +1,48 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.skip
|
||||
builtin: azure.policy.fn.skip
|
||||
|
||||
cases:
|
||||
# ── Arrays ──────────────────────────────────────────────────────────
|
||||
- note: skip_array_basic
|
||||
args: [[1, 2, 3, 4, 5], 2]
|
||||
want: [3, 4, 5]
|
||||
|
||||
- note: skip_array_zero
|
||||
args: [[1, 2, 3], 0]
|
||||
want: [1, 2, 3]
|
||||
|
||||
- note: skip_array_all
|
||||
args: [[1, 2, 3], 3]
|
||||
want: []
|
||||
|
||||
- note: skip_array_more_than_length
|
||||
args: [[1, 2], 5]
|
||||
want: []
|
||||
|
||||
- note: skip_array_one
|
||||
args: [[10, 20, 30], 1]
|
||||
want: [20, 30]
|
||||
|
||||
- note: skip_array_empty
|
||||
args: [[], 3]
|
||||
want: []
|
||||
|
||||
# ── Strings ─────────────────────────────────────────────────────────
|
||||
- note: skip_string_basic
|
||||
args: ["hello world", 6]
|
||||
want: "world"
|
||||
|
||||
- note: skip_string_zero
|
||||
args: ["hello", 0]
|
||||
want: "hello"
|
||||
|
||||
- note: skip_string_all
|
||||
args: ["hello", 5]
|
||||
want: ""
|
||||
|
||||
- note: skip_string_more_than_length
|
||||
args: ["hi", 10]
|
||||
want: ""
|
||||
@@ -0,0 +1,61 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.split
|
||||
builtin: azure.policy.fn.split
|
||||
|
||||
cases:
|
||||
# ── Basic splitting ─────────────────────────────────────────────────
|
||||
- note: split_simple_comma
|
||||
args: ["hello,world", ","]
|
||||
want: ["hello", "world"]
|
||||
|
||||
- note: split_multiple_segments
|
||||
args: ["a.b.c.d", "."]
|
||||
want: ["a", "b", "c", "d"]
|
||||
|
||||
- note: split_no_match
|
||||
args: ["hello", ","]
|
||||
want: ["hello"]
|
||||
|
||||
- note: split_empty_string
|
||||
args: ["", ","]
|
||||
want: [""]
|
||||
|
||||
- note: split_delimiter_at_edges
|
||||
args: [",hello,", ","]
|
||||
want: ["", "hello", ""]
|
||||
|
||||
- note: split_consecutive_delimiters
|
||||
args: ["a,,b", ","]
|
||||
want: ["a", "", "b"]
|
||||
|
||||
- note: split_multi_char_delimiter
|
||||
args: ["a::b::c", "::"]
|
||||
want: ["a", "b", "c"]
|
||||
|
||||
- note: split_single_char_string
|
||||
args: ["x", "x"]
|
||||
want: ["", ""]
|
||||
|
||||
# ── Array of delimiters ────────────────────────────────────────────
|
||||
- note: split_array_delimiters
|
||||
args: ["a.b/c", [".", "/"]]
|
||||
want: ["a", "b", "c"]
|
||||
|
||||
- note: split_array_single_delimiter
|
||||
args: ["a-b-c", ["-"]]
|
||||
want: ["a", "b", "c"]
|
||||
|
||||
# ── Edge cases ──────────────────────────────────────────────────────
|
||||
- note: split_unicode
|
||||
args: ["café☕bar", "☕"]
|
||||
want: ["café", "bar"]
|
||||
|
||||
- note: split_entire_string_is_delimiter
|
||||
args: ["abc", "abc"]
|
||||
want: ["", ""]
|
||||
|
||||
- note: split_empty_delimiter
|
||||
args: ["abc", ""]
|
||||
want: ["abc"]
|
||||
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.starts_with
|
||||
builtin: azure.policy.fn.starts_with
|
||||
|
||||
cases:
|
||||
- note: starts_with_true
|
||||
args: ["hello world", "hello"]
|
||||
want: true
|
||||
|
||||
- note: starts_with_false
|
||||
args: ["hello world", "world"]
|
||||
want: false
|
||||
|
||||
- note: starts_with_case_insensitive
|
||||
args: ["Hello World", "hello"]
|
||||
want: true
|
||||
|
||||
- note: starts_with_empty_needle
|
||||
args: ["hello", ""]
|
||||
want: true
|
||||
|
||||
- note: starts_with_empty_haystack
|
||||
args: ["", "hello"]
|
||||
want: false
|
||||
|
||||
- note: starts_with_both_empty
|
||||
args: ["", ""]
|
||||
want: true
|
||||
|
||||
- note: starts_with_exact_match
|
||||
args: ["hello", "hello"]
|
||||
want: true
|
||||
|
||||
- note: starts_with_longer_needle
|
||||
args: ["hi", "hello"]
|
||||
want: false
|
||||
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.string
|
||||
builtin: azure.policy.fn.string
|
||||
|
||||
cases:
|
||||
- note: string_from_string
|
||||
args: ["hello"]
|
||||
want: "hello"
|
||||
|
||||
- note: string_from_integer
|
||||
args: [42]
|
||||
want: "42"
|
||||
|
||||
- note: string_from_negative
|
||||
args: [-7]
|
||||
want: "-7"
|
||||
|
||||
- note: string_from_bool_true
|
||||
args: [true]
|
||||
want: "true"
|
||||
|
||||
- note: string_from_bool_false
|
||||
args: [false]
|
||||
want: "false"
|
||||
|
||||
- note: string_from_null
|
||||
args: [null]
|
||||
want: "null"
|
||||
|
||||
- note: string_from_zero
|
||||
args: [0]
|
||||
want: "0"
|
||||
|
||||
- note: string_from_empty
|
||||
args: [""]
|
||||
want: ""
|
||||
@@ -0,0 +1,48 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.take
|
||||
builtin: azure.policy.fn.take
|
||||
|
||||
cases:
|
||||
# ── Arrays ──────────────────────────────────────────────────────────
|
||||
- note: take_array_basic
|
||||
args: [[1, 2, 3, 4, 5], 3]
|
||||
want: [1, 2, 3]
|
||||
|
||||
- note: take_array_zero
|
||||
args: [[1, 2, 3], 0]
|
||||
want: []
|
||||
|
||||
- note: take_array_more_than_length
|
||||
args: [[1, 2], 5]
|
||||
want: [1, 2]
|
||||
|
||||
- note: take_array_all
|
||||
args: [[1, 2, 3], 3]
|
||||
want: [1, 2, 3]
|
||||
|
||||
- note: take_array_one
|
||||
args: [[10, 20, 30], 1]
|
||||
want: [10]
|
||||
|
||||
- note: take_array_empty
|
||||
args: [[], 3]
|
||||
want: []
|
||||
|
||||
# ── Strings ─────────────────────────────────────────────────────────
|
||||
- note: take_string_basic
|
||||
args: ["hello world", 5]
|
||||
want: "hello"
|
||||
|
||||
- note: take_string_zero
|
||||
args: ["hello", 0]
|
||||
want: ""
|
||||
|
||||
- note: take_string_more_than_length
|
||||
args: ["hi", 10]
|
||||
want: "hi"
|
||||
|
||||
- note: take_string_one
|
||||
args: ["hello", 1]
|
||||
want: "h"
|
||||
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.trim
|
||||
builtin: azure.policy.fn.trim
|
||||
|
||||
cases:
|
||||
- note: trim_whitespace
|
||||
args: [" hello "]
|
||||
want: "hello"
|
||||
|
||||
- note: trim_tabs_and_newlines
|
||||
args: ["\thello\n"]
|
||||
want: "hello"
|
||||
|
||||
- note: trim_no_whitespace
|
||||
args: ["hello"]
|
||||
want: "hello"
|
||||
|
||||
- note: trim_empty
|
||||
args: [""]
|
||||
want: ""
|
||||
|
||||
- note: trim_only_whitespace
|
||||
args: [" "]
|
||||
want: ""
|
||||
|
||||
- note: trim_left_only
|
||||
args: [" hello"]
|
||||
want: "hello"
|
||||
|
||||
- note: trim_right_only
|
||||
args: ["hello "]
|
||||
want: "hello"
|
||||
|
||||
- note: trim_inner_whitespace_preserved
|
||||
args: [" hello world "]
|
||||
want: "hello world"
|
||||
@@ -0,0 +1,51 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.try_get
|
||||
builtin: azure.policy.fn.try_get
|
||||
|
||||
cases:
|
||||
- note: get_existing_key
|
||||
args:
|
||||
- name: "test"
|
||||
value: 42
|
||||
- "name"
|
||||
want: "test"
|
||||
|
||||
- note: get_missing_key
|
||||
args:
|
||||
- name: "test"
|
||||
- "missing"
|
||||
want_null: true
|
||||
|
||||
- note: get_array_index
|
||||
args: [["a", "b", "c"], 1]
|
||||
want: "b"
|
||||
|
||||
- note: get_array_out_of_bounds
|
||||
args: [["a", "b"], 5]
|
||||
want_null: true
|
||||
|
||||
- note: get_from_non_container
|
||||
args: ["not_an_object", "key"]
|
||||
want_null: true
|
||||
|
||||
- note: get_null_item
|
||||
args: [null, "key"]
|
||||
want_null: true
|
||||
|
||||
- note: get_nested_value
|
||||
args:
|
||||
- data:
|
||||
inner: true
|
||||
- "data"
|
||||
want:
|
||||
inner: true
|
||||
|
||||
- note: get_number_item
|
||||
args: [42, "key"]
|
||||
want_null: true
|
||||
|
||||
- note: get_array_first
|
||||
args: [[10, 20, 30], 0]
|
||||
want: 10
|
||||
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.try_index_from_end
|
||||
builtin: azure.policy.fn.try_index_from_end
|
||||
|
||||
cases:
|
||||
- note: last_element
|
||||
args: [["a", "b", "c", "d"], 1]
|
||||
want: "d"
|
||||
|
||||
- note: second_from_end
|
||||
args: [["a", "b", "c", "d"], 2]
|
||||
want: "c"
|
||||
|
||||
- note: first_element_via_end
|
||||
args: [["a", "b", "c", "d"], 4]
|
||||
want: "a"
|
||||
|
||||
- note: index_zero_returns_null
|
||||
args: [["a", "b"], 0]
|
||||
want_null: true
|
||||
|
||||
- note: index_exceeds_length_returns_null
|
||||
args: [["a", "b"], 5]
|
||||
want_null: true
|
||||
|
||||
- note: non_array_returns_null
|
||||
args: ["not_an_array", 1]
|
||||
want_null: true
|
||||
|
||||
- note: single_element
|
||||
args: [["only"], 1]
|
||||
want: "only"
|
||||
|
||||
- note: numeric_array
|
||||
args: [[10, 20, 30], 2]
|
||||
want: 20
|
||||
@@ -0,0 +1,89 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.union
|
||||
builtin: azure.policy.fn.union
|
||||
|
||||
cases:
|
||||
# ── Array union ─────────────────────────────────────────────────────
|
||||
- note: union_arrays_basic
|
||||
args: [[1, 2], [2, 3]]
|
||||
want: [1, 2, 3]
|
||||
|
||||
- note: union_arrays_no_overlap
|
||||
args: [[1, 2], [3, 4]]
|
||||
want: [1, 2, 3, 4]
|
||||
|
||||
- note: union_arrays_identical
|
||||
args: [[1, 2], [1, 2]]
|
||||
want: [1, 2]
|
||||
|
||||
- note: union_arrays_empty
|
||||
args: [[], [1, 2]]
|
||||
want: [1, 2]
|
||||
|
||||
- note: union_arrays_both_empty
|
||||
args: [[], []]
|
||||
want: []
|
||||
|
||||
- note: union_arrays_three
|
||||
args: [[1], [2], [3]]
|
||||
want: [1, 2, 3]
|
||||
|
||||
- note: union_arrays_strings
|
||||
args: [["a", "b"], ["b", "c"]]
|
||||
want: ["a", "b", "c"]
|
||||
|
||||
# ── Object union ────────────────────────────────────────────────────
|
||||
- note: union_objects_merge
|
||||
args: [{"a": 1}, {"b": 2}]
|
||||
want: {"a": 1, "b": 2}
|
||||
|
||||
- note: union_objects_overwrite
|
||||
args: [{"a": 1, "b": 2}, {"b": 20, "c": 30}]
|
||||
want: {"a": 1, "b": 20, "c": 30}
|
||||
|
||||
- note: union_objects_empty
|
||||
args: [{}, {"a": 1}]
|
||||
want: {"a": 1}
|
||||
|
||||
- note: union_objects_recursive_merge
|
||||
args:
|
||||
- outer:
|
||||
inner_a: 1
|
||||
inner_b: 2
|
||||
- outer:
|
||||
inner_b: 20
|
||||
inner_c: 30
|
||||
want:
|
||||
outer:
|
||||
inner_a: 1
|
||||
inner_b: 20
|
||||
inner_c: 30
|
||||
|
||||
- note: union_objects_deeply_nested_merge
|
||||
args:
|
||||
- level1:
|
||||
level2:
|
||||
a: 1
|
||||
b: 2
|
||||
- level1:
|
||||
level2:
|
||||
b: 20
|
||||
c: 30
|
||||
want:
|
||||
level1:
|
||||
level2:
|
||||
a: 1
|
||||
b: 20
|
||||
c: 30
|
||||
|
||||
- note: union_objects_array_replaces_not_merges
|
||||
args:
|
||||
- data:
|
||||
items: [1, 2, 3]
|
||||
- data:
|
||||
items: [4, 5]
|
||||
want:
|
||||
data:
|
||||
items: [4, 5]
|
||||
@@ -0,0 +1,54 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.uri
|
||||
builtin: azure.policy.fn.uri
|
||||
|
||||
cases:
|
||||
- note: base_with_trailing_slash
|
||||
args: ["https://example.com/", "path/to/resource"]
|
||||
want: "https://example.com/path/to/resource"
|
||||
|
||||
- note: base_without_trailing_slash
|
||||
args: ["https://example.com/base", "relative"]
|
||||
want: "https://example.com/relative"
|
||||
|
||||
- note: relative_is_absolute_url
|
||||
args: ["https://example.com/base", "https://other.com/path"]
|
||||
want: "https://other.com/path"
|
||||
|
||||
- note: relative_with_leading_slash
|
||||
args: ["https://example.com/", "/path"]
|
||||
want: "https://example.com/path"
|
||||
|
||||
- note: base_with_path_components
|
||||
args: ["https://example.com/a/b/c", "d"]
|
||||
want: "https://example.com/a/b/d"
|
||||
|
||||
- note: non_string_base
|
||||
args: [42, "relative"]
|
||||
want_undefined: true
|
||||
|
||||
- note: non_string_relative
|
||||
args: ["https://example.com/", 42]
|
||||
want_undefined: true
|
||||
|
||||
- note: simple_base
|
||||
args: ["https://example.com/", "api/v1"]
|
||||
want: "https://example.com/api/v1"
|
||||
|
||||
- note: authority_only_base
|
||||
args: ["https://example.com", "api/v1"]
|
||||
want: "https://example.com/api/v1"
|
||||
|
||||
- note: authority_only_base_with_leading_slash
|
||||
args: ["https://example.com", "/api/v1"]
|
||||
want: "https://example.com/api/v1"
|
||||
|
||||
- note: uri_relative_with_query_string
|
||||
args: ["https://example.com/api", "v2?key=value"]
|
||||
want: "https://example.com/v2?key=value"
|
||||
|
||||
- note: uri_relative_with_fragment
|
||||
args: ["https://example.com/docs/", "page#section"]
|
||||
want: "https://example.com/docs/page#section"
|
||||
@@ -0,0 +1,30 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.uri_component and azure.policy.fn.uri_component_to_string
|
||||
builtin: azure.policy.fn.uri_component
|
||||
|
||||
cases:
|
||||
- note: uri_encode_simple
|
||||
args: ["hello world"]
|
||||
want: "hello%20world"
|
||||
|
||||
- note: uri_encode_special_chars
|
||||
args: ["a=b&c=d"]
|
||||
want: "a%3Db%26c%3Dd"
|
||||
|
||||
- note: uri_encode_already_safe
|
||||
args: ["hello"]
|
||||
want: "hello"
|
||||
|
||||
- note: uri_encode_empty
|
||||
args: [""]
|
||||
want: ""
|
||||
|
||||
- note: uri_encode_slash
|
||||
args: ["path/to/resource"]
|
||||
want: "path%2Fto%2Fresource"
|
||||
|
||||
- note: uri_encode_spaces_and_plus
|
||||
args: ["hello+world 2"]
|
||||
want: "hello%2Bworld%202"
|
||||
@@ -0,0 +1,26 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for azure.policy.fn.uri_component_to_string
|
||||
builtin: azure.policy.fn.uri_component_to_string
|
||||
|
||||
cases:
|
||||
- note: uri_decode_simple
|
||||
args: ["hello%20world"]
|
||||
want: "hello world"
|
||||
|
||||
- note: uri_decode_special_chars
|
||||
args: ["a%3Db%26c%3Dd"]
|
||||
want: "a=b&c=d"
|
||||
|
||||
- note: uri_decode_no_encoding
|
||||
args: ["hello"]
|
||||
want: "hello"
|
||||
|
||||
- note: uri_decode_empty
|
||||
args: [""]
|
||||
want: ""
|
||||
|
||||
- note: uri_decode_plus_literal
|
||||
args: ["hello%2Bworld"]
|
||||
want: "hello+world"
|
||||
@@ -0,0 +1,215 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! YAML-based test runner for Azure Policy builtins.
|
||||
//!
|
||||
//! Each YAML file contains a `builtin` field naming the function under test
|
||||
//! and a list of `cases`. Every case specifies `args` (positional arguments)
|
||||
//! and either `want` (expected return value) or `want_undefined` (the builtin
|
||||
//! should return `Value::Undefined`).
|
||||
//!
|
||||
//! Tests call builtins directly via the registry (`BUILTINS` map) instead of
|
||||
//! going through Rego evaluation. This avoids issues with builtin names that
|
||||
//! collide with Rego keywords (e.g. `azure.policy.if`).
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use regorus::unstable::{Source, Span, BUILTINS};
|
||||
use regorus::Value;
|
||||
use serde::Deserialize;
|
||||
use test_generator::test_resources;
|
||||
|
||||
// ── YAML schema ───────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct YamlTestFile {
|
||||
/// Dotted builtin name, e.g. `azure.policy.fn.split`.
|
||||
builtin: String,
|
||||
cases: Vec<TestCase>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct TestCase {
|
||||
/// Short human-readable label.
|
||||
note: String,
|
||||
/// Positional arguments fed to the builtin.
|
||||
args: Vec<serde_yaml::Value>,
|
||||
/// Expected return value (`null` for JSON null).
|
||||
want: Option<serde_yaml::Value>,
|
||||
/// If true, the builtin is expected to return null.
|
||||
/// (Needed because `want: null` in YAML deserializes as Option::None.)
|
||||
#[serde(default)]
|
||||
want_null: bool,
|
||||
/// If true, the builtin is expected to produce Undefined (no result).
|
||||
#[serde(default)]
|
||||
want_undefined: bool,
|
||||
/// If set, the builtin should return an error containing this substring.
|
||||
want_error: Option<String>,
|
||||
/// Skip this case.
|
||||
#[serde(default)]
|
||||
skip: bool,
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
/// Convert a serde_yaml::Value to a regorus Value.
|
||||
fn yaml_to_value(v: &serde_yaml::Value) -> Value {
|
||||
match v {
|
||||
serde_yaml::Value::Null => Value::Null,
|
||||
serde_yaml::Value::Bool(b) => Value::Bool(*b),
|
||||
serde_yaml::Value::Number(n) => {
|
||||
if let Some(i) = n.as_i64() {
|
||||
Value::from(i)
|
||||
} else if let Some(f) = n.as_f64() {
|
||||
Value::from(f)
|
||||
} else {
|
||||
panic!("unsupported YAML numeric representation: {n:?}")
|
||||
}
|
||||
}
|
||||
serde_yaml::Value::String(s) => Value::String(s.as_str().into()),
|
||||
serde_yaml::Value::Sequence(items) => {
|
||||
let vals: Vec<Value> = items.iter().map(yaml_to_value).collect();
|
||||
Value::from(vals)
|
||||
}
|
||||
serde_yaml::Value::Mapping(map) => {
|
||||
let mut obj = Value::new_object();
|
||||
{
|
||||
let m = obj.as_object_mut().unwrap();
|
||||
for (k, v) in map {
|
||||
m.insert(yaml_to_value(k), yaml_to_value(v));
|
||||
}
|
||||
}
|
||||
obj
|
||||
}
|
||||
serde_yaml::Value::Tagged(t) => yaml_to_value(&t.value),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a dummy Span for calling builtins outside of normal evaluation.
|
||||
fn dummy_span() -> Span {
|
||||
let source = Source::from_contents("<test>".to_string(), String::new())
|
||||
.expect("creating dummy source should not fail");
|
||||
Span {
|
||||
source,
|
||||
line: 1,
|
||||
col: 1,
|
||||
start: 0,
|
||||
end: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// ── Test runner ───────────────────────────────────────────────────────
|
||||
|
||||
fn run_yaml_test(path: &str) -> Result<()> {
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
let test_file: YamlTestFile = serde_yaml::from_str(&content)?;
|
||||
|
||||
let filter = std::env::var("TEST_CASE_FILTER").ok();
|
||||
|
||||
// Look up the builtin function once for all cases.
|
||||
let builtin_entry = BUILTINS.get(test_file.builtin.as_str()).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"builtin {:?} not found in BUILTINS registry",
|
||||
test_file.builtin
|
||||
)
|
||||
});
|
||||
|
||||
let builtin_fn = builtin_entry.0;
|
||||
let span = dummy_span();
|
||||
|
||||
for case in &test_file.cases {
|
||||
if case.skip {
|
||||
continue;
|
||||
}
|
||||
if let Some(ref f) = filter {
|
||||
if !case.note.contains(f.as_str()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert YAML args to Value args.
|
||||
let args: Vec<Value> = case.args.iter().map(yaml_to_value).collect();
|
||||
|
||||
// Call the builtin directly.
|
||||
let call_result = builtin_fn(&span, &[], &args, false);
|
||||
|
||||
// Check error expectations.
|
||||
if let Some(ref want_err) = case.want_error {
|
||||
match call_result {
|
||||
Err(e) => {
|
||||
let msg = format!("{e:#}");
|
||||
assert!(
|
||||
msg.contains(want_err.as_str()),
|
||||
"[{builtin} / {note}] expected error containing {want_err:?}, got: {msg}",
|
||||
builtin = test_file.builtin,
|
||||
note = case.note,
|
||||
);
|
||||
}
|
||||
Ok(ref v) if matches!(v, Value::Undefined) => {
|
||||
// `want_error` specifically expects an error message string;
|
||||
// Undefined is not acceptable here — bail.
|
||||
bail!(
|
||||
"[{builtin} / {note}] expected error containing {want_err:?} but got Undefined",
|
||||
builtin = test_file.builtin,
|
||||
note = case.note,
|
||||
);
|
||||
}
|
||||
Ok(v) => {
|
||||
bail!(
|
||||
"[{builtin} / {note}] expected error containing {want_err:?} but got: {v}",
|
||||
builtin = test_file.builtin,
|
||||
note = case.note,
|
||||
);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Not expecting an error — unwrap the result.
|
||||
let actual = call_result.map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"[{builtin} / {note}] builtin returned error: {e:#}",
|
||||
builtin = test_file.builtin,
|
||||
note = case.note,
|
||||
)
|
||||
})?;
|
||||
|
||||
if case.want_undefined {
|
||||
assert!(
|
||||
actual == Value::Undefined,
|
||||
"[{builtin} / {note}] expected Undefined but got: {actual}",
|
||||
builtin = test_file.builtin,
|
||||
note = case.note,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// We expect a concrete result.
|
||||
let expected = if case.want_null {
|
||||
Value::Null
|
||||
} else {
|
||||
let want = case.want.as_ref().unwrap_or_else(|| {
|
||||
panic!(
|
||||
"[{} / {}] test case must have `want`, `want_null`, or `want_undefined`",
|
||||
test_file.builtin, case.note
|
||||
)
|
||||
});
|
||||
yaml_to_value(want)
|
||||
};
|
||||
|
||||
assert!(
|
||||
actual == expected,
|
||||
"[{builtin} / {note}]\n expected: {expected}\n actual: {actual}",
|
||||
builtin = test_file.builtin,
|
||||
note = case.note,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ── Test entry point ──────────────────────────────────────────────────
|
||||
|
||||
#[test_resources("tests/azure_policy_builtins/cases/*.yaml")]
|
||||
fn azure_policy_builtin_yaml(path: &str) {
|
||||
run_yaml_test(path).unwrap();
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#[cfg(feature = "azure_policy")]
|
||||
mod azure_policy_builtins;
|
||||
|
||||
#[cfg(feature = "coverage")]
|
||||
mod coverage;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user