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