// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package random import ( "encoding/json" "reflect" "testing" ) func TestParsePolicy(t *testing.T) { type testCase struct { rawConfig string expected StringGenerator expectErr bool } tests := map[string]testCase{ "unrecognized rule": { rawConfig: ` length = 20 rule "testrule" { string = "teststring" int = 123 }`, expected: StringGenerator{}, expectErr: true, }, "charset restrictions": { rawConfig: ` length = 20 rule "charset" { charset = "abcde" min-chars = 2 }`, expected: StringGenerator{ Length: 20, charset: []rune("abcde"), Rules: []Rule{ CharsetRule{ Charset: []rune("abcde"), MinChars: 2, }, }, }, expectErr: false, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { actual, err := ParsePolicy(test.rawConfig) if test.expectErr && err == nil { t.Fatalf("err expected, got nil") } if !test.expectErr && err != nil { t.Fatalf("no error expected, got: %s", err) } if !reflect.DeepEqual(actual, test.expected) { t.Fatalf("Actual: %#v\nExpected:%#v", actual, test.expected) } }) } } func TestParser_ParsePolicy(t *testing.T) { type testCase struct { registry map[string]ruleConstructor rawConfig string expected StringGenerator expectErr bool } tests := map[string]testCase{ "empty config": { registry: defaultRuleNameMapping, rawConfig: "", expected: StringGenerator{}, expectErr: true, }, "bogus config": { registry: defaultRuleNameMapping, rawConfig: "asdf", expected: StringGenerator{}, expectErr: true, }, "config with only length": { registry: defaultRuleNameMapping, rawConfig: ` length = 20`, expected: StringGenerator{}, expectErr: true, }, "config with zero length": { registry: defaultRuleNameMapping, rawConfig: ` length = 0 rule "charset" { charset = "abcde" }`, expected: StringGenerator{}, expectErr: true, }, "config with negative length": { registry: defaultRuleNameMapping, rawConfig: ` length = -2 rule "charset" { charset = "abcde" }`, expected: StringGenerator{}, expectErr: true, }, "charset restrictions": { registry: defaultRuleNameMapping, rawConfig: ` length = 20 rule "charset" { charset = "abcde" min-chars = 2 }`, expected: StringGenerator{ Length: 20, charset: []rune("abcde"), Rules: []Rule{ CharsetRule{ Charset: []rune("abcde"), MinChars: 2, }, }, }, expectErr: false, }, "test rule": { registry: map[string]ruleConstructor{ "testrule": newTestRule, }, rawConfig: ` length = 20 rule "testrule" { string = "teststring" int = 123 }`, expected: StringGenerator{ Length: 20, charset: deduplicateRunes([]rune("teststring")), Rules: []Rule{ testCharsetRule{ String: "teststring", Integer: 123, }, }, }, expectErr: false, }, "test rule and charset restrictions": { registry: map[string]ruleConstructor{ "testrule": newTestRule, "charset": ParseCharset, }, rawConfig: ` length = 20 rule "testrule" { string = "teststring" int = 123 } rule "charset" { charset = "abcde" min-chars = 2 }`, expected: StringGenerator{ Length: 20, charset: deduplicateRunes([]rune("abcdeteststring")), Rules: []Rule{ testCharsetRule{ String: "teststring", Integer: 123, }, CharsetRule{ Charset: []rune("abcde"), MinChars: 2, }, }, }, expectErr: false, }, "unrecognized rule": { registry: defaultRuleNameMapping, rawConfig: ` length = 20 rule "testrule" { string = "teststring" int = 123 }`, expected: StringGenerator{}, expectErr: true, }, // ///////////////////////////////////////////////// // JSON data "manually JSONified HCL": { registry: map[string]ruleConstructor{ "testrule": newTestRule, "charset": ParseCharset, }, rawConfig: ` { "charset": "abcde", "length": 20, "rule": [ { "testrule": [ { "string": "teststring", "int": 123 } ] }, { "charset": [ { "charset": "abcde", "min-chars": 2 } ] } ] }`, expected: StringGenerator{ Length: 20, charset: deduplicateRunes([]rune("abcdeteststring")), Rules: []Rule{ testCharsetRule{ String: "teststring", Integer: 123, }, CharsetRule{ Charset: []rune("abcde"), MinChars: 2, }, }, }, expectErr: false, }, "JSONified HCL": { registry: map[string]ruleConstructor{ "testrule": newTestRule, "charset": ParseCharset, }, rawConfig: toJSON(t, StringGenerator{ Length: 20, Rules: []Rule{ testCharsetRule{ String: "teststring", Integer: 123, }, CharsetRule{ Charset: []rune("abcde"), MinChars: 2, }, }, }), expected: StringGenerator{ Length: 20, charset: deduplicateRunes([]rune("abcdeteststring")), Rules: []Rule{ testCharsetRule{ String: "teststring", Integer: 123, }, CharsetRule{ Charset: []rune("abcde"), MinChars: 2, }, }, }, expectErr: false, }, "JSON unrecognized rule": { registry: defaultRuleNameMapping, rawConfig: ` { "charset": "abcde", "length": 20, "rule": [ { "testrule": [ { "string": "teststring", "int": 123 } ], } ] }`, expected: StringGenerator{}, expectErr: true, }, "config value with empty slice": { registry: defaultRuleNameMapping, rawConfig: ` rule { n = [] }`, expected: StringGenerator{}, expectErr: true, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { parser := PolicyParser{ RuleRegistry: Registry{ Rules: test.registry, }, } actual, err := parser.ParsePolicy(test.rawConfig) if test.expectErr && err == nil { t.Fatalf("err expected, got nil") } if !test.expectErr && err != nil { t.Fatalf("no error expected, got: %s", err) } if !reflect.DeepEqual(actual, test.expected) { t.Fatalf("Actual: %#v\nExpected:%#v", actual, test.expected) } }) } } func TestParseRules(t *testing.T) { type testCase struct { registry map[string]ruleConstructor rawRules []map[string]interface{} expectedRules []Rule expectErr bool } tests := map[string]testCase{ "nil rule data": { registry: defaultRuleNameMapping, rawRules: nil, expectedRules: nil, expectErr: false, }, "empty rule data": { registry: defaultRuleNameMapping, rawRules: []map[string]interface{}{}, expectedRules: nil, expectErr: false, }, "invalid rule data": { registry: defaultRuleNameMapping, rawRules: []map[string]interface{}{ { "testrule": map[string]interface{}{ "string": "teststring", }, }, }, expectedRules: nil, expectErr: true, }, "unrecognized rule data": { registry: defaultRuleNameMapping, rawRules: []map[string]interface{}{ { "testrule": []map[string]interface{}{ { "string": "teststring", "int": 123, }, }, }, }, expectedRules: nil, expectErr: true, }, "recognized rule": { registry: map[string]ruleConstructor{ "testrule": newTestRule, }, rawRules: []map[string]interface{}{ { "testrule": []map[string]interface{}{ { "string": "teststring", "int": 123, }, }, }, }, expectedRules: []Rule{ testCharsetRule{ String: "teststring", Integer: 123, }, }, expectErr: false, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { registry := Registry{ Rules: test.registry, } actualRules, err := parseRules(registry, test.rawRules) if test.expectErr && err == nil { t.Fatalf("err expected, got nil") } if !test.expectErr && err != nil { t.Fatalf("no error expected, got: %s", err) } if !reflect.DeepEqual(actualRules, test.expectedRules) { t.Fatalf("Actual: %#v\nExpected:%#v", actualRules, test.expectedRules) } }) } } func TestGetMapSlice(t *testing.T) { type testCase struct { input map[string]interface{} key string expectedSlice []map[string]interface{} expectErr bool } tests := map[string]testCase{ "nil map": { input: nil, key: "testkey", expectedSlice: nil, expectErr: false, }, "empty map": { input: map[string]interface{}{}, key: "testkey", expectedSlice: nil, expectErr: false, }, "ignored keys": { input: map[string]interface{}{ "foo": "bar", }, key: "testkey", expectedSlice: nil, expectErr: false, }, "key has wrong type": { input: map[string]interface{}{ "foo": "bar", }, key: "foo", expectedSlice: nil, expectErr: true, }, "good data": { input: map[string]interface{}{ "foo": []map[string]interface{}{ { "sub-foo": "bar", }, }, }, key: "foo", expectedSlice: []map[string]interface{}{ { "sub-foo": "bar", }, }, expectErr: false, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { actualSlice, err := getMapSlice(test.input, test.key) if test.expectErr && err == nil { t.Fatalf("err expected, got nil") } if !test.expectErr && err != nil { t.Fatalf("no error expected, got: %s", err) } if !reflect.DeepEqual(actualSlice, test.expectedSlice) { t.Fatalf("Actual: %#v\nExpected:%#v", actualSlice, test.expectedSlice) } }) } } func TestGetRuleInfo(t *testing.T) { type testCase struct { rule map[string]interface{} expectedInfo ruleInfo expectErr bool } tests := map[string]testCase{ "nil rule": { rule: nil, expectedInfo: ruleInfo{}, expectErr: true, }, "empty rule": { rule: map[string]interface{}{}, expectedInfo: ruleInfo{}, expectErr: true, }, "rule with invalid type": { rule: map[string]interface{}{ "TestRuleType": "wrong type", }, expectedInfo: ruleInfo{}, expectErr: true, }, "rule with good data": { rule: map[string]interface{}{ "TestRuleType": []map[string]interface{}{ { "foo": "bar", }, }, }, expectedInfo: ruleInfo{ ruleType: "TestRuleType", data: map[string]interface{}{ "foo": "bar", }, }, expectErr: false, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { actualInfo, err := getRuleInfo(test.rule) if test.expectErr && err == nil { t.Fatalf("err expected, got nil") } if !test.expectErr && err != nil { t.Fatalf("no error expected, got: %s", err) } if !reflect.DeepEqual(actualInfo, test.expectedInfo) { t.Fatalf("Actual: %#v\nExpected:%#v", actualInfo, test.expectedInfo) } }) } } func BenchmarkParser_Parse(b *testing.B) { config := `length = 20 rule "charset" { charset = "abcde" min-chars = 2 }` for i := 0; i < b.N; i++ { parser := PolicyParser{ RuleRegistry: Registry{ Rules: defaultRuleNameMapping, }, } _, err := parser.ParsePolicy(config) if err != nil { b.Fatalf("Failed to parse: %s", err) } } } func toJSON(t *testing.T, val interface{}) string { t.Helper() b, err := json.Marshal(val) if err != nil { t.Fatalf("unable to marshal to JSON: %s", err) } return string(b) }