1// Copyright 2017 Santhosh Kumar Tekuri. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package jsonschema_test
6
7import (
8	"bytes"
9	"crypto/tls"
10	"encoding/json"
11	"io/ioutil"
12	"net/http"
13	"net/http/httptest"
14	"net/url"
15	"os"
16	"path/filepath"
17	"runtime"
18	"strings"
19	"testing"
20
21	"github.com/santhosh-tekuri/jsonschema"
22	_ "github.com/santhosh-tekuri/jsonschema/httploader"
23)
24
25var draft4, draft6, draft7 []byte
26
27func init() {
28	var err error
29	draft4, err = ioutil.ReadFile("testdata/draft4.json")
30	if err != nil {
31		panic(err)
32	}
33	draft6, err = ioutil.ReadFile("testdata/draft6.json")
34	if err != nil {
35		panic(err)
36	}
37	draft7, err = ioutil.ReadFile("testdata/draft7.json")
38	if err != nil {
39		panic(err)
40	}
41}
42func TestDraft4(t *testing.T) {
43	testFolder(t, "testdata/draft4", jsonschema.Draft4)
44}
45
46func TestDraft6(t *testing.T) {
47	testFolder(t, "testdata/draft6", jsonschema.Draft6)
48}
49
50func TestDraft7(t *testing.T) {
51	testFolder(t, "testdata/draft7", jsonschema.Draft7)
52}
53
54type testGroup struct {
55	Description string
56	Schema      json.RawMessage
57	Tests       []struct {
58		Description string
59		Data        json.RawMessage
60		Valid       bool
61		Skip        *string
62	}
63}
64
65func testFolder(t *testing.T, folder string, draft *jsonschema.Draft) {
66	server := &http.Server{Addr: "localhost:1234", Handler: http.FileServer(http.Dir("testdata/remotes"))}
67	go func() {
68		if err := server.ListenAndServe(); err != http.ErrServerClosed {
69			t.Fatal(err)
70		}
71	}()
72	defer server.Close()
73
74	err := filepath.Walk(folder, func(path string, info os.FileInfo, err error) error {
75		if err != nil {
76			t.Error(err)
77			return nil
78		}
79		if info.IsDir() {
80			return nil
81		}
82		if filepath.Ext(info.Name()) != ".json" {
83			return nil
84		}
85
86		t.Log(info.Name())
87		data, err := ioutil.ReadFile(path)
88		if err != nil {
89			t.Errorf("  FAIL: %v\n", err)
90			return nil
91		}
92		var tg []testGroup
93		if err = json.Unmarshal(data, &tg); err != nil {
94			t.Errorf("  FAIL: %v\n", err)
95			return nil
96		}
97		for _, group := range tg {
98			t.Logf("  %s\n", group.Description)
99			c := jsonschema.NewCompiler()
100			if err := c.AddResource("http://json-schema.org/draft-04/schema", bytes.NewReader(draft4)); err != nil {
101				t.Errorf("    FAIL: add resource failed, reason: %v\n", err)
102				continue
103			}
104			if err := c.AddResource("http://json-schema.org/draft-06/schema", bytes.NewReader(draft6)); err != nil {
105				t.Errorf("    FAIL: add resource failed, reason: %v\n", err)
106				continue
107			}
108			if err := c.AddResource("http://json-schema.org/draft-07/schema", bytes.NewReader(draft7)); err != nil {
109				t.Errorf("    FAIL: add resource failed, reason: %v\n", err)
110				continue
111			}
112			c.Draft = draft
113			if err := c.AddResource("test.json", bytes.NewReader(group.Schema)); err != nil {
114				t.Errorf("    FAIL: add resource failed, reason: %v\n", err)
115				continue
116			}
117			schema, err := c.Compile("test.json")
118			if err != nil {
119				t.Errorf("    FAIL: schema compilation failed, reason: %v\n", err)
120				continue
121			}
122			for _, test := range group.Tests {
123				t.Logf("      %s\n", test.Description)
124				if test.Skip != nil {
125					t.Logf("        skipping: %s\n", *test.Skip)
126					continue
127				}
128				err = schema.Validate(bytes.NewReader(test.Data))
129				valid := err == nil
130				if !valid {
131					for _, line := range strings.Split(err.Error(), "\n") {
132						t.Logf("        %s\n", line)
133					}
134				}
135				if test.Valid != valid {
136					t.Errorf("        FAIL: expected valid=%t got valid=%t\n", test.Valid, valid)
137				}
138			}
139		}
140		return nil
141	})
142	if err != nil {
143		t.Fatal(err)
144	}
145
146	invalidDocTests := []struct {
147		description string
148		doc         string
149	}{
150		{"non json instance", "{"},
151		{"multiple json instance", "{}{}"},
152	}
153	for _, test := range invalidDocTests {
154		t.Run(test.description, func(t *testing.T) {
155			c := jsonschema.NewCompiler()
156			if err := c.AddResource("test.json", strings.NewReader("{}")); err != nil {
157				t.Fatal(err)
158			}
159			s, err := c.Compile("test.json")
160			if err != nil {
161				t.Fatal(err)
162			}
163			if err := s.Validate(strings.NewReader(test.doc)); err != nil {
164				t.Log(err)
165			} else {
166				t.Error("error expected")
167			}
168		})
169	}
170}
171
172func TestInvalidSchema(t *testing.T) {
173	t.Run("MustCompile with panic", func(t *testing.T) {
174		defer func() {
175			if r := recover(); r == nil {
176				t.Error("panic expected")
177			}
178		}()
179		jsonschema.MustCompile("testdata/invalid_schema.json")
180	})
181
182	t.Run("MustCompile without panic", func(t *testing.T) {
183		defer func() {
184			if r := recover(); r != nil {
185				t.Error("panic not expected")
186			}
187		}()
188		jsonschema.MustCompile("testdata/customer_schema.json#/0")
189	})
190
191	t.Run("invalid json", func(t *testing.T) {
192		if err := jsonschema.NewCompiler().AddResource("test.json", strings.NewReader("{")); err == nil {
193			t.Error("error expected")
194		} else {
195			t.Log(err)
196		}
197	})
198
199	t.Run("multiple json", func(t *testing.T) {
200		if err := jsonschema.NewCompiler().AddResource("test.json", strings.NewReader("{}{}")); err == nil {
201			t.Error("error expected")
202		} else {
203			t.Log(err)
204		}
205	})
206
207	type test struct {
208		Description string
209		Schema      json.RawMessage
210		Fragment    string
211	}
212	data, err := ioutil.ReadFile("testdata/invalid_schemas.json")
213	if err != nil {
214		t.Fatal(err)
215	}
216	var tests []test
217	if err = json.Unmarshal(data, &tests); err != nil {
218		t.Fatal(err)
219	}
220	for _, test := range tests {
221		t.Run(test.Description, func(t *testing.T) {
222			c := jsonschema.NewCompiler()
223			url := "test.json"
224			if err := c.AddResource(url, bytes.NewReader(test.Schema)); err != nil {
225				t.Fatal(err)
226			}
227			if len(test.Fragment) > 0 {
228				url += test.Fragment
229			}
230			if _, err = c.Compile(url); err == nil {
231				t.Error("error expected")
232			} else {
233				t.Log(err)
234			}
235		})
236	}
237}
238
239func TestCompileURL(t *testing.T) {
240	tr := http.DefaultTransport.(*http.Transport)
241	if tr.TLSClientConfig == nil {
242		tr.TLSClientConfig = &tls.Config{}
243	}
244	tr.TLSClientConfig.InsecureSkipVerify = true
245
246	handler := http.FileServer(http.Dir("testdata"))
247	httpServer := httptest.NewServer(handler)
248	defer httpServer.Close()
249	httpsServer := httptest.NewTLSServer(handler)
250	defer httpsServer.Close()
251
252	validTests := []struct {
253		schema, doc string
254	}{
255		{"testdata/customer_schema.json#/0", "testdata/customer.json"},
256		{toFileURL("testdata/customer_schema.json") + "#/0", "testdata/customer.json"},
257		{httpServer.URL + "/customer_schema.json#/0", "testdata/customer.json"},
258		{httpsServer.URL + "/customer_schema.json#/0", "testdata/customer.json"},
259		{toFileURL("testdata/empty schema.json"), "testdata/empty schema.json"},
260	}
261	for i, test := range validTests {
262		t.Logf("valid #%d: %+v", i, test)
263		s, err := jsonschema.Compile(test.schema)
264		if err != nil {
265			t.Errorf("valid #%d: %v", i, err)
266			return
267		}
268		f, err := os.Open(test.doc)
269		if err != nil {
270			t.Errorf("valid #%d: %v", i, err)
271			return
272		}
273		err = s.Validate(f)
274		_ = f.Close()
275		if err != nil {
276			t.Errorf("valid #%d: %v", i, err)
277		}
278	}
279
280	invalidTests := []string{
281		"testdata/syntax_error.json",
282		"testdata/missing.json",
283		toFileURL("testdata/missing.json"),
284		httpServer.URL + "/missing.json",
285		httpsServer.URL + "/missing.json",
286	}
287	for i, test := range invalidTests {
288		t.Logf("invalid #%d: %v", i, test)
289		if _, err := jsonschema.Compile(test); err == nil {
290			t.Errorf("invalid #%d: expected error", i)
291		} else {
292			t.Logf("invalid #%d: %v", i, err)
293		}
294	}
295}
296
297func TestValidateInterface(t *testing.T) {
298	files := []string{
299		"testdata/draft4/type.json",
300		"testdata/draft4/minimum.json",
301		"testdata/draft4/maximum.json",
302	}
303	for _, file := range files {
304		t.Log(filepath.Base(file))
305		data, err := ioutil.ReadFile(file)
306		if err != nil {
307			t.Errorf("  FAIL: %v\n", err)
308			return
309		}
310		var tg []testGroup
311		if err = json.Unmarshal(data, &tg); err != nil {
312			t.Errorf("  FAIL: %v\n", err)
313			return
314		}
315		for _, group := range tg {
316			t.Logf("  %s\n", group.Description)
317			c := jsonschema.NewCompiler()
318			if err := c.AddResource("test.json", bytes.NewReader(group.Schema)); err != nil {
319				t.Errorf("    FAIL: add resource failed, reason: %v\n", err)
320				continue
321			}
322			c.Draft = jsonschema.Draft4
323			schema, err := c.Compile("test.json")
324			if err != nil {
325				t.Errorf("    FAIL: schema compilation failed, reason: %v\n", err)
326				continue
327			}
328			for _, test := range group.Tests {
329				t.Logf("      %s\n", test.Description)
330
331				decoder := json.NewDecoder(bytes.NewReader(test.Data))
332				var doc interface{}
333				if err := decoder.Decode(&doc); err != nil {
334					t.Errorf("        FAIL: decode json failed, reason: %v\n", err)
335					continue
336				}
337
338				err = schema.ValidateInterface(doc)
339				valid := err == nil
340				if !valid {
341					for _, line := range strings.Split(err.Error(), "\n") {
342						t.Logf("        %s\n", line)
343					}
344				}
345				if test.Valid != valid {
346					t.Errorf("        FAIL: expected valid=%t got valid=%t\n", test.Valid, valid)
347				}
348			}
349		}
350	}
351}
352
353func TestInvalidJsonTypeError(t *testing.T) {
354	compiler := jsonschema.NewCompiler()
355	err := compiler.AddResource("test.json", strings.NewReader(`{ "type": "string"}`))
356	if err != nil {
357		t.Fatalf("addResource failed. reason: %v\n", err)
358	}
359	schema, err := compiler.Compile("test.json")
360	if err != nil {
361		t.Fatalf("schema compilation failed. reason: %v\n", err)
362	}
363	v := struct{ name string }{"hello world"}
364	err = schema.ValidateInterface(v)
365	switch err.(type) {
366	case jsonschema.InvalidJSONTypeError:
367		// passed
368	default:
369		t.Fatalf("got %v. want InvalidJSONTypeErr", err)
370	}
371}
372
373func TestExtractAnnotations(t *testing.T) {
374	t.Run("false", func(t *testing.T) {
375		compiler := jsonschema.NewCompiler()
376
377		err := compiler.AddResource("test.json", strings.NewReader(`{
378			"title": "this is title"
379		}`))
380		if err != nil {
381			t.Fatalf("addResource failed. reason: %v\n", err)
382		}
383
384		schema, err := compiler.Compile("test.json")
385		if err != nil {
386			t.Fatalf("schema compilation failed. reason: %v\n", err)
387		}
388
389		if schema.Title != "" {
390			t.Error("title should not be extracted")
391		}
392	})
393
394	t.Run("true", func(t *testing.T) {
395		compiler := jsonschema.NewCompiler()
396		compiler.ExtractAnnotations = true
397
398		err := compiler.AddResource("test.json", strings.NewReader(`{
399			"title": "this is title"
400		}`))
401		if err != nil {
402			t.Fatalf("addResource failed. reason: %v\n", err)
403		}
404
405		schema, err := compiler.Compile("test.json")
406		if err != nil {
407			t.Fatalf("schema compilation failed. reason: %v\n", err)
408		}
409
410		if schema.Title != "this is title" {
411			t.Errorf("title: got %q, want %q", schema.Title, "this is title")
412		}
413	})
414}
415
416func toFileURL(path string) string {
417	path, err := filepath.Abs(path)
418	if err != nil {
419		panic(err)
420	}
421	path = filepath.ToSlash(path)
422	if runtime.GOOS == "windows" {
423		path = "/" + path
424	}
425	u, err := url.Parse("file://" + path)
426	if err != nil {
427		panic(err)
428	}
429	return u.String()
430}
431
432// TestPanic tests https://github.com/santhosh-tekuri/jsonschema/issues/18
433func TestPanic(t *testing.T) {
434	schema_d := `
435	{
436		"type": "object",
437		"properties": {
438		"myid": { "type": "integer" },
439		"otype": { "$ref": "defs.json#someid" }
440		}
441	}
442	`
443	defs_d := `
444	{
445		"definitions": {
446		"stt": {
447			"$schema": "http://json-schema.org/draft-07/schema#",
448			"$id": "#someid",
449				"type": "object",
450			"enum": [ { "name": "stainless" }, { "name": "zinc" } ]
451		}
452		}
453	}
454	`
455	c := jsonschema.NewCompiler()
456	c.Draft = jsonschema.Draft7
457	c.AddResource("schema.json", strings.NewReader(schema_d))
458	c.AddResource("defs.json", strings.NewReader(defs_d))
459
460	if _, err := c.Compile("schema.json"); err != nil {
461		t.Error("no error expected")
462		return
463	}
464}
465