

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 測試 AWS CloudFormation Guard 規則
<a name="testing-rules"></a>

您可以使用 AWS CloudFormation Guard 內建單元測試架構來驗證 Guard 規則是否如預期般運作。本節提供如何撰寫單元測試檔案，以及如何使用 `test`命令來測試規則檔案的逐步解說。

您的單元測試檔案必須具有下列其中一個副檔名：`.json`、`.JSON`、`.jsn``.yaml`、`.YAML`、 或 `.yml`。

**Topics**
+ [

## 先決條件
](#testing-rules-prerequisites)
+ [

## Guard 單位測試檔案概觀
](#testing-rules-overview)
+ [

## 撰寫 Guard 規則單位測試檔案的演練
](#testing-rules-example)

## 先決條件
<a name="testing-rules-prerequisites"></a>

撰寫 Guard 規則來評估您的輸入資料。如需詳細資訊，請參閱[撰寫 Guard 規則](writing-rules.md)。

## Guard 單位測試檔案概觀
<a name="testing-rules-overview"></a>

Guard 單位測試檔案是 JSON 或 YAML 格式的檔案，其中包含多個輸入，以及在 Guard 規則檔案中寫入規則的預期結果。可以有多個範例來評估不同的期望。我們建議您先測試空白輸入，然後逐步新增評估各種規則和子句的資訊。

此外，我們建議您使用尾碼 `_test.json`或 命名單元測試檔案`_tests.yaml`。例如，如果您有一個名為 的規則檔案`my_rules.guard`，請命名您的單元測試檔案 `my_rules_tests.yaml`。

### 語法
<a name="testing-rules-syntax"></a>

以下顯示 YAML 格式的單位測試檔案語法。

```
---
- name: <TEST NAME>
  input:
     <SAMPLE INPUT>
   expectations:
     rules:
       <RULE NAME>: [PASS|FAIL|SKIP]
```

### Properties
<a name="testing-rules-properties"></a>

以下是 Guard 測試檔案的屬性。

`input`  <a name="testing-rules-properties-input"></a>
要測試規則的資料。我們建議您的第一個測試使用空白輸入，如下列範例所示。  

```
---
- name: MyTest1
  input {}
```
對於後續測試，新增要測試的輸入資料。  
 *必要*：是 

`expectations`  <a name="testing-rules-properties-expectations"></a>
根據您的輸入資料評估特定規則時的預期結果。除了每個規則的預期結果之外，指定您要測試的一或多個規則。預期結果必須是下列其中一項：  
+ `PASS` – 當 對您的輸入資料執行時，規則會評估為 `true`。
+ `FAIL` – 當 對您的輸入資料執行時，規則會評估為 `false`。
+ `SKIP` – 對輸入資料執行 時，不會觸發規則。

```
expectations:
    rules:
      check_rest_api_is_private: PASS
```
 *必要*：是 

## 撰寫 Guard 規則單位測試檔案的演練
<a name="testing-rules-example"></a>

以下是名為 的規則檔案`api_gateway_private.guard`。此規則的目的是檢查 CloudFormation 範本中定義的所有 Amazon API Gateway 資源類型是否僅部署為私有存取。它也會檢查至少一個政策陳述式是否允許從虛擬私有雲端 (VPC) 存取。

```
#
# Select all AWS::ApiGateway::RestApi resources
#     present in the Resources section of the template. 
#
let api_gws = Resources.*[ Type == 'AWS::ApiGateway::RestApi']

#
# Rule intent:         
# 1) All AWS::ApiGateway::RestApi resources deployed must be private.                                            
# 2) All AWS::ApiGateway::RestApi resources deployed must have at least one AWS Identity and Access Management (IAM) policy condition key to allow access from a VPC.
#
# Expectations:        
# 1) SKIP when there are no AWS::ApiGateway::RestApi resources in the template.  
# 2) PASS when:
#     ALL AWS::ApiGateway::RestApi resources in the template have the EndpointConfiguration property set to Type: PRIVATE. 
#     ALL AWS::ApiGateway::RestApi resources in the template have one IAM condition key specified in the Policy property with aws:sourceVpc or :SourceVpc.    
# 3) FAIL otherwise.                                                                                  
#
#

rule check_rest_api_is_private when %api_gws !empty {      
    %api_gws {
        Properties.EndpointConfiguration.Types[*] == "PRIVATE"                             
    }  
}       

rule check_rest_api_has_vpc_access when check_rest_api_is_private {
    %api_gws {
        Properties {
            #
            # ALL AWS::ApiGateway::RestApi resources in the template have one IAM condition key specified in the Policy property with 
            #     aws:sourceVpc or :SourceVpc
            #           
            some Policy.Statement[*] {
                Condition.*[ keys == /aws:[sS]ource(Vpc|VPC|Vpce|VPCE)/ ] !empty
            }
        }
    }
}
```

此演練會測試第一個規則意圖：所有部署`AWS::ApiGateway::RestApi`的資源都必須是私有的。

1. 建立名為 的單元測試檔案`api_gateway_private_tests.yaml`，其中包含下列初始測試。在初始測試中，新增空白輸入，並預期規則`check_rest_api_is_private`會略過，因為沒有`AWS::ApiGateway::RestApi`資源做為輸入。

   ```
   ---
   - name: MyTest1
     input: {}
     expectations:
       rules:
         check_rest_api_is_private: SKIP
   ```

1. 使用 `test`命令在終端機中執行第一個測試。針對 `--rules-file` 參數，指定您的規則檔案。針對 `--test-data` 參數，指定您的單位測試檔案。

   ```
   cfn-guard test --rules-file api_gateway_private.guard --test-data api_gateway_private_tests.yaml
   ```

   第一個測試的結果是 `PASS`。

   ```
   Test Case #1
   Name: "MyTest1"
     PASS Rules:
       check_rest_api_is_private: Expected = SKIP, Evaluated = SKIP
   ```

1. 將另一個測試新增至您的單元測試檔案。現在，擴展測試以包含空白資源。以下是更新的 `api_gateway_private_tests.yaml` 檔案。

   ```
   ---
   - name: MyTest1
     input: {}
     expectations:
       rules:
         check_rest_api_is_private: SKIP
   - name: MyTest2
     input:
        Resources: {}
     expectations:
       rules:
         check_rest_api_is_private: SKIP
   ```

1. `test` 使用更新的單位測試檔案執行 。

   ```
   cfn-guard test --rules-file api_gateway_private.guard --test-data api_gateway_private_tests.yaml
   ```

   第二個測試的結果是 `PASS`。

   ```
   Test Case #1
   Name: "MyTest1"
     PASS Rules:
       check_rest_api_is_private: Expected = SKIP, Evaluated = SKIP
   Test Case #2
   Name: "MyTest2"
     PASS Rules:
       check_rest_api_is_private: Expected = SKIP, Evaluated = SKIP
   ```

1. 將另外兩個測試新增至您的單元測試檔案。擴展測試以包含下列項目：
   + 未指定屬性`AWS::ApiGateway::RestApi`的資源。
**注意**  
這不是有效的 CloudFormation 範本，但即使輸入格式不正確，測試規則是否正常運作也很有用。

     預期此測試會失敗，因為未指定 `EndpointConfiguration` 屬性，因此未設定為 `PRIVATE`。
   + 一種`AWS::ApiGateway::RestApi`資源，其滿足 `EndpointConfiguration` 屬性設定為 的第一個意圖，`PRIVATE`但無法滿足第二個意圖，因為它未定義政策陳述式。預期此測試將會通過。

   以下是更新的單位測試檔案。

   ```
   ---
   - name: MyTest1
     input: {}
     expectations:
       rules:
         check_rest_api_is_private: SKIP
   - name: MyTest2
     input:
        Resources: {}
     expectations:
       rules:
         check_rest_api_is_private: SKIP
   - name: MyTest3
     input:
       Resources: 
         apiGw:
           Type: AWS::ApiGateway::RestApi
     expectations:
       rules:
         check_rest_api_is_private: FAIL
   - name: MyTest4
     input:
       Resources: 
         apiGw:
           Type: AWS::ApiGateway::RestApi
           Properties:
             EndpointConfiguration:
               Types: "PRIVATE"
     expectations:
       rules:
         check_rest_api_is_private: PASS
   ```

1. `test` 使用更新的單位測試檔案執行 。

   ```
   cfn-guard test --rules-file api_gateway_private.guard --test-data api_gateway_private_tests.yaml \
   ```

   第三個結果是 `FAIL`，第四個結果是 `PASS`。

   ```
   Test Case #1
   Name: "MyTest1"
     PASS Rules:
       check_rest_api_is_private: Expected = SKIP, Evaluated = SKIP
   
   Test Case #2
   Name: "MyTest2"
     PASS Rules:
       check_rest_api_is_private: Expected = SKIP, Evaluated = SKIP
   
   Test Case #3
   Name: "MyTest3"
     PASS Rules:
       check_rest_api_is_private: Expected = FAIL, Evaluated = FAIL
   
   Test Case #4
   Name: "MyTest4"
     PASS Rules:
       check_rest_api_is_private: Expected = PASS, Evaluated = PASS
   ```

1. 在您的單元測試檔案中註解測試 1–3。僅存取第四個測試的詳細內容。以下是更新的單位測試檔案。

   ```
   ---
   #- name: MyTest1
   #  input: {}
   #  expectations:
   #    rules:
   #      check_rest_api_is_private_and_has_access: SKIP
   #- name: MyTest2
   #  input:
   #     Resources: {}
   #  expectations:
   #    rules:
   #      check_rest_api_is_private_and_has_access: SKIP
   #- name: MyTest3
   #  input:
   #    Resources: 
   #      apiGw:
   #        Type: AWS::ApiGateway::RestApi
   #  expectations:
   #    rules:
   #      check_rest_api_is_private_and_has_access: FAIL
   - name: MyTest4
     input:
       Resources: 
         apiGw:
           Type: AWS::ApiGateway::RestApi
           Properties:
             EndpointConfiguration:
               Types: "PRIVATE"
     expectations:
       rules:
         check_rest_api_is_private: PASS
   ```

1. 使用 `--verbose`旗標，在終端機中執行 `test`命令來檢查評估結果。詳細內容有助於了解評估。在這種情況下，它會提供有關第四個測試成功產生`PASS`結果的原因的詳細資訊。

   ```
   cfn-guard test --rules-file api_gateway_private.guard --test-data api_gateway_private_tests.yaml \
     --verbose
   ```

   以下是該執行的輸出。

   ```
   Test Case #1
   Name: "MyTest4"
     PASS Rules:
       check_rest_api_is_private: Expected = PASS, Evaluated = PASS
   Rule(check_rest_api_is_private, PASS)
       |  Message: DEFAULT MESSAGE(PASS)
       Condition(check_rest_api_is_private, PASS)
           |  Message: DEFAULT MESSAGE(PASS)
           Clause(Clause(Location[file:api_gateway_private.guard, line:20, column:37], Check: %api_gws NOT EMPTY ), PASS)
               |  From: Map((Path("/Resources/apiGw"), MapValue { keys: [String((Path("/Resources/apiGw/Type"), "Type")), String((Path("/Resources/apiGw/Properties"), "Properties"))], values: {"Type": String((Path("/Resources/apiGw/Type"), "AWS::ApiGateway::RestApi")), "Properties": Map((Path("/Resources/apiGw/Properties"), MapValue { keys: [String((Path("/Resources/apiGw/Properties/EndpointConfiguration"), "EndpointConfiguration"))], values: {"EndpointConfiguration": Map((Path("/Resources/apiGw/Properties/EndpointConfiguration"), MapValue { keys: [String((Path("/Resources/apiGw/Properties/EndpointConfiguration/Types"), "Types"))], values: {"Types": String((Path("/Resources/apiGw/Properties/EndpointConfiguration/Types"), "PRIVATE"))} }))} }))} }))
               |  Message: (DEFAULT: NO_MESSAGE)
       Conjunction(cfn_guard::rules::exprs::GuardClause, PASS)
           |  Message: DEFAULT MESSAGE(PASS)
           Clause(Clause(Location[file:api_gateway_private.guard, line:22, column:5], Check: Properties.EndpointConfiguration.Types[*]  EQUALS String("PRIVATE")), PASS)
               |  Message: (DEFAULT: NO_MESSAGE)
   ```

   來自輸出的關鍵觀察是行 `Clause(Location[file:api_gateway_private.guard, line:22, column:5], Check: Properties.EndpointConfiguration.Types[*] EQUALS String("PRIVATE")), PASS)`，指出檢查通過。此範例也顯示 `Types` 預期為陣列，但提供單一值的案例。在這種情況下，Guard 會繼續評估並提供正確的結果。

1. 將如第四個測試案例的測試案例新增至具有指定 `EndpointConfiguration` 屬性之`AWS::ApiGateway::RestApi`資源的單元測試檔案。測試案例將會失敗，而不是通過。以下是更新的單位測試檔案。

   ```
   ---
   #- name: MyTest1
   #  input: {}
   #  expectations:
   #    rules:
   #      check_rest_api_is_private_and_has_access: SKIP
   #- name: MyTest2
   #  input:
   #     Resources: {}
   #  expectations:
   #    rules:
   #      check_rest_api_is_private_and_has_access: SKIP
   #- name: MyTest3
   #  input:
   #    Resources: 
   #      apiGw:
   #        Type: AWS::ApiGateway::RestApi
   #  expectations:
   #    rules:
   #      check_rest_api_is_private_and_has_access: FAIL
   #- name: MyTest4
   #  input:
   #    Resources: 
   #      apiGw:
   #        Type: AWS::ApiGateway::RestApi
   #        Properties:
   #          EndpointConfiguration:
   #            Types: "PRIVATE"
   #  expectations:
   #    rules:
   #      check_rest_api_is_private: PASS
   - name: MyTest5
     input:
       Resources: 
         apiGw:
           Type: AWS::ApiGateway::RestApi
           Properties:
             EndpointConfiguration:
               Types: [PRIVATE, REGIONAL]
     expectations:
       rules:
         check_rest_api_is_private: FAIL
   ```

1. 使用 `--verbose`旗標搭配更新的單元測試檔案執行 `test`命令。

   ```
   cfn-guard test --rules-file api_gateway_private.guard --test-data api_gateway_private_tests.yaml \
    --verbose
   ```

   結果會`FAIL`如預期，因為 `REGIONAL` 是針對 指定，`EndpointConfiguration`但不預期。

   ```
   Test Case #1
   Name: "MyTest5"
     PASS Rules: 
       check_rest_api_is_private: Expected = FAIL, Evaluated = FAIL
   Rule(check_rest_api_is_private, FAIL)
       |  Message: DEFAULT MESSAGE(FAIL)
       Condition(check_rest_api_is_private, PASS) 
           |  Message: DEFAULT MESSAGE(PASS)
           Clause(Clause(Location[file:api_gateway_private.guard, line:20, column:37], Check: %api_gws NOT EMPTY ), PASS)
               |  From: Map((Path("/Resources/apiGw"), MapValue { keys: [String((Path("/Resources/apiGw/Type"), "Type")), String((Path("/Resources/apiGw/Properties"), "Properties"))], values: {"Type": String((Path("/Resources/apiGw/Type"), "AWS::ApiGateway::RestApi")), "Properties": Map((Path("/Resources/apiGw/Properties"), MapValue { keys: [String((Path("/Resources/apiGw/Properties/EndpointConfiguration"), "EndpointConfiguration"))], values: {"EndpointConfiguration": Map((Path("/Resources/apiGw/Properties/EndpointConfiguration"), MapValue { keys: [String((Path("/Resources/apiGw/Properties/EndpointConfiguration/Types"), "Types"))], values: {"Types": List((Path("/Resources/apiGw/Properties/EndpointConfiguration/Types"), [String((Path("/Resources/apiGw/Properties/EndpointConfiguration/Types/0"), "PRIVATE")), String((Path("/Resources/apiGw/Properties/EndpointConfiguration/Types/1"), "REGIONAL"))]))} }))} }))} }))
               |  Message: DEFAULT MESSAGE(PASS)
       BlockClause(Block[Location[file:api_gateway_private.guard, line:21, column:3]], FAIL)
           |  Message: DEFAULT MESSAGE(FAIL)
           Conjunction(cfn_guard::rules::exprs::GuardClause, FAIL)
               |  Message: DEFAULT MESSAGE(FAIL)
               Clause(Clause(Location[file:api_gateway_private.guard, line:22, column:5], Check: Properties.EndpointConfiguration.Types[*]  EQUALS String("PRIVATE")), FAIL)
                   |  From: String((Path("/Resources/apiGw/Properties/EndpointConfiguration/Types/1"), "REGIONAL"))
                   |  To: String((Path("api_gateway_private.guard/22/5/Clause/"), "PRIVATE"))
                   |  Message: (DEFAULT: NO_MESSAGE)
   ```

   `test` 命令的詳細輸出遵循規則檔案的結構。規則檔案中的每個區塊都是詳細輸出中的區塊。最上方的區塊是每個規則。如果有針對規則`when`的條件，這些條件會出現在同盟條件區塊中。在下列範例中，`%api_gws !empty`測試條件並通過。

   ```
   rule check_rest_api_is_private when %api_gws !empty {
   ```

   一旦條件通過，我們會測試規則子句。

   ```
   %api_gws {
       Properties.EndpointConfiguration.Types[*] == "PRIVATE"                      
   }
   ```

   `%api_gws` 是區塊規則，對應至輸出中的`BlockClause`層級 （行：21)。規則子句是一組結合 (AND) 子句，其中每個結合子句都是一組接合 (`OR`)。結合具有單一子句 `Properties.EndpointConfiguration.Types[*] == "PRIVATE"`。因此，詳細輸出會顯示單一子句。路徑`/Resources/apiGw/Properties/EndpointConfiguration/Types/1`會顯示要比較輸入中的哪些值，在此案例中是索引`Types`為 1 的 元素。

在 中[根據 Guard 規則驗證輸入資料](validating-rules.md)，您可以使用本節中的範例，使用 `validate`命令根據規則評估輸入資料。