cuelang.org/go@v0.13.0/internal/filetypes/filetypes_test.go (about)

     1  // Copyright 2020 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 filetypes
    16  
    17  import (
    18  	"strings"
    19  	"testing"
    20  
    21  	"github.com/go-quicktest/qt"
    22  	"github.com/google/go-cmp/cmp"
    23  	"github.com/google/go-cmp/cmp/cmpopts"
    24  
    25  	"cuelang.org/go/cue/build"
    26  	"cuelang.org/go/cue/errors"
    27  )
    28  
    29  func check(t *testing.T, want, x interface{}, err error) {
    30  	t.Helper()
    31  	if err != nil {
    32  		x = errors.String(err.(errors.Error))
    33  	}
    34  	if diff := cmp.Diff(want, x, cmpopts.EquateEmpty()); diff != "" {
    35  		t.Errorf("unexpected result; -want +got\n%s", diff)
    36  	}
    37  }
    38  
    39  func TestFromFile(t *testing.T) {
    40  	testCases := []struct {
    41  		name string
    42  		in   build.File
    43  		mode Mode
    44  		out  interface{}
    45  	}{{
    46  		name: "must specify encoding",
    47  		in:   build.File{},
    48  		out:  `no encoding specified`,
    49  	}, {
    50  		// Default without any
    51  		name: "cue",
    52  		in: build.File{
    53  			Filename: "",
    54  			Encoding: build.CUE,
    55  		},
    56  		mode: Def,
    57  		out: &FileInfo{
    58  			Encoding:     build.CUE,
    59  			Form:         build.Schema,
    60  			Definitions:  true,
    61  			Data:         true,
    62  			Optional:     true,
    63  			Constraints:  true,
    64  			References:   true,
    65  			Cycles:       true,
    66  			KeepDefaults: true,
    67  			Incomplete:   true,
    68  			Imports:      true,
    69  			Docs:         true,
    70  			Attributes:   true,
    71  		},
    72  	}, {
    73  		// CUE encoding in data form.
    74  		name: "data: cue",
    75  		in: build.File{
    76  			Filename: "",
    77  			Form:     build.Data,
    78  			Encoding: build.CUE,
    79  		},
    80  		mode: Input,
    81  		out: &FileInfo{
    82  			Filename:   "",
    83  			Encoding:   build.CUE,
    84  			Form:       build.Data,
    85  			Data:       true,
    86  			Docs:       true,
    87  			Attributes: true,
    88  		},
    89  	}, {
    90  		// Filename starting with a . but no other extension.
    91  		name: "filename-with-dot",
    92  		in: build.File{
    93  			Filename: ".json",
    94  		},
    95  		mode: Def,
    96  		out:  `no encoding specified`,
    97  	}, {
    98  		name: "yaml",
    99  		mode: Def,
   100  		in: build.File{
   101  			Filename: "foo.yaml",
   102  			Encoding: build.YAML,
   103  		},
   104  		out: &FileInfo{
   105  			Filename:   "foo.yaml",
   106  			Encoding:   build.YAML,
   107  			Form:       build.Graph,
   108  			Data:       true,
   109  			References: true,
   110  			Cycles:     true,
   111  			Stream:     true,
   112  			Docs:       true,
   113  			Attributes: true,
   114  		},
   115  	}, {
   116  		name: "yaml+openapi",
   117  		in: build.File{
   118  			Filename:       "foo.yaml",
   119  			Encoding:       build.YAML,
   120  			Interpretation: build.OpenAPI,
   121  		},
   122  		out: &FileInfo{
   123  			Filename:       "foo.yaml",
   124  			Encoding:       build.YAML,
   125  			Interpretation: build.OpenAPI,
   126  			Form:           build.Schema,
   127  			Definitions:    true,
   128  			Data:           true,
   129  			Optional:       true,
   130  			Constraints:    true,
   131  			References:     true,
   132  			Cycles:         true,
   133  			KeepDefaults:   true,
   134  			Incomplete:     true,
   135  			Imports:        true,
   136  			Docs:           true,
   137  			Attributes:     true,
   138  		},
   139  	}, {
   140  		name: "JSONDefault",
   141  		mode: Input,
   142  		in: build.File{
   143  			Filename: "data.json",
   144  			Encoding: build.JSON,
   145  		},
   146  		out: &FileInfo{
   147  			Filename:     "data.json",
   148  			Encoding:     build.JSON,
   149  			Form:         build.Data,
   150  			Definitions:  false,
   151  			Data:         true,
   152  			Optional:     false,
   153  			Constraints:  false,
   154  			References:   false,
   155  			Cycles:       false,
   156  			KeepDefaults: false,
   157  			Incomplete:   false,
   158  			Imports:      false,
   159  			Docs:         false,
   160  			Attributes:   false,
   161  		},
   162  	}, {
   163  		name: "JSONSchema",
   164  		in: build.File{
   165  			Filename:       "foo.json",
   166  			Interpretation: "jsonschema",
   167  			Encoding:       build.JSON,
   168  		},
   169  		out: &FileInfo{
   170  			Filename:       "foo.json",
   171  			Encoding:       build.JSON,
   172  			Interpretation: "jsonschema",
   173  			Form:           build.Schema,
   174  			Definitions:    true,
   175  			Data:           true,
   176  			Optional:       true,
   177  			Constraints:    true,
   178  			References:     true,
   179  			Cycles:         true,
   180  			KeepDefaults:   true,
   181  			Incomplete:     true,
   182  			Imports:        true,
   183  			Docs:           true,
   184  			Attributes:     true,
   185  		},
   186  	}, {
   187  		name: "JSONOpenAPI",
   188  		in: build.File{
   189  			Filename:       "foo.json",
   190  			Encoding:       build.JSON,
   191  			Interpretation: build.OpenAPI,
   192  		},
   193  		mode: Def,
   194  		out: &FileInfo{
   195  			Filename:       "foo.json",
   196  			Encoding:       build.JSON,
   197  			Interpretation: build.OpenAPI,
   198  			Form:           build.Schema,
   199  			Definitions:    true,
   200  			Data:           true,
   201  			Optional:       true,
   202  			Constraints:    true,
   203  			References:     true,
   204  			Cycles:         true,
   205  			KeepDefaults:   true,
   206  			Incomplete:     true,
   207  			Imports:        true,
   208  			Docs:           true,
   209  			Attributes:     true,
   210  		},
   211  	}, {
   212  		name: "KoalaXML",
   213  		in: build.File{
   214  			Filename: "foo.xml",
   215  			Encoding: "xml",
   216  			BoolTags: map[string]bool{
   217  				"koala": true,
   218  			},
   219  		},
   220  		mode: Def,
   221  		out: &FileInfo{
   222  			Filename:     "foo.xml",
   223  			Encoding:     "xml",
   224  			Form:         "data",
   225  			Definitions:  false,
   226  			Data:         true,
   227  			Optional:     false,
   228  			Constraints:  false,
   229  			References:   false,
   230  			Cycles:       false,
   231  			KeepDefaults: false,
   232  			Incomplete:   false,
   233  			Imports:      false,
   234  			Docs:         true,
   235  			Attributes:   true,
   236  		},
   237  	}, {
   238  		name: "OpenAPIDefaults",
   239  		in: build.File{
   240  			Filename:       "-",
   241  			Encoding:       build.JSON,
   242  			Interpretation: build.OpenAPI,
   243  		},
   244  		mode: Def,
   245  		out: &FileInfo{
   246  			Filename:       "-",
   247  			Encoding:       build.JSON,
   248  			Interpretation: build.OpenAPI,
   249  			Form:           build.Schema,
   250  			Definitions:    true,
   251  			Data:           true,
   252  			Optional:       true,
   253  			Constraints:    true,
   254  			References:     true,
   255  			Cycles:         true,
   256  			KeepDefaults:   true,
   257  			Incomplete:     true,
   258  			Imports:        true,
   259  			Docs:           true,
   260  			Attributes:     true,
   261  		},
   262  	}, {
   263  		name: "Go",
   264  		in: build.File{
   265  			Encoding: build.Code,
   266  			Filename: "foo.go",
   267  		},
   268  		mode: Def,
   269  		out: &FileInfo{
   270  			Filename:     "foo.go",
   271  			Encoding:     build.Code,
   272  			Form:         build.Schema,
   273  			Definitions:  true,
   274  			Data:         true,
   275  			Optional:     true,
   276  			Constraints:  true,
   277  			References:   true,
   278  			Cycles:       true,
   279  			KeepDefaults: true,
   280  			Incomplete:   true,
   281  			Imports:      true,
   282  			Docs:         true,
   283  			Attributes:   true,
   284  		},
   285  	}}
   286  	for _, tc := range testCases {
   287  		t.Run(tc.name, func(t *testing.T) {
   288  			info, err := FromFile(&tc.in, tc.mode)
   289  			check(t, tc.out, info, err)
   290  		})
   291  	}
   292  }
   293  
   294  func TestParseFile(t *testing.T) {
   295  	// TODO(errors): wrong path?
   296  	testCases := []struct {
   297  		in   string
   298  		mode Mode
   299  		out  interface{}
   300  	}{{
   301  		in:   "file.json",
   302  		mode: Input,
   303  		out: &build.File{
   304  			Filename:       "file.json",
   305  			Encoding:       build.JSON,
   306  			Interpretation: build.Auto,
   307  		},
   308  	}, {
   309  		in:   ".json",
   310  		mode: Input,
   311  		out:  `no encoding specified for file ".json"`,
   312  	}, {
   313  		in:   ".json.yaml",
   314  		mode: Input,
   315  		out: &build.File{
   316  			Filename:       ".json.yaml",
   317  			Encoding:       build.YAML,
   318  			Interpretation: build.Auto,
   319  		},
   320  	}, {
   321  		in:   "file.json",
   322  		mode: Def,
   323  		out: &build.File{
   324  			Filename: "file.json",
   325  			Encoding: build.JSON,
   326  		},
   327  	}, {
   328  		in: "schema:file.json",
   329  		out: &build.File{
   330  			Filename:       "file.json",
   331  			Encoding:       build.JSON,
   332  			Interpretation: build.Auto,
   333  			Form:           build.Schema,
   334  		},
   335  	}, {
   336  		in: "openapi:-",
   337  		out: &build.File{
   338  			Filename:       "-",
   339  			Encoding:       build.JSON,
   340  			Interpretation: build.OpenAPI,
   341  			Form:           build.Schema,
   342  			BoolTags: map[string]bool{
   343  				"strict":         false,
   344  				"strictFeatures": false,
   345  				"strictKeywords": false,
   346  			},
   347  		},
   348  	}, {
   349  		in: "cue:file.json",
   350  		out: &build.File{
   351  			Filename: "file.json",
   352  			Encoding: build.CUE,
   353  		},
   354  	}, {
   355  		in: "cue+schema:-",
   356  		out: &build.File{
   357  			Filename: "-",
   358  			Encoding: build.CUE,
   359  			Form:     build.Schema,
   360  		},
   361  	}, {
   362  		in: "code+lang=js:foo.x",
   363  		out: &build.File{
   364  			Filename: "foo.x",
   365  			Encoding: build.Code,
   366  			Tags:     map[string]string{"lang": "js"},
   367  		},
   368  	}, {
   369  		in:  "json+lang=js:foo.x",
   370  		out: `tag lang is not allowed in this context`,
   371  	}, {
   372  		in:  "foo:file.bar",
   373  		out: `unknown filetype foo`,
   374  	}, {
   375  		in:  "file.bar",
   376  		out: `unknown file extension .bar`,
   377  	}, {
   378  		in:   "-",
   379  		mode: Input,
   380  		out: &build.File{
   381  			Filename: "-",
   382  			Encoding: build.CUE,
   383  		},
   384  	}, {
   385  		in:   "-",
   386  		mode: Export,
   387  		out: &build.File{
   388  			Filename: "-",
   389  			Encoding: build.JSON,
   390  		},
   391  	}}
   392  	for _, tc := range testCases {
   393  		t.Run(tc.in, func(t *testing.T) {
   394  			f, err := ParseFile(tc.in, tc.mode)
   395  			check(t, tc.out, f, err)
   396  		})
   397  	}
   398  }
   399  
   400  func TestParseArgs(t *testing.T) {
   401  	testCases := []struct {
   402  		in  string
   403  		out interface{}
   404  	}{{
   405  		in: "foo.json baz.yaml",
   406  		out: []*build.File{
   407  			{
   408  				Filename:       "foo.json",
   409  				Encoding:       build.JSON,
   410  				Interpretation: build.Auto,
   411  			},
   412  			{
   413  				Filename:       "baz.yaml",
   414  				Encoding:       build.YAML,
   415  				Interpretation: build.Auto,
   416  			},
   417  		},
   418  	}, {
   419  		in: "data: foo.cue",
   420  		out: []*build.File{
   421  			{Filename: "foo.cue", Encoding: build.CUE, Form: build.Data},
   422  		},
   423  	}, {
   424  		in: "json: foo.json bar.data jsonschema: bar.schema",
   425  		out: []*build.File{
   426  			{Filename: "foo.json", Encoding: build.JSON}, // no auto!
   427  			{Filename: "bar.data", Encoding: build.JSON},
   428  			{
   429  				Filename:       "bar.schema",
   430  				Encoding:       build.JSON,
   431  				Form:           build.Schema,
   432  				Interpretation: "jsonschema",
   433  				BoolTags: map[string]bool{
   434  					"strict":         false,
   435  					"strictFeatures": false,
   436  					"strictKeywords": false,
   437  				},
   438  			},
   439  		},
   440  	}, {
   441  		in: "koala: bar.xml",
   442  		out: []*build.File{
   443  			{
   444  				Filename:       "bar.xml",
   445  				Encoding:       "xml",
   446  				Interpretation: "auto",
   447  				BoolTags: map[string]bool{
   448  					"koala": true,
   449  				},
   450  			},
   451  		},
   452  	}, {
   453  		in: "jsonschema+strict: bar.schema",
   454  		out: []*build.File{
   455  			{
   456  				Filename:       "bar.schema",
   457  				Encoding:       "json",
   458  				Interpretation: "jsonschema",
   459  				Form:           build.Schema,
   460  				BoolTags: map[string]bool{
   461  					"strict":         true,
   462  					"strictFeatures": true,
   463  					"strictKeywords": true,
   464  				},
   465  			},
   466  		},
   467  	}, {
   468  		in: "jsonschema+strictFeatures=1: bar.schema",
   469  		out: []*build.File{
   470  			{
   471  				Filename:       "bar.schema",
   472  				Encoding:       "json",
   473  				Interpretation: "jsonschema",
   474  				Form:           build.Schema,
   475  				BoolTags: map[string]bool{
   476  					"strict":         false,
   477  					"strictFeatures": true,
   478  					"strictKeywords": false,
   479  				},
   480  			},
   481  		},
   482  	}, {
   483  		in: `json: c:\foo.json c:\path\to\file.dat`,
   484  		out: []*build.File{
   485  			{Filename: `c:\foo.json`, Encoding: build.JSON},
   486  			{Filename: `c:\path\to\file.dat`, Encoding: build.JSON},
   487  		},
   488  	}, {
   489  		in:  "json: json+schema: bar.schema",
   490  		out: `scoped qualifier "json:" without file`,
   491  	}, {
   492  		in:  "json:",
   493  		out: `scoped qualifier "json:" without file`,
   494  	}, {
   495  		in:  "json:foo:bar.yaml",
   496  		out: `unsupported file name "json:foo:bar.yaml": may not have ':'`,
   497  	}}
   498  	for _, tc := range testCases {
   499  		t.Run(tc.in, func(t *testing.T) {
   500  			files, err := ParseArgs(strings.Split(tc.in, " "))
   501  			check(t, tc.out, files, err)
   502  		})
   503  	}
   504  }
   505  
   506  func TestDefaultTagsForInterpretation(t *testing.T) {
   507  	tags := DefaultTagsForInterpretation(build.JSONSchema, Input)
   508  	qt.Assert(t, qt.DeepEquals(tags, map[string]bool{
   509  		"strict":         false,
   510  		"strictFeatures": false,
   511  		"strictKeywords": false,
   512  	}))
   513  }