1// Copyright 2016 Google LLC
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package disco
6
7import (
8	"io/ioutil"
9	"reflect"
10	"strings"
11	"testing"
12)
13
14var stringSchema = &Schema{
15	Type: "string",
16	Kind: SimpleKind,
17}
18
19func TestDocument(t *testing.T) {
20	bytes, err := ioutil.ReadFile("testdata/test-api.json")
21	if err != nil {
22		t.Fatal(err)
23	}
24	got, err := NewDocument(bytes)
25	if err != nil {
26		t.Fatal(err)
27	}
28	want := &Document{
29		ID:                "storage:v1",
30		Name:              "storage",
31		Version:           "v1",
32		Title:             "Cloud Storage JSON API",
33		RootURL:           "https://www.googleapis.com/",
34		ServicePath:       "storage/v1/",
35		BasePath:          "/storage/v1/",
36		DocumentationLink: "https://developers.google.com/storage/docs/json_api/",
37		Auth: Auth{
38			OAuth2Scopes: []Scope{
39				{"https://www.googleapis.com/auth/cloud-platform",
40					"View and manage your data across Google Cloud Platform services"},
41				{"https://www.googleapis.com/auth/cloud-platform.read-only",
42					"View your data across Google Cloud Platform services"},
43				{"https://www.googleapis.com/auth/devstorage.full_control",
44					"Manage your data and permissions in Google Cloud Storage"},
45				{"https://www.googleapis.com/auth/devstorage.read_only",
46					"View your data in Google Cloud Storage"},
47				{"https://www.googleapis.com/auth/devstorage.read_write",
48					"Manage your data in Google Cloud Storage"},
49			},
50		},
51		Features: []string{"dataWrapper"},
52		Schemas: map[string]*Schema{
53			"Bucket": {
54				Name:        "Bucket",
55				ID:          "Bucket",
56				Type:        "object",
57				Description: "A bucket.",
58				Kind:        StructKind,
59				Properties: []*Property{
60					{"cors", &Schema{
61						Type: "array",
62						Kind: ArrayKind,
63						ItemSchema: &Schema{
64							Type: "object",
65							Kind: StructKind,
66							Properties: []*Property{
67								{"maxAgeSeconds", &Schema{
68									Type:   "integer",
69									Format: "int32",
70									Kind:   SimpleKind,
71								}},
72								{"method", &Schema{
73									Type:       "array",
74									Kind:       ArrayKind,
75									ItemSchema: stringSchema,
76								}},
77							},
78						},
79					}},
80					{"id", stringSchema},
81					{"kind", &Schema{
82						Type:    "string",
83						Kind:    SimpleKind,
84						Default: "storage#bucket",
85					}},
86				},
87			},
88			"Buckets": {
89				ID:   "Buckets",
90				Name: "Buckets",
91				Type: "object",
92				Kind: StructKind,
93				Properties: []*Property{
94					{"items", &Schema{
95						Type: "array",
96						Kind: ArrayKind,
97						ItemSchema: &Schema{
98							Kind:      ReferenceKind,
99							Ref:       "Bucket",
100							RefSchema: nil,
101						},
102					}},
103				},
104			},
105			"VariantExample": {
106				ID:   "VariantExample",
107				Name: "VariantExample",
108				Type: "object",
109				Kind: StructKind,
110				Variant: &Variant{
111					Discriminant: "type",
112					Map: []*VariantMapItem{
113						{TypeValue: "Bucket", Ref: "Bucket"},
114						{TypeValue: "Buckets", Ref: "Buckets"},
115					},
116				},
117			},
118		},
119		Methods: MethodList{
120			&Method{
121				Name:       "getCertForOpenIdConnect",
122				ID:         "oauth2.getCertForOpenIdConnect",
123				Path:       "oauth2/v1/certs",
124				HTTPMethod: "GET",
125				Response:   &Schema{Ref: "Bucket", Kind: ReferenceKind},
126			},
127		},
128		Resources: ResourceList{
129			&Resource{
130				Name:     "buckets",
131				FullName: ".buckets",
132				Methods: MethodList{
133					&Method{
134						Name:        "get",
135						ID:          "storage.buckets.get",
136						Path:        "b/{bucket}",
137						HTTPMethod:  "GET",
138						Description: "d",
139						Parameters: ParameterList{
140							&Parameter{
141								Name: "bucket",
142								Schema: Schema{
143									Type: "string",
144								},
145								Required: true,
146								Location: "path",
147							},
148							&Parameter{
149								Name: "ifMetagenerationMatch",
150								Schema: Schema{
151									Type:   "string",
152									Format: "int64",
153								},
154								Location: "query",
155							},
156							&Parameter{
157								Name: "projection",
158								Schema: Schema{
159									Type:  "string",
160									Enums: []string{"full", "noAcl"},
161									EnumDescriptions: []string{
162										"Include all properties.",
163										"Omit owner, acl and defaultObjectAcl properties.",
164									},
165								},
166								Location: "query",
167							},
168						},
169						ParameterOrder: []string{"bucket"},
170						Response:       &Schema{Ref: "Bucket", Kind: ReferenceKind},
171						Scopes: []string{
172							"https://www.googleapis.com/auth/cloud-platform",
173							"https://www.googleapis.com/auth/cloud-platform.read-only",
174							"https://www.googleapis.com/auth/devstorage.full_control",
175							"https://www.googleapis.com/auth/devstorage.read_only",
176							"https://www.googleapis.com/auth/devstorage.read_write",
177						},
178						SupportsMediaDownload: true,
179						MediaUpload: &MediaUpload{
180							Accept:  []string{"application/octet-stream"},
181							MaxSize: "1GB",
182							Protocols: map[string]Protocol{
183								"simple": {
184									Multipart: true,
185									Path:      "/upload/customDataSources/{customDataSourceId}/uploads",
186								},
187								"resumable": {
188									Multipart: true,
189									Path:      "/resumable/upload/customDataSources/{customDataSourceId}/uploads",
190								},
191							},
192						},
193					},
194				},
195			},
196		},
197	}
198	// Resolve schema references.
199	bucket := want.Schemas["Bucket"]
200	want.Schemas["Buckets"].Properties[0].Schema.ItemSchema.RefSchema = bucket
201	want.Methods[0].Response.RefSchema = bucket
202	want.Resources[0].Methods[0].Response.RefSchema = bucket
203	for k, gs := range got.Schemas {
204		ws := want.Schemas[k]
205		if !reflect.DeepEqual(gs, ws) {
206			t.Fatalf("schema %s: got\n%+v\nwant\n%+v", k, gs, ws)
207		}
208	}
209	if len(got.Schemas) != len(want.Schemas) {
210		t.Errorf("want %d schemas, got %d", len(got.Schemas), len(want.Schemas))
211	}
212	compareMethodLists(t, got.Methods, want.Methods)
213	for i, gr := range got.Resources {
214		wr := want.Resources[i]
215		compareMethodLists(t, gr.Methods, wr.Methods)
216		if !reflect.DeepEqual(gr, wr) {
217			t.Fatalf("resource %d: got\n%+v\nwant\n%+v", i, gr, wr)
218		}
219	}
220	if len(got.Resources) != len(want.Resources) {
221		t.Errorf("want %d resources, got %d", len(got.Resources), len(want.Resources))
222	}
223	if !reflect.DeepEqual(got, want) {
224		t.Errorf("got\n%+v\nwant\n%+v", got, want)
225	}
226}
227
228func compareMethodLists(t *testing.T, got, want MethodList) {
229	if len(got) != len(want) {
230		t.Fatalf("got %d methods, want %d", len(got), len(want))
231	}
232	for i, gm := range got {
233		gm.JSONMap = nil // don't compare the raw JSON
234		wm := want[i]
235		if !reflect.DeepEqual(gm, wm) {
236			t.Errorf("#%d: got\n%+v\nwant\n%+v", i, gm, wm)
237		}
238	}
239}
240
241func TestDocumentErrors(t *testing.T) {
242	for _, in := range []string{
243		`{"name": "X"`, // malformed JSON
244		`{"id": 3}`,    // ID is an int instead of a string
245		`{"auth": "oauth2": { "scopes": "string" }}`, // wrong auth structure
246	} {
247		_, err := NewDocument([]byte(in))
248		if err == nil {
249			t.Errorf("%s: got nil, want error", in)
250		}
251	}
252}
253
254func TestSchemaErrors(t *testing.T) {
255	for _, s := range []*Schema{
256		{Type: "array"},                         // missing item schema
257		{Type: "string", ItemSchema: &Schema{}}, // items w/o array
258		{Type: "moose"},                         // bad kind
259		{Ref: "Thing"},                          // unresolved reference
260	} {
261		if err := s.init(nil); err == nil {
262			t.Errorf("%+v: got nil, want error", s)
263		}
264	}
265}
266
267func TestErrorDoc(t *testing.T) {
268	bytes, err := ioutil.ReadFile("testdata/error.json")
269	if err != nil {
270		t.Fatal(err)
271	}
272	if _, err := NewDocument(bytes); err == nil {
273		t.Error("got nil, want error")
274	} else if !strings.Contains(err.Error(), "404") {
275		t.Errorf("got %v, want 404", err)
276	}
277}
278