591 lines
12 KiB
Go
591 lines
12 KiB
Go
// 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)
|
|
}
|