blob: 577d070b8f9bf3f268c0a6cb159ac68a2ad37338 [file] [log] [blame]
Zsolt Harasztibae12752016-10-10 09:55:30 -07001#
2# Copyright 2016 the original author or authors.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16import json
17import os
18from unittest import TestCase
Matteo Scandolo02af1332017-04-27 10:22:35 -070019from mock import Mock
Zsolt Harasztibae12752016-10-10 09:55:30 -070020
21from chameleon.protoc_plugins.descriptor_parser import DescriptorParser
22from chameleon.protoc_plugins.swagger_template \
23 import native_descriptors_to_swagger, DuplicateMethodAndPathError, \
24 ProtobufCompilationFailedError, InvalidPathArgumentError
25from tests.utests.chameleon.protoc_plugins.test_utils import unindent, \
26 json_rt, generate_plugin_request, load_file
27
28
29class SwaggerTemplateTests(TestCase):
30
31 maxDiff = 10000
32
33 def gen_swagger(self, proto):
34 request = generate_plugin_request(proto)
35 parser = DescriptorParser()
36 native_data = parser.parse_file_descriptors(request.proto_file,
37 type_tag_name='_type',
38 fold_comments=True)
39 swagger = native_descriptors_to_swagger(native_data)
40 return swagger
41
Matteo Scandolo02af1332017-04-27 10:22:35 -070042 def test_swagger_url(self):
43
44 grpc = {
45 '_channel': {
46 '_Rendezvous': {}
47 }
48 }
49 from chameleon.web_server.web_server import WebServer
50 server = yield WebServer(9101, '/', '/swagger', grpc)
51 server.app = 'app'
52
53 server.add_swagger_routes = Mock()
54
55 self.assertEqual(server.add_swagger_routes.call_count, 1)
56 server.add_swagger_routes.assert_called_once_with('app', '/swagger')
57
Zsolt Harasztibae12752016-10-10 09:55:30 -070058 def test_empty_def(self):
59
60 proto = unindent("""
61 syntax = "proto3";
62 package test;
63 """)
64
65 expected_swagger = {
66 u'swagger': u'2.0',
67 u'info': {
68 u'title': u'test.proto',
69 u'version': u'version not set'
70 },
71 u'schemes': [u"http", u"https"],
72 u'consumes': [u"application/json"],
73 u'produces': [u"application/json"],
74 u'paths': {},
75 u'definitions': {}
76 }
77
78 swagger = self.gen_swagger(proto)
79 self.assertEqual(json_rt(swagger), expected_swagger)
80
81 def test_empty_message_with_service(self):
82
83 proto = unindent("""
84 syntax = "proto3";
85 package test;
86 import "google/api/annotations.proto";
87 message Null {}
88 service TestService {
89 rpc Get(Null) returns(Null) {
90 option (google.api.http) = {
91 get: "/test"
92 };
93 }
94 }
95 """)
96
97 expected_swagger = {
98 u'swagger': u'2.0',
99 u'info': {
100 u'title': u'test.proto',
101 u'version': u"version not set"
102 },
103 u'schemes': [u"http", u"https"],
104 u'consumes': [u"application/json"],
105 u'produces': [u"application/json"],
106 u'paths': {
107 u'/test': {
108 u'get': {
109 u'operationId': u'Get',
110 u'responses': {
111 u'200': {
112 u'description': u'',
113 u'schema': {
114 u'$ref': u'#/definitions/test.Null'
115 }
116 }
117 },
118 u'tags': [u'TestService']
119 }
120 }
121 },
122 u'definitions': {
123 u'test.Null': {
124 u'type': u'object'
125 }
126 }
127 }
128
129 swagger = self.gen_swagger(proto)
130 self.assertEqual(json_rt(swagger), expected_swagger)
131
132 def test_simple_annotated_message_with_simple_annotated_service(self):
133
134 proto = unindent("""
135 syntax = "proto3";
136 package test;
137 import "google/api/annotations.proto";
138
139 // Simple Message
140 message Simple {
141 string str = 1; // a string attribute
142 int32 int = 2; // an int32 attribute
143 }
144
145 // Service to get things done
146 service TestService {
147
148 /* Get simple answer
149 *
150 * Returns the true answer to all of life's persistent questions.
151 */
152 rpc Get(Simple) returns(Simple) {
153 option (google.api.http) = {
154 get: "/test"
155 };
156 }
157 }
158 """)
159
160 expected_swagger = {
161 u'swagger': u'2.0',
162 u'info': {
163 u'title': u'test.proto',
164 u'version': u"version not set"
165 },
166 u'schemes': [u"http", u"https"],
167 u'consumes': [u"application/json"],
168 u'produces': [u"application/json"],
169 u'paths': {
170 u'/test': {
171 u'get': {
172 u'summary': u'Get simple answer',
173 u'description':
174 u' Returns the true answer to all of life\'s '
175 u'persistent questions.',
176 u'operationId': u'Get',
177 u'responses': {
178 u'200': {
179 u'description': u'',
180 u'schema': {
181 u'$ref': u'#/definitions/test.Simple'
182 }
183 }
184 },
185 u'tags': [u'TestService']
186 }
187 }
188 },
189 u'definitions': {
190 u'test.Simple': {
191 u'description': u'Simple Message',
192 u'type': u'object',
193 u'properties': {
194 u'int': {
195 u'description': u'an int32 attribute',
196 u'type': u'integer',
197 u'format': u'int32'
198 },
199 u'str': {
200 u'description': u'a string attribute',
201 u'type': u'string',
202 u'format': u'string'
203 }
204 }
205 }
206 }
207 }
208
209 swagger = self.gen_swagger(proto)
210 self.assertEqual(json_rt(swagger), expected_swagger)
211
212 def test_method_input_params_in_body(self):
213
214 proto = unindent("""
215 syntax = "proto3";
216 package test;
217 import "google/api/annotations.proto";
218
219 // Simple Message
220 message Simple {
221 string str = 1; // a string attribute
222 int32 int = 2; // an int32 attribute
223 }
224
225 // Service to get things done
226 service TestService {
227
228 /* Get simple answer
229 *
230 * Returns the true answer to all of life's persistent questions.
231 */
232 rpc Get(Simple) returns(Simple) {
233 option (google.api.http) = {
234 get: "/test"
235 };
236 }
237
238 /*
239 * Make up an answer (notice the leading blank line)
240 *
241 * Define the ultimate answer
242 */
243 rpc MakeUp(Simple) returns(Simple) {
244 option (google.api.http) = {
245 post: "/test"
246 body: "*"
247 };
248 }
249 }
250 """)
251
252 expected_swagger = {
253 u'swagger': u'2.0',
254 u'info': {
255 u'title': u'test.proto',
256 u'version': u"version not set"
257 },
258 u'schemes': [u"http", u"https"],
259 u'consumes': [u"application/json"],
260 u'produces': [u"application/json"],
261 u'paths': {
262 u'/test': {
263 u'get': {
264 u'summary': u'Get simple answer',
265 u'description':
266 u' Returns the true answer to all of life\'s '
267 u'persistent questions.',
268 u'operationId': u'Get',
269 u'responses': {
270 u'200': {
271 u'description': u'',
272 u'schema': {
273 u'$ref': u'#/definitions/test.Simple'
274 }
275 }
276 },
277 u'tags': [u'TestService']
278 },
279 u'post': {
280 u'summary': u'Make up an answer (notice the leading '
281 u'blank line)',
282 u'description': u' Define the ultimate answer',
283 u'operationId': u'MakeUp',
284 u'parameters': [{
285 u'name': u'body',
286 u'in': u'body',
287 u'required': True,
288 u'schema': {u'$ref': u'#/definitions/test.Simple'}
289 }],
290 u'responses': {
291 u'200': {
292 u'description': u'',
293 u'schema': {
294 u'$ref': u'#/definitions/test.Simple'
295 }
296 }
297 },
298 u'tags': [u'TestService']
299 }
300 }
301 },
302 u'definitions': {
303 u'test.Simple': {
304 u'description': u'Simple Message',
305 u'type': u'object',
306 u'properties': {
307 u'int': {
308 u'description': u'an int32 attribute',
309 u'type': u'integer',
310 u'format': u'int32'
311 },
312 u'str': {
313 u'description': u'a string attribute',
314 u'type': u'string',
315 u'format': u'string'
316 }
317 }
318 }
319 }
320 }
321
322 swagger = self.gen_swagger(proto)
323 self.assertEqual(json_rt(swagger), expected_swagger)
324
325 def test_catch_repeating_verbs_for_same_path(self):
326
327 proto = unindent("""
328 syntax = "proto3";
329 package test;
330 import "google/api/annotations.proto";
331 message Null {}
332 service TestService {
333 rpc Get(Null) returns(Null) {
334 option (google.api.http) = {
335 get: "/test"
336 };
337 }
338 rpc MakeUp(Null) returns(Null) {
339 option (google.api.http) = {
340 get: "/test"
341 body: "*"
342 };
343 }
344 }
345 """)
346
347 with self.assertRaises(DuplicateMethodAndPathError):
348 self.gen_swagger(proto)
349
350 def test_catch_unresolved_message_type_reference(self):
351
352 proto = unindent("""
353 syntax = "proto3";
354 package test;
355 import "google/api/annotations.proto";
356 message Null {}
357 service TestService {
358 rpc Get(Simple) returns(Simple) {
359 option (google.api.http) = {
360 get: "/test"
361 };
362 }
363 rpc MakeUp(Simple) returns(Simple) {
364 option (google.api.http) = {
365 get: "/test"
366 body: "*"
367 };
368 }
369 }
370 """)
371
372 with self.assertRaises(ProtobufCompilationFailedError):
373 self.gen_swagger(proto)
374
375 def test_path_parameter_handling(self):
376
377 proto = unindent("""
378 syntax = "proto3";
379 package test;
380 import "google/api/annotations.proto";
381 message Simple {
382 string str = 1;
383 int32 int = 2;
384 }
385 service TestService {
386 rpc Get(Simple) returns(Simple) {
387 option (google.api.http) = {
388 get: "/test/{str}/{int}"
389 };
390 }
391 }
392 """)
393
394 expected_swagger_path = {
395 u'/test/{str}/{int}': {
396 u'get': {
397 u'operationId': u'Get',
398 u'parameters': [{
399 u'name': u'str',
400 u'in': u'path',
401 u'type': u'string',
402 u'format': u'string',
403 u'required': True
404 }, {
405 u'name': u'int',
406 u'in': u'path',
407 u'type': u'integer',
408 u'format': u'int32',
409 u'required': True
410 }],
411 u'responses': {
412 u'200': {
413 u'description': u'',
414 u'schema': {
415 u'$ref': u'#/definitions/test.Simple'
416 }
417 }
418 },
419 u'tags': [u'TestService']
420 }
421 }
422 }
423
424 swagger = self.gen_swagger(proto)
425 self.assertEqual(json_rt(swagger['paths']), expected_swagger_path)
426
427 def test_path_parameter_error_handling(self):
428
429 proto = unindent("""
430 syntax = "proto3";
431 package test;
432 import "google/api/annotations.proto";
433 message Simple {
434 string str = 1;
435 int32 int = 2;
436 }
437 service TestService {
438 rpc Get(Simple) returns(Simple) {
439 option (google.api.http) = {
440 get: "/test/{str}/{xxxxx}/{int}"
441 };
442 }
443 }
444 """)
445
446 with self.assertRaises(InvalidPathArgumentError):
447 self.gen_swagger(proto)
448
449 def test_alternative_bindings(self):
450
451 proto = unindent("""
452 syntax = "proto3";
453 package test;
454 import "google/api/annotations.proto";
455 message Simple {
456 string str = 1;
457 int32 int = 2;
458 }
459 service TestService {
460 rpc Get(Simple) returns(Simple) {
461 option (google.api.http) = {
462 get: "/v1/test/{str}/{int}"
463 additional_bindings {
464 post: "/v2/test"
465 body: "*"
466 }
467 additional_bindings {
468 get: "/v2/test/{int}/{str}"
469 }
470 };
471 }
472 }
473 """)
474
475 expected_swagger_path = {
476 u'/v1/test/{str}/{int}': {
477 u'get': {
478 u'operationId': u'Get',
479 u'parameters': [{
480 u'name': u'str',
481 u'in': u'path',
482 u'type': u'string',
483 u'format': u'string',
484 u'required': True
485 }, {
486 u'name': u'int',
487 u'in': u'path',
488 u'type': u'integer',
489 u'format': u'int32',
490 u'required': True
491 }],
492 u'responses': {
493 u'200': {
494 u'description': u'',
495 u'schema': {
496 u'$ref': u'#/definitions/test.Simple'
497 }
498 }
499 },
500 u'tags': [u'TestService']
501 }
502 },
503 u'/v2/test': {
504 u'post': {
505 u'operationId': u'Get',
506 u'parameters': [{
507 u'in': u'body',
508 u'name': u'body',
509 u'required': True,
510 u'schema': {u'$ref': u'#/definitions/test.Simple'}
511 }],
512 u'responses': {
513 u'200': {
514 u'description': u'',
515 u'schema': {u'$ref': u'#/definitions/test.Simple'}
516 }
517 },
518 u'tags': [u'TestService']
519 }
520 },
521 u'/v2/test/{int}/{str}': {
522 u'get': {
523 u'operationId': u'Get',
524 u'parameters': [{
525 u'format': u'int32',
526 u'in': u'path',
527 u'name': u'int',
528 u'required': True,
529 u'type': u'integer'
530 }, {
531 u'format': u'string',
532 u'in': u'path',
533 u'name': u'str',
534 u'required': True,
535 u'type': u'string'
536 }],
537 u'responses': {
538 u'200': {
539 u'description': u'',
540 u'schema': {u'$ref': u'#/definitions/test.Simple'}
541 }
542 },
543 u'tags': [u'TestService']
544 }
545 }
546 }
547
548 swagger = self.gen_swagger(proto)
549 self.assertEqual(json_rt(swagger['paths']), expected_swagger_path)
550
551 def test_google_null(self):
552
553 proto = unindent("""
554 syntax = "proto3";
555 package test;
556 import "google/api/annotations.proto";
557 import "google/protobuf/empty.proto";
558 service TestService {
559 rpc Get(google.protobuf.Empty) returns(google.protobuf.Empty) {
560 option (google.api.http) = {
561 get: "/echo"
562 };
563 }
564 }
565 """)
566
567 expected_swagger = {
568 u'swagger': u'2.0',
569 u'info': {
570 u'title': u'test.proto',
571 u'version': u"version not set"
572 },
573 u'schemes': [u"http", u"https"],
574 u'consumes': [u"application/json"],
575 u'produces': [u"application/json"],
576 u'paths': {
577 u'/echo': {
578 u'get': {
579 u'operationId': u'Get',
580 u'responses': {
581 u'200': {
582 u'description': u'',
583 u'schema': {
584 u'$ref':
585 u'#/definitions/google.protobuf.Empty'
586 }
587 }
588 },
589 u'tags': [u'TestService']
590 }
591 }
592 },
593 u'definitions': {
594 u'google.protobuf.Empty': {
595 u'description': u'A generic empty message that you can '
596 u're-use to avoid defining duplicated\n '
597 u'empty messages in your APIs. A typical '
598 u'example is to use it as the request\n '
599 u'or the response type of an API method. '
600 u'For instance:\n\n service Foo {\n '
601 u' rpc Bar(google.protobuf.Empty) '
602 u'returns (google.protobuf.Empty);\n '
603 u'}\n\n The JSON representation for '
604 u'`Empty` is empty JSON object `{}`.',
605 u'type': u'object'
606 }
607 }
608 }
609
610 swagger = self.gen_swagger(proto)
611 self.assertEqual(json_rt(swagger), expected_swagger)
612
613
614 def test_nested_type_definitions(self):
615
616 proto = unindent("""
617 syntax = "proto3";
618 package test;
619 import "google/api/annotations.proto";
620 import "google/protobuf/empty.proto";
621 message Null {}
622 message Outer {
623 message Inner {
624 message Innermost {
625 bool healthy = 1;
626 string illness = 2;
627 }
628 Innermost innermost = 1;
629 string other = 2;
630 }
631 string name = 1;
632 Inner inner = 2;
633 }
634 service TestService {
635 rpc Get(Null) returns(Outer) {
636 option (google.api.http) = {
637 get: "/test"
638 };
639 }
640 }
641 """)
642
643 expected_swagger_definitions = {
644 u'test.Null': {u'type': u'object'},
645 u'test.Outer': {
646 u'type': u'object',
647 u'properties': {
648 u'inner': {
649 u'$ref': u'#/definitions/test.Outer.Inner'
650 },
651 u'name': {
652 u'type': u'string',
653 u'format': u'string'
654 }
655 }
656 },
657 u'test.Outer.Inner': {
658 u'type': u'object',
659 u'properties': {
660 u'innermost': {
661 u'$ref': u'#/definitions/test.Outer.Inner.Innermost'
662 },
663 u'other': {
664 u'type': u'string',
665 u'format': u'string'
666 }
667 }
668 },
669 u'test.Outer.Inner.Innermost': {
670 u'type': u'object',
671 u'properties': {
672 u'healthy': {
673 u'type': u'boolean',
674 u'format': u'boolean'
675 },
676 u'illness': {
677 u'type': u'string',
678 u'format': u'string'
679 }
680 }
681 }
682 }
683
684 swagger = self.gen_swagger(proto)
685 self.assertEqual(json_rt(swagger['definitions']),
686 expected_swagger_definitions)
687
688 def test_enum(self):
689
690 proto = unindent("""
691 syntax = "proto3";
692 package test;
693 import "google/api/annotations.proto";
694 message Null {};
695 // Detailed weather state
696 enum WeatherState {
697 GOOD = 0; // Weather is good
698 BAD = 1; // Weather is bad
699 }
700 message Forecast {
701 WeatherState forecast = 1;
702 }
703 service ForecastService {
704 rpc GetForecast(Null) returns(Forecast) {
705 option (google.api.http) = {
706 get: "/forecast"
707 };
708 }
709 }
710 """)
711
712 expected_swagger_definitions = {
713 u'test.Null': {u'type': u'object'},
714 u'test.WeatherState': {
715 u'default': u'GOOD',
716 u'description': u'Detailed weather state\n'
717 u'Valid values:\n'
718 u' - GOOD: Weather is good\n'
719 u' - BAD: Weather is bad',
720 u'type': u'string',
721 u'enum': [u'GOOD', u'BAD']
722 },
723 u'test.Forecast': {
724 u'type': u'object',
725 u'properties': {
726 u'forecast': {u'$ref': u'#/definitions/test.WeatherState'}
727 }
728 }
729 }
730
731 swagger = self.gen_swagger(proto)
732 self.assertEqual(json_rt(swagger['definitions']),
733 expected_swagger_definitions)
734
735 def test_nested_enum(self):
736
737 proto = unindent("""
738 syntax = "proto3";
739 package test;
740 import "google/api/annotations.proto";
741 message Null {};
742 message Forecast {
743 // Detailed weather state
744 enum WeatherState {
745 GOOD = 0; // Weather is good
746 BAD = 1; // Weather is bad
747 }
748 WeatherState forecast = 1;
749 }
750 service ForecastService {
751 rpc GetForecast(Null) returns(Forecast) {
752 option (google.api.http) = {
753 get: "/forecast"
754 };
755 }
756 }
757 """)
758
759 expected_swagger_definitions = {
760 u'test.Null': {u'type': u'object'},
761 u'test.Forecast.WeatherState': {
762 u'default': u'GOOD',
763 u'description': u'Detailed weather state\n'
764 u'Valid values:\n'
765 u' - GOOD: Weather is good\n'
766 u' - BAD: Weather is bad',
767 u'type': u'string',
768 u'enum': [u'GOOD', u'BAD']
769 },
770 u'test.Forecast': {
771 u'type': u'object',
772 u'properties': {
773 u'forecast': {
774 u'$ref': u'#/definitions/test.Forecast.WeatherState'}
775 }
776 }
777 }
778
779 swagger = self.gen_swagger(proto)
780 self.assertEqual(json_rt(swagger['definitions']),
781 expected_swagger_definitions)
782
783 def test_array_of_simple_types(self):
784
785 proto = unindent("""
786 syntax = "proto3";
787 package test;
788 import "google/api/annotations.proto";
789 message Null {};
790 message Invitations {
791 string event = 1;
792 repeated string names = 2;
793 repeated int32 ages = 3;
794 }
795 service RsvpService {
796 rpc Get(Null) returns(Invitations) {
797 option (google.api.http) = {
798 get: "/invitations"
799 };
800 }
801 }
802 """)
803
804 expected_swagger_definitions = {
805 u'test.Null': {u'type': u'object'},
806 u'test.Invitations': {
807 u'type': u'object',
808 u'properties': {
809 u'event': {
810 u'type': u'string',
811 u'format': u'string'
812 },
813 u'names': {
814 u'type': u'array',
815 u'items': {
816 u'type': u'string',
817 u'format': u'string'
818 }
819 },
820 u'ages': {
821 u'type': u'array',
822 u'items': {
823 u'type': u'integer',
824 u'format': u'int32'
825 }
826 }
827 }
828 }
829 }
830
831 swagger = self.gen_swagger(proto)
832 self.assertEqual(json_rt(swagger['definitions']),
833 expected_swagger_definitions)
834
835 def test_array_of_object_type(self):
836
837 proto = unindent("""
838 syntax = "proto3";
839 package test;
840 import "google/api/annotations.proto";
841 message Null {};
842 message Invitations {
843 message Address {
844 string street = 1;
845 string city = 2;
846 }
847 string event = 1;
848 repeated Null nulles = 2;
849 repeated Address addresses = 3;
850 }
851 service RsvpService {
852 rpc Get(Null) returns(Invitations) {
853 option (google.api.http) = {
854 get: "/invitations"
855 };
856 }
857 }
858 """)
859
860 expected_swagger_definitions = {
861 u'test.Null': {u'type': u'object'},
862 u'test.Invitations.Address': {
863 u'type': u'object',
864 u'properties': {
865 u'street': {
866 u'type': u'string',
867 u'format': u'string'
868 },
869 u'city': {
870 u'type': u'string',
871 u'format': u'string'
872 }
873 }
874 },
875 u'test.Invitations': {
876 u'type': u'object',
877 u'properties': {
878 u'event': {
879 u'type': u'string',
880 u'format': u'string'
881 },
882 u'nulles': {
883 u'type': u'array',
884 u'items': {
885 u'$ref': u'#/definitions/test.Null'
886 }
887 },
888 u'addresses': {
889 u'type': u'array',
890 u'items': {
891 u'$ref': u'#/definitions/test.Invitations.Address'
892 }
893 }
894 }
895 }
896 }
897
898 swagger = self.gen_swagger(proto)
899 self.assertEqual(json_rt(swagger['definitions']),
900 expected_swagger_definitions)
901
902 def test_recursively_nested_values(self):
903
904 proto = unindent("""
905 syntax = "proto3";
906 package test;
907 import "google/api/annotations.proto";
908 message Null {};
909 message TreeNode {
910 string name = 1;
911 repeated TreeNode children = 2;
912 }
913 service RsvpService {
914 rpc Get(Null) returns(TreeNode) {
915 option (google.api.http) = {
916 get: "/invitations"
917 };
918 }
919 }
920 """)
921
922 expected_swagger_definitions = {
923 u'test.Null': {u'type': u'object'},
924 u'test.TreeNode': {
925 u'type': u'object',
926 u'properties': {
927 u'name': {
928 u'type': u'string',
929 u'format': u'string'
930 },
931 u'children': {
932 u'type': u'array',
933 u'items': {
934 u'$ref': u'#/definitions/test.TreeNode'
935 }
936 }
937 }
938 }
939 }
940
941 swagger = self.gen_swagger(proto)
942 self.assertEqual(json_rt(swagger['definitions']),
943 expected_swagger_definitions)
944
945 def test_map_fields(self):
946
947 proto = unindent("""
948 syntax = "proto3";
949 package test;
950 import "google/api/annotations.proto";
951 message Null {};
952 message Maps {
953 map<string, string> string_map = 1;
954 map<string, int32> int32_map = 2;
955 map<string, Null> object_map = 3;
956 }
957 service RsvpService {
958 rpc Get(Null) returns(Maps) {
959 option (google.api.http) = {
960 get: "/maps"
961 };
962 }
963 }
964 """)
965
966 expected_swagger_definitions = {
967 u'test.Null': {u'type': u'object'},
968 u'test.Maps': {
969 u'type': u'object',
970 u'properties': {
971 u'string_map': {
972 u'type': u'object',
973 u'additionalProperties': {
974 u'type': u'string',
975 u'format': u'string'
976 }
977 },
978 u'int32_map': {
979 u'type': u'object',
980 u'additionalProperties': {
981 u'type': u'integer',
982 u'format': u'int32'
983 }
984 },
985 u'object_map': {
986 u'type': u'object',
987 u'additionalProperties': {
988 u'$ref': u'#/definitions/test.Null',
989 }
990 }
991 }
992 }
993 }
994
995 swagger = self.gen_swagger(proto)
996 self.assertEqual(json_rt(swagger['definitions']),
997 expected_swagger_definitions)
998
999 def test_array_and_map_of_enum(self):
1000
1001 proto = unindent("""
1002 syntax = "proto3";
1003 package test;
1004 import "google/api/annotations.proto";
1005 message Null {};
1006 enum State {
1007 GOOD = 0;
1008 BAD = 1;
1009 }
1010 message Map {
1011 map<string, State> enum_map = 1;
1012 repeated State states = 2;
1013 }
1014 service RsvpService {
1015 rpc Get(Null) returns(Map) {
1016 option (google.api.http) = {
1017 get: "/maps"
1018 };
1019 }
1020 }
1021 """)
1022
1023 expected_swagger_definitions = {
1024 u'test.Null': {u'type': u'object'},
1025 u'test.State': {
1026 u'default': u'GOOD',
1027 u'description': u'State\n'
1028 u'Valid values:\n'
1029 u' - GOOD\n'
1030 u' - BAD',
1031 u'type': u'string',
1032 u'enum': [u'GOOD', u'BAD']
1033 },
1034 u'test.Map': {
1035 u'type': u'object',
1036 u'properties': {
1037 u'enum_map': {
1038 u'type': u'object',
1039 u'additionalProperties': {
1040 u'$ref': u'#/definitions/test.State',
1041 }
1042 },
1043 u'states': {
1044 u'type': u'array',
1045 u'items': {
1046 u'$ref': u'#/definitions/test.State'
1047 }
1048 }
1049 }
1050 }
1051 }
1052
1053 swagger = self.gen_swagger(proto)
1054 self.assertEqual(json_rt(swagger['definitions']),
1055 expected_swagger_definitions)
1056
1057 def test_kitchen_sink(self):
1058
1059 proto = load_file(
1060 os.path.dirname(__file__) + '/a_bit_of_everything.proto')
1061
1062 swagger = self.gen_swagger(proto)
1063
1064 expected_swagger = json.loads(load_file(
1065 os.path.dirname(__file__) + '/a_bit_of_everything.swagger.json')
1066 )
1067
1068 self.maxDiff = 100000
1069 self.assertEqual(json_rt(swagger), expected_swagger)