// Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package schema import ( "bytes" "context" "fmt" "io" "os" "path/filepath" "strings" "testing" "github.com/pkg/errors" "github.com/ghodss/yaml" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" "github.com/ory/jsonschema/v3" _ "github.com/ory/jsonschema/v3/fileloader" ) type schema struct { name string raw string s *jsonschema.Schema } type schemas []schema type result int const ( success result = iota failure ) func (r result) String() string { return []string{"success", "failure"}[r] } func (s schema) validate(t *testing.T, path string) error { t.Helper() if s.s == nil { compiler := jsonschema.NewCompiler() if err := compiler.AddResource(s.name, strings.NewReader(s.raw)); err != nil { return errors.WithStack(err) } sx, err := compiler.Compile(context.Background(), s.name) if err != nil { return errors.WithStack(err) } s.s = sx } var doc io.Reader y, err := os.ReadFile(path) // #nosec G304 test code if err != nil { return errors.WithStack(err) } j, err := yaml.YAMLToJSON(y) if err != nil { return errors.WithStack(err) } doc = bytes.NewBuffer(j) if err := s.s.Validate(doc); err != nil { return errors.Errorf("there were validation errors: %s", err) } return nil } func (ss *schemas) getByName(n string) (*schema, error) { for _, s := range *ss { if s.name == n { return &s, nil } } return nil, errors.Errorf("could not find schema with name %s", n) } func TestSchemas(t *testing.T) { t.Run("test embedx/config.schema.json", SchemaTestRunner("../../embedx", "config")) } func SchemaTestRunner(spath string, sname string) func(*testing.T) { return func(t *testing.T) { sb, err := os.ReadFile(fmt.Sprintf("%s/%s.schema.json", spath, sname)) require.NoError(t, err) // To test refs independently and reduce test case size we replace every "$ref" with "const". // That way refs will not be resolved but we still make sure that they are pointing to the right definition. // Changing a definition will result in just changing test cases for that definition. s := strings.ReplaceAll(string(sb), `"$ref":`, `"const":`) schemas := schemas{{ name: "root", raw: s, }} def := gjson.Get(s, "definitions") if def.Exists() { require.True(t, def.IsObject()) def.ForEach(func(key, value gjson.Result) bool { require.Equal(t, gjson.String, key.Type) schemas = append(schemas, schema{ name: key.String(), raw: value.Raw, }) return true }) } RunCases(t, schemas, fmt.Sprintf("./fixtures/%s.schema.test.success", sname), success) RunCases(t, schemas, fmt.Sprintf("./fixtures/%s.schema.test.failure", sname), failure) } } func RunCases(t *testing.T, ss schemas, dir string, expected result) { err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { require.NoError(t, err) if info.IsDir() { return nil } parts := strings.Split(info.Name(), ".") require.Equal(t, 3, len(parts)) sName, tc := parts[0], parts[1] s, err := ss.getByName(sName) require.NoError(t, err) t.Run(fmt.Sprintf("case=schema %s test case %s expects %s", sName, tc, expected), func(t *testing.T) { err := s.validate(t, path) if expected == success { assert.NoError(t, err, "path: %s", path) } else { assert.Error(t, err, "path: %s", path) } }) return nil }) require.NoError(t, err) }