k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/builder/parameters_test.go (about)

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package builder
    18  
    19  import (
    20  	"encoding/json"
    21  	"reflect"
    22  	"testing"
    23  
    24  	"github.com/stretchr/testify/require"
    25  
    26  	"k8s.io/kube-openapi/pkg/validation/spec"
    27  )
    28  
    29  func TestCollectSharedParameters(t *testing.T) {
    30  	tests := []struct {
    31  		name string
    32  		spec string
    33  		want map[string]string
    34  	}{
    35  		{
    36  			name: "empty",
    37  			spec: "",
    38  			want: nil,
    39  		},
    40  		{
    41  			name: "no shared",
    42  			spec: `{
    43    "parameters": {"pre": {"in": "body", "name": "body", "required": true, "schema": {}}},
    44    "paths": {
    45      "/api/v1/a/{name}": {"get": {"parameters": [
    46  		  {"description": "x","in":"query","name": "x","type":"boolean","uniqueItems":true},
    47  		  {"description": "y","in":"query","name": "y","type":"boolean","uniqueItems":true}
    48      ]}},
    49      "/api/v1/a/{name}/foo": {"get": {"parameters": [
    50  		  {"description": "z","in":"query","name": "z","type":"boolean","uniqueItems":true}
    51      ]}},
    52      "/api/v1/b/{name}": {"get": {"parameters": [
    53  		  {"description": "x","in":"query","name": "x2","type":"boolean","uniqueItems":true},
    54  		  {"description": "y","in":"query","name": "y2","type":"boolean","uniqueItems":true}
    55      ]}},
    56      "/api/v1/b/{name}/foo": {"get": {"parameters": [
    57  		  {"description": "z","in":"query","name": "z2","type":"boolean","uniqueItems":true}
    58      ]}}
    59    }
    60  }`,
    61  			want: map[string]string{
    62  				`{"uniqueItems":true,"type":"boolean","description":"x","name":"x","in":"query"}`:  "x-yaDSHpi7",
    63  				`{"uniqueItems":true,"type":"boolean","description":"y","name":"y","in":"query"}`:  "y-g6h7lEsz",
    64  				`{"uniqueItems":true,"type":"boolean","description":"z","name":"z","in":"query"}`:  "z--SXYWoM_",
    65  				`{"uniqueItems":true,"type":"boolean","description":"x","name":"x2","in":"query"}`: "x2-nds6MpS1",
    66  				`{"uniqueItems":true,"type":"boolean","description":"y","name":"y2","in":"query"}`: "y2-exnalzYE",
    67  				`{"uniqueItems":true,"type":"boolean","description":"z","name":"z2","in":"query"}`: "z2-8oJfzBQF",
    68  			},
    69  		},
    70  		{
    71  			name: "shared per operation",
    72  			spec: `{
    73    "parameters": {"pre": {"in": "body", "name": "body", "required": true, "schema": {}}},
    74    "paths": {
    75      "/api/v1/a/{name}": {"get": {"parameters": [
    76  		  {"description": "x","in":"query","name": "x","type":"boolean","uniqueItems":true},
    77  		  {"description": "y","in":"query","name": "y","type":"boolean","uniqueItems":true}
    78      ]}},
    79      "/api/v1/a/{name}/foo": {"get": {"parameters": [
    80  		  {"description": "z","in":"query","name": "z","type":"boolean","uniqueItems":true},
    81  		  {"description": "y","in":"query","name": "y","type":"boolean","uniqueItems":true}
    82      ]}},
    83      "/api/v1/b/{name}": {"get": {"parameters": [
    84  		  {"description": "z","in":"query","name": "z","type":"boolean","uniqueItems":true}
    85      ]}},
    86      "/api/v1/b/{name}/foo": {"get": {"parameters": [
    87  		  {"description": "x","in":"query","name": "x","type":"boolean","uniqueItems":true}
    88      ]}}
    89    }
    90  }`,
    91  			want: map[string]string{
    92  				`{"uniqueItems":true,"type":"boolean","description":"x","name":"x","in":"query"}`: "x-yaDSHpi7",
    93  				`{"uniqueItems":true,"type":"boolean","description":"y","name":"y","in":"query"}`: "y-g6h7lEsz",
    94  				`{"uniqueItems":true,"type":"boolean","description":"z","name":"z","in":"query"}`: "z--SXYWoM_",
    95  			},
    96  		},
    97  		{
    98  			name: "shared per path",
    99  			spec: `{
   100    "parameters": {"pre": {"in": "body", "name": "body", "required": true, "schema": {}}},
   101    "paths": {
   102      "/api/v1/a/{name}": {"get": {},
   103        "parameters": [
   104  		  {"description": "x","in":"query","name": "x","type":"boolean","uniqueItems":true},
   105  		  {"description": "y","in":"query","name": "y","type":"boolean","uniqueItems":true}
   106        ]
   107      },
   108      "/api/v1/a/{name}/foo": {"get": {"parameters": [
   109  		  {"description": "z","in":"query","name": "z","type":"boolean","uniqueItems":true},
   110  		  {"description": "y","in":"query","name": "y","type":"boolean","uniqueItems":true}
   111      ]}},
   112      "/api/v1/b/{name}": {"get": {},
   113        "parameters": [
   114  		  {"description": "z","in":"query","name": "z","type":"boolean","uniqueItems":true}
   115        ]
   116      },
   117      "/api/v1/b/{name}/foo": {"get": {"parameters": [
   118  		  {"description": "x","in":"query","name": "x","type":"boolean","uniqueItems":true}
   119      ]}}
   120    }
   121  }`,
   122  			want: map[string]string{
   123  				`{"uniqueItems":true,"type":"boolean","description":"x","name":"x","in":"query"}`: "x-yaDSHpi7",
   124  				`{"uniqueItems":true,"type":"boolean","description":"y","name":"y","in":"query"}`: "y-g6h7lEsz",
   125  				`{"uniqueItems":true,"type":"boolean","description":"z","name":"z","in":"query"}`: "z--SXYWoM_",
   126  			},
   127  		},
   128  	}
   129  
   130  	for _, tt := range tests {
   131  		t.Run(tt.name, func(t *testing.T) {
   132  			var sp *spec.Swagger
   133  			if tt.spec != "" {
   134  				err := json.Unmarshal([]byte(tt.spec), &sp)
   135  				require.NoError(t, err)
   136  			}
   137  
   138  			gotNamesByJSON, _, err := collectSharedParameters(sp)
   139  			require.NoError(t, err)
   140  			require.Equalf(t, tt.want, gotNamesByJSON, "unexpected shared parameters")
   141  		})
   142  	}
   143  }
   144  
   145  func TestReplaceSharedParameters(t *testing.T) {
   146  	shared := map[string]string{
   147  		`{"uniqueItems":true,"type":"boolean","description":"x","name":"x","in":"query"}`: "x",
   148  		`{"uniqueItems":true,"type":"boolean","description":"y","name":"y","in":"query"}`: "y",
   149  		`{"uniqueItems":true,"type":"boolean","description":"z","name":"z","in":"query"}`: "z",
   150  	}
   151  
   152  	tests := []struct {
   153  		name string
   154  		spec string
   155  		want string
   156  	}{
   157  		{
   158  			name: "empty",
   159  			spec: "{}",
   160  			want: `{"paths":null}`,
   161  		},
   162  		{
   163  			name: "existing parameters",
   164  			spec: `{"parameters": {"a":{"type":"boolean"}}}`,
   165  			want: `{"parameters": {"a":{"type":"boolean"}},"paths":null}`,
   166  		},
   167  		{
   168  			name: "replace",
   169  			spec: `{
   170    "parameters": {"pre": {"in": "body", "name": "body", "required": true, "schema": {}}},
   171    "paths": {
   172      "/api/v1/a/{name}": {"get": {"description":"foo"},
   173        "parameters": [
   174          {"description": "x","in":"query","name": "x","type":"boolean","uniqueItems":true},
   175  	    {"description": "y","in":"query","name": "y","type":"boolean","uniqueItems":true}
   176        ]
   177      },
   178      "/api/v1/a/{name}/foo": {"get": {"parameters": [
   179        {"description": "z","in":"query","name": "z","type":"boolean","uniqueItems":true},
   180  	  {"description": "y","in":"query","name": "y","type":"boolean","uniqueItems":true}
   181      ]}},
   182      "/api/v1/b/{name}": {"get": {"parameters": [
   183  	  {"description": "z","in":"query","name": "z","type":"boolean","uniqueItems":true}
   184      ]}},
   185      "/api/v1/b/{name}/foo": {"get": {"parameters": [
   186  	  {"description": "x","in":"query","name": "x","type":"boolean","uniqueItems":true},
   187        {"description": "w","in":"query","name": "w","type":"boolean","uniqueItems":true}
   188      ]}}
   189    }
   190  }`,
   191  			want: `{
   192    "parameters": {"pre":{"in":"body","name":"body","required":true,"schema":{}}},
   193    "paths": {
   194      "/api/v1/a/{name}": {"get": {"description":"foo"},
   195        "parameters": [
   196          {"$ref": "#/parameters/x"},
   197          {"$ref": "#/parameters/y"}
   198        ]
   199      },
   200      "/api/v1/a/{name}/foo": {"get": {"parameters": [
   201        {"$ref": "#/parameters/z"},
   202        {"$ref": "#/parameters/y"}
   203      ]}},
   204      "/api/v1/b/{name}": {"get": {"parameters": [
   205        {"$ref": "#/parameters/z"}
   206      ]}},
   207      "/api/v1/b/{name}/foo": {"get": {"parameters": [
   208        {"$ref":"#/parameters/x"},
   209        {"description": "w","in":"query","name": "w","type":"boolean","uniqueItems":true}
   210      ]}}
   211    }
   212  }`,
   213  		},
   214  	}
   215  	for _, tt := range tests {
   216  		t.Run(tt.name, func(t *testing.T) {
   217  			var unmarshalled *spec.Swagger
   218  			err := json.Unmarshal([]byte(tt.spec), &unmarshalled)
   219  			require.NoError(t, err)
   220  
   221  			got, err := replaceSharedParameters(shared, unmarshalled)
   222  			require.NoError(t, err)
   223  
   224  			require.Equalf(t, normalizeJSON(t, tt.want), normalizeJSON(t, toJSON(t, got)), "unexpected result")
   225  		})
   226  	}
   227  }
   228  
   229  func toJSON(t *testing.T, x interface{}) string {
   230  	bs, err := json.Marshal(x)
   231  	require.NoError(t, err)
   232  
   233  	return string(bs)
   234  }
   235  
   236  func normalizeJSON(t *testing.T, j string) string {
   237  	var obj interface{}
   238  	err := json.Unmarshal([]byte(j), &obj)
   239  	require.NoError(t, err)
   240  	return toJSON(t, obj)
   241  }
   242  
   243  func TestOperations(t *testing.T) {
   244  	t.Log("Ensuring that operations() returns all operations in spec.PathItemProps")
   245  	path := spec.PathItem{}
   246  	v := reflect.ValueOf(path.PathItemProps)
   247  	var rOps []any
   248  	for i := 0; i < v.NumField(); i++ {
   249  		if v.Field(i).Kind() == reflect.Ptr {
   250  			rOps = append(rOps, v.Field(i).Interface())
   251  		}
   252  	}
   253  
   254  	ops := operations(&path)
   255  	require.Equal(t, len(rOps), len(ops), "operations() should return all operations in spec.PathItemProps")
   256  }