...

Source file src/cuelang.org/go/encoding/openapi/openapi_test.go

Documentation: cuelang.org/go/encoding/openapi

     1  // Copyright 2019 CUE Authors
     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  //     http://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 openapi_test
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/json"
    20  	"os"
    21  	"path/filepath"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/google/go-cmp/cmp"
    26  
    27  	"cuelang.org/go/cue"
    28  	"cuelang.org/go/cue/build"
    29  	"cuelang.org/go/cue/cuecontext"
    30  	"cuelang.org/go/cue/errors"
    31  	"cuelang.org/go/cue/load"
    32  	"cuelang.org/go/encoding/openapi"
    33  	"cuelang.org/go/internal/cuetest"
    34  )
    35  
    36  func TestParseDefinitions(t *testing.T) {
    37  	info := struct {
    38  		Title   string `json:"title"`
    39  		Version string `json:"version"`
    40  	}{"test", "v1"}
    41  	defaultConfig := &openapi.Config{}
    42  	resolveRefs := &openapi.Config{Info: info, ExpandReferences: true}
    43  
    44  	testCases := []struct {
    45  		in, out      string
    46  		variant      string
    47  		instanceOnly bool
    48  		valueOnly    bool
    49  		config       *openapi.Config
    50  		err          string
    51  	}{{
    52  		in:     "structural.cue",
    53  		out:    "structural.json",
    54  		config: resolveRefs,
    55  	}, {
    56  		in:           "structural.cue",
    57  		variant:      "+ReferenceFunc",
    58  		out:          "structural.json",
    59  		instanceOnly: true,
    60  		config: &openapi.Config{
    61  			Info:             info,
    62  			ExpandReferences: true,
    63  			ReferenceFunc: func(v *cue.Instance, path []string) string {
    64  				return strings.Join(path, "_")
    65  			},
    66  		},
    67  	}, {
    68  		in:           "protobuf.cue",
    69  		out:          "protobuf.json",
    70  		instanceOnly: true,
    71  		config: &openapi.Config{
    72  			Info:             info,
    73  			ExpandReferences: true,
    74  			ReferenceFunc: func(p *cue.Instance, path []string) string {
    75  				return strings.Join(path, ".")
    76  			},
    77  		},
    78  	}, {
    79  		in:     "nested.cue",
    80  		out:    "nested.json",
    81  		config: defaultConfig,
    82  	}, {
    83  		in:     "simple.cue",
    84  		out:    "simple.json",
    85  		config: resolveRefs,
    86  	}, {
    87  		in:     "simple.cue",
    88  		out:    "simple-filter.json",
    89  		config: &openapi.Config{Info: info, FieldFilter: "min.*|max.*"},
    90  	}, {
    91  		in:     "array.cue",
    92  		out:    "array.json",
    93  		config: defaultConfig,
    94  	}, {
    95  		in:     "enum.cue",
    96  		out:    "enum.json",
    97  		config: defaultConfig,
    98  	}, {
    99  		in:     "struct.cue",
   100  		out:    "struct.json",
   101  		config: defaultConfig,
   102  	}, {
   103  		in:     "strings.cue",
   104  		out:    "strings.json",
   105  		config: defaultConfig,
   106  	}, {
   107  		in:     "nums.cue",
   108  		out:    "nums.json",
   109  		config: defaultConfig,
   110  	}, {
   111  		in:     "nums.cue",
   112  		out:    "nums-v3.1.0.json",
   113  		config: &openapi.Config{Info: info, Version: "3.1.0"},
   114  	}, {
   115  		in:     "builtins.cue",
   116  		out:    "builtins.json",
   117  		config: defaultConfig,
   118  	}, {
   119  		in:     "oneof.cue",
   120  		out:    "oneof.json",
   121  		config: defaultConfig,
   122  	}, {
   123  		in:     "oneof.cue",
   124  		out:    "oneof-resolve.json",
   125  		config: resolveRefs,
   126  	}, {
   127  		in:     "openapi.cue",
   128  		out:    "openapi.json",
   129  		config: defaultConfig,
   130  	}, {
   131  		in:     "openapi.cue",
   132  		out:    "openapi-norefs.json",
   133  		config: resolveRefs,
   134  	}, {
   135  		in:     "embed.cue",
   136  		out:    "embed.json",
   137  		config: defaultConfig,
   138  	}, {
   139  		in:     "embed.cue",
   140  		out:    "embed-norefs.json",
   141  		config: resolveRefs,
   142  	}, {
   143  		in:  "oneof.cue",
   144  		out: "oneof-funcs.json",
   145  		config: &openapi.Config{
   146  			Info: info,
   147  			NameFunc: func(v cue.Value, path cue.Path) string {
   148  				var buf strings.Builder
   149  				for i, sel := range path.Selectors() {
   150  					if i > 0 {
   151  						buf.WriteByte('_')
   152  					}
   153  					s := sel.String()
   154  					s = strings.TrimPrefix(s, "#")
   155  					buf.WriteString(strings.ToUpper(s))
   156  				}
   157  				return buf.String()
   158  			},
   159  			DescriptionFunc: func(v cue.Value) string {
   160  				return "Randomly picked description from a set of size one."
   161  			},
   162  		},
   163  	}, {
   164  		in:           "oneof.cue",
   165  		out:          "oneof-funcs.json",
   166  		variant:      "+ReferenceFunc",
   167  		instanceOnly: true,
   168  		config: &openapi.Config{
   169  			Info: info,
   170  			ReferenceFunc: func(v *cue.Instance, path []string) string {
   171  				return strings.ToUpper(strings.Join(path, "_"))
   172  			},
   173  			DescriptionFunc: func(v cue.Value) string {
   174  				return "Randomly picked description from a set of size one."
   175  			},
   176  		},
   177  	}, {
   178  		in:  "refs.cue",
   179  		out: "refs.json",
   180  		config: &openapi.Config{
   181  			Info: info,
   182  			NameFunc: func(v cue.Value, path cue.Path) string {
   183  				switch {
   184  				case strings.HasPrefix(path.Selectors()[0].String(), "#Excluded"):
   185  					return ""
   186  				}
   187  				return strings.TrimPrefix(path.String(), "#")
   188  			},
   189  		},
   190  	}, {
   191  		in:           "refs.cue",
   192  		out:          "refs.json",
   193  		variant:      "+ReferenceFunc",
   194  		instanceOnly: true,
   195  		config: &openapi.Config{
   196  			Info: info,
   197  			ReferenceFunc: func(v *cue.Instance, path []string) string {
   198  				switch {
   199  				case strings.HasPrefix(path[0], "Excluded"):
   200  					return ""
   201  				}
   202  				return strings.Join(path, ".")
   203  			},
   204  		},
   205  	}, {
   206  		in:     "issue131.cue",
   207  		out:    "issue131.json",
   208  		config: &openapi.Config{Info: info, SelfContained: true},
   209  	}, {
   210  		// Issue #915
   211  		in:     "cycle.cue",
   212  		out:    "cycle.json",
   213  		config: &openapi.Config{Info: info},
   214  	}, {
   215  		// Issue #915
   216  		in:     "cycle.cue",
   217  		config: &openapi.Config{Info: info, ExpandReferences: true},
   218  		err:    "cycle",
   219  	}, {
   220  		in:     "quotedfield.cue",
   221  		out:    "quotedfield.json",
   222  		config: defaultConfig,
   223  	}, {
   224  		in:     "omitvalue.cue",
   225  		out:    "omitvalue.json",
   226  		config: defaultConfig,
   227  	}}
   228  	for _, tc := range testCases {
   229  		t.Run(tc.out+tc.variant, func(t *testing.T) {
   230  			run := func(t *testing.T, inst cue.InstanceOrValue) {
   231  				b, err := openapi.Gen(inst, tc.config)
   232  				if err != nil {
   233  					if tc.err == "" {
   234  						t.Fatal("unexpected error:", errors.Details(err, nil))
   235  					}
   236  					return
   237  				}
   238  
   239  				if tc.err != "" {
   240  					t.Fatal("unexpected success:", tc.err)
   241  				} else {
   242  					all, err := tc.config.All(inst)
   243  					if err != nil {
   244  						t.Fatal(err)
   245  					}
   246  					walk(all)
   247  				}
   248  
   249  				var out = &bytes.Buffer{}
   250  				_ = json.Indent(out, b, "", "   ")
   251  
   252  				wantFile := filepath.Join("testdata", tc.out)
   253  				if cuetest.UpdateGoldenFiles {
   254  					_ = os.WriteFile(wantFile, out.Bytes(), 0644)
   255  					return
   256  				}
   257  
   258  				b, err = os.ReadFile(wantFile)
   259  				if err != nil {
   260  					t.Fatal(err)
   261  				}
   262  
   263  				if d := cmp.Diff(string(b), out.String()); d != "" {
   264  					t.Errorf("files differ:\n%v", d)
   265  				}
   266  			}
   267  			filename := filepath.FromSlash(tc.in)
   268  			inst := load.Instances([]string{filename}, &load.Config{
   269  				Dir: "./testdata",
   270  			})[0]
   271  			if !tc.valueOnly {
   272  				t.Run("Instance", func(t *testing.T) {
   273  					// Old style call, with *cue.Instance.
   274  					inst := cue.Build([]*build.Instance{inst})[0]
   275  					if inst.Err != nil {
   276  						t.Fatal(errors.Details(inst.Err, nil))
   277  					}
   278  					run(t, inst)
   279  				})
   280  			}
   281  			if !tc.instanceOnly {
   282  				t.Run("Value", func(t *testing.T) {
   283  					// New style call, with cue.Value
   284  					ctx := cuecontext.New()
   285  					v := ctx.BuildInstance(inst)
   286  					if err := v.Err(); err != nil {
   287  						t.Fatal(errors.Details(err, nil))
   288  					}
   289  					run(t, v)
   290  				})
   291  			}
   292  		})
   293  	}
   294  }
   295  
   296  // walk traverses an openapi.OrderedMap. This is a helper function
   297  // used to ensure that a generated OpenAPI value is well-formed.
   298  func walk(om *openapi.OrderedMap) {
   299  	for _, p := range om.Pairs() {
   300  		switch p := p.Value.(type) {
   301  		case *openapi.OrderedMap:
   302  			walk(p)
   303  		case []*openapi.OrderedMap:
   304  			for _, om := range p {
   305  				walk(om)
   306  			}
   307  		}
   308  	}
   309  }
   310  
   311  // TODO: move OpenAPI testing to txtar and allow errors.
   312  func TestIssue1234(t *testing.T) {
   313  	var r cue.Runtime
   314  	inst, err := r.Compile("test", `
   315  #Test: or([])
   316  
   317  	`)
   318  	if err != nil {
   319  		t.Fatal(err)
   320  	}
   321  
   322  	_, err = openapi.Gen(inst, &openapi.Config{})
   323  	if err == nil {
   324  		t.Fatal("expected error")
   325  	}
   326  }
   327  
   328  // This is for debugging purposes. Do not remove.
   329  func TestX(t *testing.T) {
   330  	t.Skip()
   331  
   332  	var r cue.Runtime
   333  	inst, err := r.Compile("test", `
   334  	`)
   335  	if err != nil {
   336  		t.Fatal(err)
   337  	}
   338  
   339  	b, err := openapi.Gen(inst, &openapi.Config{
   340  		// ExpandReferences: true,
   341  	})
   342  	if err != nil {
   343  		t.Fatal(errors.Details(err, nil))
   344  	}
   345  
   346  	var out = &bytes.Buffer{}
   347  	_ = json.Indent(out, b, "", "   ")
   348  	t.Error(out.String())
   349  }
   350  

View as plain text