cuelang.org/go@v0.10.1/encoding/openapi/openapi_test.go (about) 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 }