github.com/googleapis/api-linter@v1.65.2/lint/config_test.go (about)

     1  // Copyright 2019 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  // 		https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package lint
    16  
    17  import (
    18  	"errors"
    19  	"os"
    20  	"path/filepath"
    21  	"reflect"
    22  	"strings"
    23  	"testing"
    24  )
    25  
    26  func TestRuleConfigs_IsRuleEnabled(t *testing.T) {
    27  	enabled := true
    28  	disabled := false
    29  
    30  	tests := []struct {
    31  		name    string
    32  		configs Configs
    33  		path    string
    34  		rule    string
    35  		want    bool
    36  	}{
    37  		{"EmptyConfig", nil, "a", "b", enabled},
    38  		{
    39  			"NoConfigMatched_Enabled",
    40  			Configs{
    41  				{
    42  					IncludedPaths: []string{"a.proto"},
    43  					DisabledRules: []string{"testrule"},
    44  				},
    45  			},
    46  			"b.proto",
    47  			"testrule",
    48  			enabled,
    49  		},
    50  		{
    51  			"PathMatched_DisabledRulesNotMatch_Enabled",
    52  			Configs{
    53  				{
    54  					IncludedPaths: []string{"a.proto"},
    55  					DisabledRules: []string{"somerule"},
    56  				},
    57  			},
    58  			"a.proto",
    59  			"testrule",
    60  			enabled,
    61  		},
    62  		{
    63  			"PathExactMatched_DisabledRulesMatched_Disabled",
    64  			Configs{
    65  				{
    66  					IncludedPaths: []string{"a.proto"},
    67  					DisabledRules: []string{"somerule", "testrule"},
    68  				},
    69  			},
    70  			"a.proto",
    71  			"testrule",
    72  			disabled,
    73  		},
    74  		{
    75  			"PathExactMatched_DisabledRulesMatchedAll_Disabled",
    76  			Configs{
    77  				{
    78  					IncludedPaths: []string{"a.proto"},
    79  					DisabledRules: []string{"all"},
    80  				},
    81  			},
    82  			"a.proto",
    83  			"testrule",
    84  			disabled,
    85  		},
    86  		{
    87  			"PathDoubleStartMatched_DisabledRulesMatched_Disabled",
    88  			Configs{
    89  				{
    90  					IncludedPaths: []string{"a/**/*.proto"},
    91  					DisabledRules: []string{"somerule", "testrule"},
    92  				},
    93  			},
    94  			"a/with/long/sub/dir/etc/ory/e.proto",
    95  			"testrule",
    96  			disabled,
    97  		},
    98  		{
    99  			"PathMatched_DisabledRulesPrefixMatched_Disabled",
   100  			Configs{
   101  				{
   102  					IncludedPaths: []string{"a/b/c.proto"},
   103  					DisabledRules: []string{"parent"},
   104  				},
   105  			},
   106  			"a/b/c.proto",
   107  			"parent::test_rule",
   108  			disabled,
   109  		},
   110  		{
   111  			"PathMatched_DisabledRulesSuffixMatched_Disabled",
   112  			Configs{
   113  				{
   114  					IncludedPaths: []string{"a/b/c.proto"},
   115  					DisabledRules: []string{"child"},
   116  				},
   117  			},
   118  			"a/b/c.proto",
   119  			"parent::child",
   120  			disabled,
   121  		},
   122  		{
   123  			"PathMatched_DisabledRulesMiddleMatched_Disabled",
   124  			Configs{
   125  				{
   126  					IncludedPaths: []string{"a/b/c.proto"},
   127  					DisabledRules: []string{"middle"},
   128  				},
   129  			},
   130  			"a/b/c.proto",
   131  			"parent::middle::child",
   132  			disabled,
   133  		},
   134  		{
   135  			"EmptyIncludePath_ConfigMatched_DisabledRulesMatched_Disabled",
   136  			Configs{
   137  				{
   138  					DisabledRules: []string{"testrule"},
   139  				},
   140  			},
   141  			"a.proto",
   142  			"testrule",
   143  			disabled,
   144  		},
   145  		{
   146  			"ExcludedPathMatch_ConfigNotMatched_DisabledRulesMatched_Enabled",
   147  			Configs{
   148  				{
   149  					ExcludedPaths: []string{"a.proto"},
   150  					DisabledRules: []string{"testrule"},
   151  				},
   152  			},
   153  			"a.proto",
   154  			"testrule",
   155  			enabled,
   156  		},
   157  		{
   158  			"TwoConfigs_Override_Enabled",
   159  			Configs{
   160  				{
   161  					DisabledRules: []string{"testrule"},
   162  				},
   163  				{
   164  					EnabledRules: []string{"testrule::a"},
   165  				},
   166  			},
   167  			"a.proto",
   168  			"testrule::a",
   169  			enabled,
   170  		},
   171  		{
   172  			"TwoConfigs_Override_Disabled",
   173  			Configs{
   174  				{
   175  					EnabledRules: []string{"testrule"},
   176  				},
   177  				{
   178  					DisabledRules: []string{"testrule::a"},
   179  				},
   180  			},
   181  			"a.proto",
   182  			"testrule::a",
   183  			disabled,
   184  		},
   185  		{
   186  			"TwoConfigs_DoubleEnable_Enabled",
   187  			Configs{
   188  				{
   189  					EnabledRules: []string{"testrule"},
   190  				},
   191  				{
   192  					EnabledRules: []string{"testrule::a"},
   193  				},
   194  			},
   195  			"a.proto",
   196  			"testrule::a",
   197  			enabled,
   198  		},
   199  		{
   200  			"TwoConfigs_DoubleDisabled_Disabled",
   201  			Configs{
   202  				{
   203  					DisabledRules: []string{"testrule"},
   204  				},
   205  				{
   206  					DisabledRules: []string{"testrule::a"},
   207  				},
   208  			},
   209  			"a.proto",
   210  			"testrule::a",
   211  			disabled,
   212  		},
   213  		{
   214  			"NoConfigMatched_DefaultDisabled",
   215  			Configs{
   216  				{
   217  					IncludedPaths: []string{"a.proto"},
   218  					DisabledRules: []string{"testrule"},
   219  				},
   220  			},
   221  			"b.proto",
   222  			"cloud::25164::generic-fields",
   223  			disabled,
   224  		},
   225  		{
   226  			"ConfigMatched_DefaultDisabled_Enabled",
   227  			Configs{
   228  				{
   229  					IncludedPaths: []string{"a.proto"},
   230  					EnabledRules:  []string{"cloud"},
   231  				},
   232  			},
   233  			"a.proto",
   234  			"cloud::25164::generic-fields",
   235  			enabled,
   236  		},
   237  	}
   238  	for _, test := range tests {
   239  		t.Run(test.name, func(t *testing.T) {
   240  			got := test.configs.IsRuleEnabled(test.rule, test.path)
   241  
   242  			if got != test.want {
   243  				t.Errorf("IsRuleEnabled: got %t, but want %t", got, test.want)
   244  			}
   245  		})
   246  	}
   247  }
   248  
   249  type errReader int
   250  
   251  func (errReader) Read(p []byte) (int, error) {
   252  	return 0, errors.New("test error")
   253  }
   254  
   255  func TestReadConfigsJSONReaderError(t *testing.T) {
   256  	if _, err := ReadConfigsJSON(errReader(0)); err == nil {
   257  		t.Error("ReadConfigsJSON expects an error")
   258  	}
   259  }
   260  
   261  func TestReadConfigsJSONFormatError(t *testing.T) {
   262  	invalidJSON := `
   263  	[
   264  		{
   265  			"included_paths": ["path_a"],
   266  			"excluded_paths": ["path_b"],
   267  			"disabled_rules": ["rule_a", "rule_b"],
   268  			"enabled_rules": ["rule_c", "rule_d"]
   269  		}
   270  	`
   271  
   272  	if _, err := ReadConfigsJSON(strings.NewReader(invalidJSON)); err == nil {
   273  		t.Error("ReadConfigsJSON expects an error")
   274  	}
   275  }
   276  
   277  func TestReadConfigsJSON(t *testing.T) {
   278  	content := `
   279  	[
   280  		{
   281  			"included_paths": ["path_a"],
   282  			"excluded_paths": ["path_b"],
   283  			"disabled_rules": ["rule_a", "rule_b"],
   284  			"enabled_rules": ["rule_c", "rule_d"]
   285  		}
   286  	]
   287  	`
   288  
   289  	configs, err := ReadConfigsJSON(strings.NewReader(content))
   290  	if err != nil {
   291  		t.Errorf("ReadConfigsJSON returns error: %v", err)
   292  	}
   293  
   294  	expected := Configs{
   295  		{
   296  			IncludedPaths: []string{"path_a"},
   297  			ExcludedPaths: []string{"path_b"},
   298  			DisabledRules: []string{"rule_a", "rule_b"},
   299  			EnabledRules:  []string{"rule_c", "rule_d"},
   300  		},
   301  	}
   302  	if !reflect.DeepEqual(configs, expected) {
   303  		t.Errorf("ReadConfigsJSON returns %v, but want %v", configs, expected)
   304  	}
   305  }
   306  
   307  func TestReadConfigsYAMLReaderError(t *testing.T) {
   308  	if _, err := ReadConfigsYAML(errReader(0)); err == nil {
   309  		t.Error("ReadConfigsYAML expects an error")
   310  	}
   311  }
   312  
   313  func TestReadConfigsYAMLFormatError(t *testing.T) {
   314  	invalidYAML := `
   315  	[
   316  		{
   317  			"included_paths": ["path_a"],
   318  			"excluded_paths": ["path_b"],
   319  			"disabled_rules": ["rule_a", "rule_b"],
   320  			"enabled_rules": ["rule_c", "rule_d"]
   321  		}
   322  	`
   323  
   324  	if _, err := ReadConfigsYAML(strings.NewReader(invalidYAML)); err == nil {
   325  		t.Error("ReadConfigsYAML expects an error")
   326  	}
   327  }
   328  
   329  func TestReadConfigsYAML(t *testing.T) {
   330  	content := `
   331  ---
   332  - included_paths:
   333      - 'path_a'
   334    excluded_paths:
   335      - 'path_b'
   336    disabled_rules:
   337      - 'rule_a'
   338      - 'rule_b'
   339    enabled_rules:
   340      - 'rule_c'
   341      - 'rule_d'
   342  `
   343  
   344  	configs, err := ReadConfigsYAML(strings.NewReader(content))
   345  	if err != nil {
   346  		t.Errorf("ReadConfigsYAML returns error: %v", err)
   347  	}
   348  
   349  	expected := Configs{
   350  		{
   351  			IncludedPaths: []string{"path_a"},
   352  			ExcludedPaths: []string{"path_b"},
   353  			DisabledRules: []string{"rule_a", "rule_b"},
   354  			EnabledRules:  []string{"rule_c", "rule_d"},
   355  		},
   356  	}
   357  	if !reflect.DeepEqual(configs, expected) {
   358  		t.Errorf("ReadConfigsYAML returns %v, but want %v", configs, expected)
   359  	}
   360  }
   361  
   362  func TestReadConfigsFromFile(t *testing.T) {
   363  	expectedConfigs := Configs{
   364  		{
   365  			IncludedPaths: []string{"path_a"},
   366  			ExcludedPaths: []string{"path_b"},
   367  			DisabledRules: []string{"rule_a", "rule_b"},
   368  			EnabledRules:  []string{"rule_c", "rule_d"},
   369  		},
   370  	}
   371  
   372  	jsonConfigsText := `
   373  	[
   374  		{
   375  			"included_paths": ["path_a"],
   376  			"excluded_paths": ["path_b"],
   377  			"disabled_rules": ["rule_a", "rule_b"],
   378  			"enabled_rules": ["rule_c", "rule_d"]
   379  		}
   380  	]
   381  	`
   382  	jsonConfigsFile := createTempFile(t, "test.json", jsonConfigsText)
   383  	defer os.Remove(jsonConfigsFile)
   384  
   385  	yamlConfigsText := `
   386  ---
   387  - included_paths:
   388      - 'path_a'
   389    excluded_paths:
   390      - 'path_b'
   391    disabled_rules:
   392      - 'rule_a'
   393      - 'rule_b'
   394    enabled_rules:
   395      - 'rule_c'
   396      - 'rule_d'
   397  `
   398  	yamlConfigsFile := createTempFile(t, "test.yaml", yamlConfigsText)
   399  	defer os.Remove(yamlConfigsFile)
   400  
   401  	tests := []struct {
   402  		name     string
   403  		filePath string
   404  		configs  Configs
   405  		hasErr   bool
   406  	}{
   407  		{
   408  			"JSON file",
   409  			jsonConfigsFile,
   410  			expectedConfigs,
   411  			false,
   412  		},
   413  		{
   414  			"YAML file",
   415  			yamlConfigsFile,
   416  			expectedConfigs,
   417  			false,
   418  		},
   419  		{
   420  			"Invalid file extension",
   421  			"test.abc",
   422  			nil,
   423  			true,
   424  		},
   425  		{
   426  			"File not existed",
   427  			"not-existed-file.json",
   428  			nil,
   429  			true,
   430  		},
   431  	}
   432  	for _, test := range tests {
   433  		t.Run(test.name, func(t *testing.T) {
   434  			configs, err := ReadConfigsFromFile(test.filePath)
   435  			if (err != nil) != test.hasErr {
   436  				t.Errorf("ReadConfigsFromFile got error %v, but want %v", err, test.hasErr)
   437  			}
   438  			if err != nil {
   439  				if !reflect.DeepEqual(configs, test.configs) {
   440  					t.Errorf("ReadConfigsFromFile got configs %v, but want %v", configs, test.configs)
   441  				}
   442  			}
   443  		})
   444  	}
   445  }
   446  
   447  func createTempFile(t *testing.T, name, content string) string {
   448  	dir, err := os.MkdirTemp("", "config_tests")
   449  	if err != nil {
   450  		t.Fatal(err)
   451  	}
   452  	filePath := filepath.Join(dir, name)
   453  	if err := os.WriteFile(filePath, []byte(content), 0o644); err != nil {
   454  		t.Fatal(err)
   455  	}
   456  	return filePath
   457  }