Merge lp:~rackspace-titan/nova/versions_detailed_json_xml into lp:~hudson-openstack/nova/trunk

Proposed by William Wolf
Status: Merged
Approved by: Vish Ishaya
Approved revision: 1338
Merged at revision: 1357
Proposed branch: lp:~rackspace-titan/nova/versions_detailed_json_xml
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 1479 lines (+1084/-132)
5 files modified
nova/api/openstack/__init__.py (+5/-0)
nova/api/openstack/versions.py (+235/-37)
nova/api/openstack/views/versions.py (+46/-12)
nova/api/openstack/wsgi.py (+3/-0)
nova/tests/api/openstack/test_versions.py (+795/-83)
To merge this branch: bzr merge lp:~rackspace-titan/nova/versions_detailed_json_xml
Reviewer Review Type Date Requested Status
Vish Ishaya (community) Approve
Alex Meade (community) Approve
Brian Waldon (community) Approve
Review via email: mp+69153@code.launchpad.net

Description of the change

Add support for 300 Multiple Choice responses when no version identifier is used in the URI (or no version header is present)

Also adds support for server detail json/xml/atom requests.

To post a comment you must log in.
1327. By William Wolf

merge from trunk

Revision history for this message
Brian Waldon (bcwaldon) wrote :

Can you keep all of the XMLNS constant strings in nova.api.openstack.wsgi?

review: Needs Information
Revision history for this message
William Wolf (throughnothing) wrote :

Thanks Brian, I hadn't realized some of these were already in wsgi. I'll fix that up.

1328. By William Wolf

use wsgi XMLNS/ATOM vars

1329. By William Wolf

refactoring and make self links correct (not hard coded)

Revision history for this message
William Wolf (throughnothing) wrote :

Alright, I think I got the wsgi xmlns variables in use, and fixed up the self link issues.

Revision history for this message
Brian Waldon (bcwaldon) wrote :

Looking good, but you've got one pep8 error.

46, 52, 79, 85: (Minor style nit) If you are going to break up strings like this, can you line them up vertically like this:

51 + "href": "http://docs.rackspacecloud.com/"
52 + "servers/api/v1.0/application.wadl"

103-107: Why did you need to add this method? It already has this method from wsgi.Resource's parent class. I did notice the cls() call is different, can you explain?

462,463: Just a note, the trailing comma is preferred to prevent having to add it when a new item is appended to the list.

Revision history for this message
William Wolf (throughnothing) wrote :

I think you are right about factory() Brian, we don't need it. I didn't realise that wsgi.Application had it already.

I've fixed the pep8 and other issues as well.

1330. By William Wolf

fixed pep8 issues and removed unnecessary factory function

1331. By William Wolf

merge with trunk

1332. By William Wolf

fixed typo

1333. By William Wolf

fix more spacing issues, and removed self link from versions template data

1334. By William Wolf

fix run_tests.sh

Revision history for this message
Brian Waldon (bcwaldon) wrote :

Leave Gabe to me.

review: Approve
1335. By William Wolf

merge from trunk

1336. By William Wolf

merge from trunk

Revision history for this message
Alex Meade (alex-meade) wrote :

Good work nailing this branch down

review: Approve
1337. By William Wolf

merge from trunk

1338. By William Wolf

merge from trunk

Revision history for this message
Vish Ishaya (vishvananda) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'nova/api/openstack/__init__.py'
2--- nova/api/openstack/__init__.py 2011-07-22 21:04:26 +0000
3+++ nova/api/openstack/__init__.py 2011-08-02 20:52:09 +0000
4@@ -40,6 +40,7 @@
5 from nova.api.openstack import server_metadata
6 from nova.api.openstack import shared_ip_groups
7 from nova.api.openstack import users
8+from nova.api.openstack import versions
9 from nova.api.openstack import wsgi
10 from nova.api.openstack import zones
11
12@@ -115,6 +116,10 @@
13 'select': 'POST',
14 'boot': 'POST'})
15
16+ mapper.connect("versions", "/",
17+ controller=versions.create_resource(version),
18+ action='show')
19+
20 mapper.resource("console", "consoles",
21 controller=consoles.create_resource(),
22 parent_resource=dict(member_name='server',
23
24=== modified file 'nova/api/openstack/versions.py'
25--- nova/api/openstack/versions.py 2011-07-21 16:19:09 +0000
26+++ nova/api/openstack/versions.py 2011-08-02 20:52:09 +0000
27@@ -24,7 +24,66 @@
28 from nova.api.openstack import wsgi
29
30
31-ATOM_XMLNS = "http://www.w3.org/2005/Atom"
32+VERSIONS = {
33+ "v1.0": {
34+ "id": "v1.0",
35+ "status": "DEPRECATED",
36+ "updated": "2011-01-21T11:33:21Z",
37+ "links": [
38+ {
39+ "rel": "describedby",
40+ "type": "application/pdf",
41+ "href": "http://docs.rackspacecloud.com/"
42+ "servers/api/v1.0/cs-devguide-20110125.pdf"
43+ },
44+ {
45+ "rel": "describedby",
46+ "type": "application/vnd.sun.wadl+xml",
47+ "href": "http://docs.rackspacecloud.com/"
48+ "servers/api/v1.0/application.wadl"
49+ },
50+ ],
51+ "media-types": [
52+ {
53+ "base": "application/xml",
54+ "type": "application/vnd.openstack.compute-v1.0+xml"
55+ },
56+ {
57+ "base": "application/json",
58+ "type": "application/vnd.openstack.compute-v1.0+json"
59+ }
60+ ],
61+ },
62+ "v1.1": {
63+ "id": "v1.1",
64+ "status": "CURRENT",
65+ "updated": "2011-01-21T11:33:21Z",
66+ "links": [
67+ {
68+ "rel": "describedby",
69+ "type": "application/pdf",
70+ "href": "http://docs.rackspacecloud.com/"
71+ "servers/api/v1.1/cs-devguide-20110125.pdf"
72+ },
73+ {
74+ "rel": "describedby",
75+ "type": "application/vnd.sun.wadl+xml",
76+ "href": "http://docs.rackspacecloud.com/"
77+ "servers/api/v1.1/application.wadl"
78+ },
79+ ],
80+ "media-types": [
81+ {
82+ "base": "application/xml",
83+ "type": "application/vnd.openstack.compute-v1.1+xml"
84+ },
85+ {
86+ "base": "application/json",
87+ "type": "application/vnd.openstack.compute-v1.1+json"
88+ }
89+ ],
90+ },
91+}
92
93
94 class Versions(wsgi.Resource):
95@@ -36,16 +95,20 @@
96 }
97 }
98
99+ headers_serializer = VersionsHeadersSerializer()
100+
101 body_serializers = {
102 'application/atom+xml': VersionsAtomSerializer(metadata=metadata),
103 'application/xml': VersionsXMLSerializer(metadata=metadata),
104 }
105- serializer = wsgi.ResponseSerializer(body_serializers)
106+ serializer = wsgi.ResponseSerializer(
107+ body_serializers=body_serializers,
108+ headers_serializer=headers_serializer)
109
110 supported_content_types = ('application/json',
111 'application/xml',
112 'application/atom+xml')
113- deserializer = wsgi.RequestDeserializer(
114+ deserializer = VersionsRequestDeserializer(
115 supported_content_types=supported_content_types)
116
117 wsgi.Resource.__init__(self, None, serializer=serializer,
118@@ -53,60 +116,131 @@
119
120 def dispatch(self, request, *args):
121 """Respond to a request for all OpenStack API versions."""
122- version_objs = [
123- {
124- "id": "v1.1",
125- "status": "CURRENT",
126- #TODO(wwolf) get correct value for these
127- "updated": "2011-07-18T11:30:00Z",
128- },
129- {
130- "id": "v1.0",
131- "status": "DEPRECATED",
132- #TODO(wwolf) get correct value for these
133- "updated": "2010-10-09T11:30:00Z",
134- },
135- ]
136-
137 builder = nova.api.openstack.views.versions.get_view_builder(request)
138- versions = [builder.build(version) for version in version_objs]
139- return dict(versions=versions)
140+ if request.path == '/':
141+ # List Versions
142+ return builder.build_versions(VERSIONS)
143+ else:
144+ # Versions Multiple Choice
145+ return builder.build_choices(VERSIONS, request)
146+
147+
148+class VersionV10(object):
149+ def show(self, req):
150+ builder = nova.api.openstack.views.versions.get_view_builder(req)
151+ return builder.build_version(VERSIONS['v1.0'])
152+
153+
154+class VersionV11(object):
155+ def show(self, req):
156+ builder = nova.api.openstack.views.versions.get_view_builder(req)
157+ return builder.build_version(VERSIONS['v1.1'])
158+
159+
160+class VersionsRequestDeserializer(wsgi.RequestDeserializer):
161+ def get_expected_content_type(self, request):
162+ supported_content_types = list(self.supported_content_types)
163+ if request.path != '/':
164+ # Remove atom+xml accept type for 300 responses
165+ if 'application/atom+xml' in supported_content_types:
166+ supported_content_types.remove('application/atom+xml')
167+
168+ return request.best_match_content_type(supported_content_types)
169+
170+ def get_action_args(self, request_environment):
171+ """Parse dictionary created by routes library."""
172+ args = {}
173+ if request_environment['PATH_INFO'] == '/':
174+ args['action'] = 'index'
175+ else:
176+ args['action'] = 'multi'
177+
178+ return args
179
180
181 class VersionsXMLSerializer(wsgi.XMLDictSerializer):
182- def _versions_to_xml(self, versions):
183- root = self._xml_doc.createElement('versions')
184+ #TODO(wwolf): this is temporary until we get rid of toprettyxml
185+ # in the base class (XMLDictSerializer), which I plan to do in
186+ # another branch
187+ def to_xml_string(self, node, has_atom=False):
188+ self._add_xmlns(node, has_atom)
189+ return node.toxml(encoding='UTF-8')
190+
191+ def _versions_to_xml(self, versions, name="versions", xmlns=None):
192+ root = self._xml_doc.createElement(name)
193+ root.setAttribute("xmlns", wsgi.XMLNS_V11)
194+ root.setAttribute("xmlns:atom", wsgi.XMLNS_ATOM)
195
196 for version in versions:
197 root.appendChild(self._create_version_node(version))
198
199 return root
200
201- def _create_version_node(self, version):
202+ def _create_media_types(self, media_types):
203+ base = self._xml_doc.createElement('media-types')
204+ for type in media_types:
205+ node = self._xml_doc.createElement('media-type')
206+ node.setAttribute('base', type['base'])
207+ node.setAttribute('type', type['type'])
208+ base.appendChild(node)
209+
210+ return base
211+
212+ def _create_version_node(self, version, create_ns=False):
213 version_node = self._xml_doc.createElement('version')
214+ if create_ns:
215+ xmlns = wsgi.XMLNS_V11
216+ xmlns_atom = wsgi.XMLNS_ATOM
217+ version_node.setAttribute('xmlns', xmlns)
218+ version_node.setAttribute('xmlns:atom', xmlns_atom)
219+
220 version_node.setAttribute('id', version['id'])
221 version_node.setAttribute('status', version['status'])
222- version_node.setAttribute('updated', version['updated'])
223-
224- for link in version['links']:
225- link_node = self._xml_doc.createElement('atom:link')
226- link_node.setAttribute('rel', link['rel'])
227- link_node.setAttribute('href', link['href'])
228- version_node.appendChild(link_node)
229+ if 'updated' in version:
230+ version_node.setAttribute('updated', version['updated'])
231+
232+ if 'media-types' in version:
233+ media_types = self._create_media_types(version['media-types'])
234+ version_node.appendChild(media_types)
235+
236+ link_nodes = self._create_link_nodes(self._xml_doc, version['links'])
237+ for link in link_nodes:
238+ version_node.appendChild(link)
239
240 return version_node
241
242- def default(self, data):
243+ def index(self, data):
244 self._xml_doc = minidom.Document()
245 node = self._versions_to_xml(data['versions'])
246
247 return self.to_xml_string(node)
248
249+ def show(self, data):
250+ self._xml_doc = minidom.Document()
251+ node = self._create_version_node(data['version'], True)
252+
253+ return self.to_xml_string(node)
254+
255+ def multi(self, data):
256+ self._xml_doc = minidom.Document()
257+ node = self._versions_to_xml(data['choices'], 'choices',
258+ xmlns=wsgi.XMLNS_V11)
259+
260+ return self.to_xml_string(node)
261+
262
263 class VersionsAtomSerializer(wsgi.XMLDictSerializer):
264+ #TODO(wwolf): this is temporary until we get rid of toprettyxml
265+ # in the base class (XMLDictSerializer), which I plan to do in
266+ # another branch
267+ def to_xml_string(self, node, has_atom=False):
268+ self._add_xmlns(node, has_atom)
269+ return node.toxml(encoding='UTF-8')
270+
271 def __init__(self, metadata=None, xmlns=None):
272+ self.metadata = metadata or {}
273 if not xmlns:
274- self.xmlns = ATOM_XMLNS
275+ self.xmlns = wsgi.XMLNS_ATOM
276 else:
277 self.xmlns = xmlns
278
279@@ -135,8 +269,33 @@
280 link_href = link_href.rstrip('/')
281 return link_href.rsplit('/', 1)[0] + '/'
282
283- def _create_meta(self, root, versions):
284- title = self._create_text_elem('title', 'Available API Versions',
285+ def _create_detail_meta(self, root, version):
286+ title = self._create_text_elem('title', "About This Version",
287+ type='text')
288+
289+ updated = self._create_text_elem('updated', version['updated'])
290+
291+ uri = version['links'][0]['href']
292+ id = self._create_text_elem('id', uri)
293+
294+ link = self._xml_doc.createElement('link')
295+ link.setAttribute('rel', 'self')
296+ link.setAttribute('href', uri)
297+
298+ author = self._xml_doc.createElement('author')
299+ author_name = self._create_text_elem('name', 'Rackspace')
300+ author_uri = self._create_text_elem('uri', 'http://www.rackspace.com/')
301+ author.appendChild(author_name)
302+ author.appendChild(author_uri)
303+
304+ root.appendChild(title)
305+ root.appendChild(updated)
306+ root.appendChild(id)
307+ root.appendChild(author)
308+ root.appendChild(link)
309+
310+ def _create_list_meta(self, root, versions):
311+ title = self._create_text_elem('title', "Available API Versions",
312 type='text')
313 # Set this updated to the most recently updated version
314 recent = self._get_most_recent_update(versions)
315@@ -144,6 +303,7 @@
316
317 base_url = self._get_base_url(versions[0]['links'][0]['href'])
318 id = self._create_text_elem('id', base_url)
319+
320 link = self._xml_doc.createElement('link')
321 link.setAttribute('rel', 'self')
322 link.setAttribute('href', base_url)
323@@ -178,7 +338,10 @@
324 link_node = self._xml_doc.createElement('link')
325 link_node.setAttribute('rel', link['rel'])
326 link_node.setAttribute('href', link['href'])
327- entry.appendChild(link_node)
328+ if 'type' in link:
329+ link_node.setAttribute('type', link['type'])
330+
331+ entry.appendChild(link_node)
332
333 content = self._create_text_elem('content',
334 'Version %s %s (%s)' %
335@@ -190,10 +353,45 @@
336 entry.appendChild(content)
337 root.appendChild(entry)
338
339- def default(self, data):
340+ def index(self, data):
341 self._xml_doc = minidom.Document()
342 node = self._xml_doc.createElementNS(self.xmlns, 'feed')
343- self._create_meta(node, data['versions'])
344+ self._create_list_meta(node, data['versions'])
345 self._create_version_entries(node, data['versions'])
346
347 return self.to_xml_string(node)
348+
349+ def show(self, data):
350+ self._xml_doc = minidom.Document()
351+ node = self._xml_doc.createElementNS(self.xmlns, 'feed')
352+ self._create_detail_meta(node, data['version'])
353+ self._create_version_entries(node, [data['version']])
354+
355+ return self.to_xml_string(node)
356+
357+
358+class VersionsHeadersSerializer(wsgi.ResponseHeadersSerializer):
359+ def multi(self, response, data):
360+ response.status_int = 300
361+
362+
363+def create_resource(version='1.0'):
364+ controller = {
365+ '1.0': VersionV10,
366+ '1.1': VersionV11,
367+ }[version]()
368+
369+ body_serializers = {
370+ 'application/xml': VersionsXMLSerializer(),
371+ 'application/atom+xml': VersionsAtomSerializer(),
372+ }
373+ serializer = wsgi.ResponseSerializer(body_serializers)
374+
375+ supported_content_types = ('application/json',
376+ 'application/xml',
377+ 'application/atom+xml')
378+ deserializer = wsgi.RequestDeserializer(
379+ supported_content_types=supported_content_types)
380+
381+ return wsgi.Resource(controller, serializer=serializer,
382+ deserializer=deserializer)
383
384=== modified file 'nova/api/openstack/views/versions.py'
385--- nova/api/openstack/views/versions.py 2011-07-20 20:47:17 +0000
386+++ nova/api/openstack/views/versions.py 2011-08-02 20:52:09 +0000
387@@ -15,6 +15,7 @@
388 # License for the specific language governing permissions and limitations
389 # under the License.
390
391+import copy
392 import os
393
394
395@@ -31,16 +32,44 @@
396 """
397 self.base_url = base_url
398
399- def build(self, version_data):
400- """Generic method used to generate a version entity."""
401- version = {
402- "id": version_data["id"],
403- "status": version_data["status"],
404- "updated": version_data["updated"],
405- "links": self._build_links(version_data),
406- }
407-
408- return version
409+ def build_choices(self, VERSIONS, req):
410+ version_objs = []
411+ for version in VERSIONS:
412+ version = VERSIONS[version]
413+ version_objs.append({
414+ "id": version['id'],
415+ "status": version['status'],
416+ "links": [
417+ {
418+ "rel": "self",
419+ "href": self.generate_href(version['id'], req.path)
420+ }
421+ ],
422+ "media-types": version['media-types']
423+ })
424+
425+ return dict(choices=version_objs)
426+
427+ def build_versions(self, versions):
428+ version_objs = []
429+ for version in versions:
430+ version = versions[version]
431+ version_objs.append({
432+ "id": version['id'],
433+ "status": version['status'],
434+ "updated": version['updated'],
435+ "links": self._build_links(version),
436+ })
437+
438+ return dict(versions=version_objs)
439+
440+ def build_version(self, version):
441+ reval = copy.deepcopy(version)
442+ reval['links'].insert(0, {
443+ "rel": "self",
444+ "href": self.base_url.rstrip('/') + '/',
445+ })
446+ return dict(version=reval)
447
448 def _build_links(self, version_data):
449 """Generate a container of links that refer to the provided version."""
450@@ -55,6 +84,11 @@
451
452 return links
453
454- def generate_href(self, version_number):
455+ def generate_href(self, version_number, path=None):
456 """Create an url that refers to a specific version_number."""
457- return os.path.join(self.base_url, version_number) + '/'
458+ version_number = version_number.strip('/')
459+ if path:
460+ path = path.strip('/')
461+ return os.path.join(self.base_url, version_number, path)
462+ else:
463+ return os.path.join(self.base_url, version_number) + '/'
464
465=== modified file 'nova/api/openstack/wsgi.py'
466--- nova/api/openstack/wsgi.py 2011-07-28 21:59:25 +0000
467+++ nova/api/openstack/wsgi.py 2011-08-02 20:52:09 +0000
468@@ -13,6 +13,7 @@
469
470 XMLNS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0'
471 XMLNS_V11 = 'http://docs.openstack.org/compute/api/v1.1'
472+
473 XMLNS_ATOM = 'http://www.w3.org/2005/Atom'
474
475 LOG = logging.getLogger('nova.api.openstack.wsgi')
476@@ -386,6 +387,8 @@
477 link_node = xml_doc.createElement('atom:link')
478 link_node.setAttribute('rel', link['rel'])
479 link_node.setAttribute('href', link['href'])
480+ if 'type' in link:
481+ link_node.setAttribute('type', link['type'])
482 link_nodes.append(link_node)
483 return link_nodes
484
485
486=== modified file 'nova/tests/api/openstack/test_versions.py'
487--- nova/tests/api/openstack/test_versions.py 2011-07-21 21:20:32 +0000
488+++ nova/tests/api/openstack/test_versions.py 2011-08-02 20:52:09 +0000
489@@ -16,21 +16,92 @@
490 # under the License.
491
492 import json
493+import stubout
494 import webob
495+import xml.etree.ElementTree
496+
497
498 from nova import context
499 from nova import test
500 from nova.tests.api.openstack import fakes
501 from nova.api.openstack import versions
502 from nova.api.openstack import views
503+from nova.api.openstack import wsgi
504+
505+VERSIONS = {
506+ "v1.0": {
507+ "id": "v1.0",
508+ "status": "DEPRECATED",
509+ "updated": "2011-01-21T11:33:21Z",
510+ "links": [
511+ {
512+ "rel": "describedby",
513+ "type": "application/pdf",
514+ "href": "http://docs.rackspacecloud.com/"
515+ "servers/api/v1.0/cs-devguide-20110125.pdf"
516+ },
517+ {
518+ "rel": "describedby",
519+ "type": "application/vnd.sun.wadl+xml",
520+ "href": "http://docs.rackspacecloud.com/"
521+ "servers/api/v1.0/application.wadl"
522+ },
523+ ],
524+ "media-types": [
525+ {
526+ "base": "application/xml",
527+ "type": "application/vnd.openstack.compute-v1.0+xml"
528+ },
529+ {
530+ "base": "application/json",
531+ "type": "application/vnd.openstack.compute-v1.0+json"
532+ }
533+ ],
534+ },
535+ "v1.1": {
536+ "id": "v1.1",
537+ "status": "CURRENT",
538+ "updated": "2011-01-21T11:33:21Z",
539+ "links": [
540+ {
541+ "rel": "describedby",
542+ "type": "application/pdf",
543+ "href": "http://docs.rackspacecloud.com/"
544+ "servers/api/v1.1/cs-devguide-20110125.pdf"
545+ },
546+ {
547+ "rel": "describedby",
548+ "type": "application/vnd.sun.wadl+xml",
549+ "href": "http://docs.rackspacecloud.com/"
550+ "servers/api/v1.1/application.wadl"
551+ },
552+ ],
553+ "media-types": [
554+ {
555+ "base": "application/xml",
556+ "type": "application/vnd.openstack.compute-v1.1+xml"
557+ },
558+ {
559+ "base": "application/json",
560+ "type": "application/vnd.openstack.compute-v1.1+json"
561+ }
562+ ],
563+ },
564+}
565
566
567 class VersionsTest(test.TestCase):
568 def setUp(self):
569 super(VersionsTest, self).setUp()
570 self.context = context.get_admin_context()
571+ self.stubs = stubout.StubOutForTesting()
572+ fakes.stub_out_auth(self.stubs)
573+ #Stub out VERSIONS
574+ self.old_versions = versions.VERSIONS
575+ versions.VERSIONS = VERSIONS
576
577 def tearDown(self):
578+ versions.VERSIONS = self.old_versions
579 super(VersionsTest, self).tearDown()
580
581 def test_get_version_list(self):
582@@ -44,7 +115,7 @@
583 {
584 "id": "v1.1",
585 "status": "CURRENT",
586- "updated": "2011-07-18T11:30:00Z",
587+ "updated": "2011-01-21T11:33:21Z",
588 "links": [
589 {
590 "rel": "self",
591@@ -54,7 +125,7 @@
592 {
593 "id": "v1.0",
594 "status": "DEPRECATED",
595- "updated": "2010-10-09T11:30:00Z",
596+ "updated": "2011-01-21T11:33:21Z",
597 "links": [
598 {
599 "rel": "self",
600@@ -64,6 +135,183 @@
601 ]
602 self.assertEqual(versions, expected)
603
604+ def test_get_version_1_0_detail(self):
605+ req = webob.Request.blank('/v1.0/')
606+ req.accept = "application/json"
607+ res = req.get_response(fakes.wsgi_app())
608+ self.assertEqual(res.status_int, 200)
609+ self.assertEqual(res.content_type, "application/json")
610+ version = json.loads(res.body)
611+ expected = {
612+ "version": {
613+ "id": "v1.0",
614+ "status": "DEPRECATED",
615+ "updated": "2011-01-21T11:33:21Z",
616+ "links": [
617+ {
618+ "rel": "self",
619+ "href": "http://localhost/v1.0/"
620+ },
621+ {
622+ "rel": "describedby",
623+ "type": "application/pdf",
624+ "href": "http://docs.rackspacecloud.com/"
625+ "servers/api/v1.0/cs-devguide-20110125.pdf"
626+ },
627+ {
628+ "rel": "describedby",
629+ "type": "application/vnd.sun.wadl+xml",
630+ "href": "http://docs.rackspacecloud.com/"
631+ "servers/api/v1.0/application.wadl"
632+ }
633+ ],
634+ "media-types": [
635+ {
636+ "base": "application/xml",
637+ "type": "application/"
638+ "vnd.openstack.compute-v1.0+xml"
639+ },
640+ {
641+ "base": "application/json",
642+ "type": "application/"
643+ "vnd.openstack.compute-v1.0+json"
644+ }
645+ ]
646+ }
647+ }
648+ self.assertEqual(expected, version)
649+
650+ def test_get_version_1_1_detail(self):
651+ req = webob.Request.blank('/v1.1/')
652+ req.accept = "application/json"
653+ res = req.get_response(fakes.wsgi_app())
654+ self.assertEqual(res.status_int, 200)
655+ self.assertEqual(res.content_type, "application/json")
656+ version = json.loads(res.body)
657+ expected = {
658+ "version": {
659+ "id": "v1.1",
660+ "status": "CURRENT",
661+ "updated": "2011-01-21T11:33:21Z",
662+ "links": [
663+ {
664+ "rel": "self",
665+ "href": "http://localhost/v1.1/"
666+ },
667+ {
668+ "rel": "describedby",
669+ "type": "application/pdf",
670+ "href": "http://docs.rackspacecloud.com/"
671+ "servers/api/v1.1/cs-devguide-20110125.pdf"
672+ },
673+ {
674+ "rel": "describedby",
675+ "type": "application/vnd.sun.wadl+xml",
676+ "href": "http://docs.rackspacecloud.com/"
677+ "servers/api/v1.1/application.wadl"
678+ }
679+ ],
680+ "media-types": [
681+ {
682+ "base": "application/xml",
683+ "type": "application/"
684+ "vnd.openstack.compute-v1.1+xml"
685+ },
686+ {
687+ "base": "application/json",
688+ "type": "application/"
689+ "vnd.openstack.compute-v1.1+json"
690+ }
691+ ]
692+ }
693+ }
694+ self.assertEqual(expected, version)
695+
696+ def test_get_version_1_0_detail_xml(self):
697+ req = webob.Request.blank('/v1.0/')
698+ req.accept = "application/xml"
699+ res = req.get_response(fakes.wsgi_app())
700+ self.assertEqual(res.status_int, 200)
701+ self.assertEqual(res.content_type, "application/xml")
702+ root = xml.etree.ElementTree.XML(res.body)
703+ self.assertEqual(root.tag.split('}')[1], "version")
704+ self.assertEqual(root.tag.split('}')[0].strip('{'), wsgi.XMLNS_V11)
705+
706+ children = list(root)
707+ media_types = children[0]
708+ media_type_nodes = list(media_types)
709+ links = (children[1], children[2], children[3])
710+
711+ self.assertEqual(media_types.tag.split('}')[1], 'media-types')
712+ for media_node in media_type_nodes:
713+ self.assertEqual(media_node.tag.split('}')[1], 'media-type')
714+
715+ expected = """
716+ <version id="v1.0" status="DEPRECATED"
717+ updated="2011-01-21T11:33:21Z"
718+ xmlns="%s"
719+ xmlns:atom="http://www.w3.org/2005/Atom">
720+
721+ <media-types>
722+ <media-type base="application/xml"
723+ type="application/vnd.openstack.compute-v1.0+xml"/>
724+ <media-type base="application/json"
725+ type="application/vnd.openstack.compute-v1.0+json"/>
726+ </media-types>
727+
728+ <atom:link href="http://localhost/v1.0/"
729+ rel="self"/>
730+
731+ <atom:link href="http://docs.rackspacecloud.com/servers/
732+ api/v1.0/cs-devguide-20110125.pdf"
733+ rel="describedby"
734+ type="application/pdf"/>
735+
736+ <atom:link href="http://docs.rackspacecloud.com/servers/
737+ api/v1.0/application.wadl"
738+ rel="describedby"
739+ type="application/vnd.sun.wadl+xml"/>
740+ </version>""".replace(" ", "").replace("\n", "") % wsgi.XMLNS_V11
741+
742+ actual = res.body.replace(" ", "").replace("\n", "")
743+ self.assertEqual(expected, actual)
744+
745+ def test_get_version_1_1_detail_xml(self):
746+ req = webob.Request.blank('/v1.1/')
747+ req.accept = "application/xml"
748+ res = req.get_response(fakes.wsgi_app())
749+ self.assertEqual(res.status_int, 200)
750+ self.assertEqual(res.content_type, "application/xml")
751+ expected = """
752+ <version id="v1.1" status="CURRENT"
753+ updated="2011-01-21T11:33:21Z"
754+ xmlns="%s"
755+ xmlns:atom="http://www.w3.org/2005/Atom">
756+
757+ <media-types>
758+ <media-type base="application/xml"
759+ type="application/vnd.openstack.compute-v1.1+xml"/>
760+ <media-type base="application/json"
761+ type="application/vnd.openstack.compute-v1.1+json"/>
762+ </media-types>
763+
764+ <atom:link href="http://localhost/v1.1/"
765+ rel="self"/>
766+
767+ <atom:link href="http://docs.rackspacecloud.com/servers/
768+ api/v1.1/cs-devguide-20110125.pdf"
769+ rel="describedby"
770+ type="application/pdf"/>
771+
772+ <atom:link href="http://docs.rackspacecloud.com/servers/
773+ api/v1.1/application.wadl"
774+ rel="describedby"
775+ type="application/vnd.sun.wadl+xml"/>
776+ </version>""".replace(" ", "").replace("\n", "") % wsgi.XMLNS_V11
777+
778+ actual = res.body.replace(" ", "").replace("\n", "")
779+ self.assertEqual(expected, actual)
780+
781 def test_get_version_list_xml(self):
782 req = webob.Request.blank('/')
783 req.accept = "application/xml"
784@@ -71,18 +319,94 @@
785 self.assertEqual(res.status_int, 200)
786 self.assertEqual(res.content_type, "application/xml")
787
788- expected = """<versions>
789- <version id="v1.1" status="CURRENT" updated="2011-07-18T11:30:00Z">
790+ expected = """
791+ <versions xmlns="%s" xmlns:atom="%s">
792+ <version id="v1.1" status="CURRENT" updated="2011-01-21T11:33:21Z">
793 <atom:link href="http://localhost/v1.1/" rel="self"/>
794 </version>
795 <version id="v1.0" status="DEPRECATED"
796- updated="2010-10-09T11:30:00Z">
797+ updated="2011-01-21T11:33:21Z">
798 <atom:link href="http://localhost/v1.0/" rel="self"/>
799 </version>
800- </versions>""".replace(" ", "").replace("\n", "")
801-
802- actual = res.body.replace(" ", "").replace("\n", "")
803-
804+ </versions>""".replace(" ", "").replace("\n", "") % (wsgi.XMLNS_V11,
805+ wsgi.XMLNS_ATOM)
806+
807+ actual = res.body.replace(" ", "").replace("\n", "")
808+
809+ self.assertEqual(expected, actual)
810+
811+ def test_get_version_1_0_detail_atom(self):
812+ req = webob.Request.blank('/v1.0/')
813+ req.accept = "application/atom+xml"
814+ res = req.get_response(fakes.wsgi_app())
815+ self.assertEqual(res.status_int, 200)
816+ self.assertEqual("application/atom+xml", res.content_type)
817+ expected = """
818+ <feed xmlns="http://www.w3.org/2005/Atom">
819+ <title type="text">About This Version</title>
820+ <updated>2011-01-21T11:33:21Z</updated>
821+ <id>http://localhost/v1.0/</id>
822+ <author>
823+ <name>Rackspace</name>
824+ <uri>http://www.rackspace.com/</uri>
825+ </author>
826+ <link href="http://localhost/v1.0/" rel="self"/>
827+ <entry>
828+ <id>http://localhost/v1.0/</id>
829+ <title type="text">Version v1.0</title>
830+ <updated>2011-01-21T11:33:21Z</updated>
831+ <link href="http://localhost/v1.0/"
832+ rel="self"/>
833+ <link href="http://docs.rackspacecloud.com/servers/
834+ api/v1.0/cs-devguide-20110125.pdf"
835+ rel="describedby" type="application/pdf"/>
836+ <link href="http://docs.rackspacecloud.com/servers/
837+ api/v1.0/application.wadl"
838+ rel="describedby" type="application/vnd.sun.wadl+xml"/>
839+ <content type="text">
840+ Version v1.0 DEPRECATED (2011-01-21T11:33:21Z)
841+ </content>
842+ </entry>
843+ </feed>""".replace(" ", "").replace("\n", "")
844+
845+ actual = res.body.replace(" ", "").replace("\n", "")
846+ self.assertEqual(expected, actual)
847+
848+ def test_get_version_1_1_detail_atom(self):
849+ req = webob.Request.blank('/v1.1/')
850+ req.accept = "application/atom+xml"
851+ res = req.get_response(fakes.wsgi_app())
852+ self.assertEqual(res.status_int, 200)
853+ self.assertEqual("application/atom+xml", res.content_type)
854+ expected = """
855+ <feed xmlns="http://www.w3.org/2005/Atom">
856+ <title type="text">About This Version</title>
857+ <updated>2011-01-21T11:33:21Z</updated>
858+ <id>http://localhost/v1.1/</id>
859+ <author>
860+ <name>Rackspace</name>
861+ <uri>http://www.rackspace.com/</uri>
862+ </author>
863+ <link href="http://localhost/v1.1/" rel="self"/>
864+ <entry>
865+ <id>http://localhost/v1.1/</id>
866+ <title type="text">Version v1.1</title>
867+ <updated>2011-01-21T11:33:21Z</updated>
868+ <link href="http://localhost/v1.1/"
869+ rel="self"/>
870+ <link href="http://docs.rackspacecloud.com/servers/
871+ api/v1.1/cs-devguide-20110125.pdf"
872+ rel="describedby" type="application/pdf"/>
873+ <link href="http://docs.rackspacecloud.com/servers/
874+ api/v1.1/application.wadl"
875+ rel="describedby" type="application/vnd.sun.wadl+xml"/>
876+ <content type="text">
877+ Version v1.1 CURRENT (2011-01-21T11:33:21Z)
878+ </content>
879+ </entry>
880+ </feed>""".replace(" ", "").replace("\n", "")
881+
882+ actual = res.body.replace(" ", "").replace("\n", "")
883 self.assertEqual(expected, actual)
884
885 def test_get_version_list_atom(self):
886@@ -95,7 +419,7 @@
887 expected = """
888 <feed xmlns="http://www.w3.org/2005/Atom">
889 <title type="text">Available API Versions</title>
890- <updated>2011-07-18T11:30:00Z</updated>
891+ <updated>2011-01-21T11:33:21Z</updated>
892 <id>http://localhost/</id>
893 <author>
894 <name>Rackspace</name>
895@@ -105,19 +429,19 @@
896 <entry>
897 <id>http://localhost/v1.1/</id>
898 <title type="text">Version v1.1</title>
899- <updated>2011-07-18T11:30:00Z</updated>
900+ <updated>2011-01-21T11:33:21Z</updated>
901 <link href="http://localhost/v1.1/" rel="self"/>
902 <content type="text">
903- Version v1.1 CURRENT (2011-07-18T11:30:00Z)
904+ Version v1.1 CURRENT (2011-01-21T11:33:21Z)
905 </content>
906 </entry>
907 <entry>
908 <id>http://localhost/v1.0/</id>
909 <title type="text">Version v1.0</title>
910- <updated>2010-10-09T11:30:00Z</updated>
911+ <updated>2011-01-21T11:33:21Z</updated>
912 <link href="http://localhost/v1.0/" rel="self"/>
913 <content type="text">
914- Version v1.0 DEPRECATED (2010-10-09T11:30:00Z)
915+ Version v1.0 DEPRECATED (2011-01-21T11:33:21Z)
916 </content>
917 </entry>
918 </feed>
919@@ -127,28 +451,184 @@
920
921 self.assertEqual(expected, actual)
922
923+ def test_multi_choice_image(self):
924+ req = webob.Request.blank('/images/1')
925+ req.accept = "application/json"
926+ res = req.get_response(fakes.wsgi_app())
927+ self.assertEqual(res.status_int, 300)
928+ self.assertEqual(res.content_type, "application/json")
929+
930+ expected = {
931+ "choices": [
932+ {
933+ "id": "v1.1",
934+ "status": "CURRENT",
935+ "links": [
936+ {
937+ "href": "http://localhost/v1.1/images/1",
938+ "rel": "self",
939+ },
940+ ],
941+ "media-types": [
942+ {
943+ "base": "application/xml",
944+ "type": "application/vnd.openstack.compute-v1.1+xml"
945+ },
946+ {
947+ "base": "application/json",
948+ "type": "application/vnd.openstack.compute-v1.1+json"
949+ },
950+ ],
951+ },
952+ {
953+ "id": "v1.0",
954+ "status": "DEPRECATED",
955+ "links": [
956+ {
957+ "href": "http://localhost/v1.0/images/1",
958+ "rel": "self",
959+ },
960+ ],
961+ "media-types": [
962+ {
963+ "base": "application/xml",
964+ "type": "application/vnd.openstack.compute-v1.0+xml"
965+ },
966+ {
967+ "base": "application/json",
968+ "type": "application/vnd.openstack.compute-v1.0+json"
969+ },
970+ ],
971+ },
972+ ], }
973+
974+ self.assertDictMatch(expected, json.loads(res.body))
975+
976+ def test_multi_choice_image_xml(self):
977+ req = webob.Request.blank('/images/1')
978+ req.accept = "application/xml"
979+ res = req.get_response(fakes.wsgi_app())
980+ self.assertEqual(res.status_int, 300)
981+ self.assertEqual(res.content_type, "application/xml")
982+
983+ expected = """
984+ <choices xmlns="%s" xmlns:atom="%s">
985+ <version id="v1.1" status="CURRENT">
986+ <media-types>
987+ <media-type base="application/xml"
988+ type="application/vnd.openstack.compute-v1.1+xml"/>
989+ <media-type base="application/json"
990+ type="application/vnd.openstack.compute-v1.1+json"/>
991+ </media-types>
992+ <atom:link href="http://localhost/v1.1/images/1" rel="self"/>
993+ </version>
994+ <version id="v1.0" status="DEPRECATED">
995+ <media-types>
996+ <media-type base="application/xml"
997+ type="application/vnd.openstack.compute-v1.0+xml"/>
998+ <media-type base="application/json"
999+ type="application/vnd.openstack.compute-v1.0+json"/>
1000+ </media-types>
1001+ <atom:link href="http://localhost/v1.0/images/1" rel="self"/>
1002+ </version>
1003+ </choices>""".replace(" ", "").replace("\n", "") % (wsgi.XMLNS_V11,
1004+ wsgi.XMLNS_ATOM)
1005+
1006+ def test_multi_choice_server_atom(self):
1007+ """
1008+ Make sure multi choice responses do not have content-type
1009+ application/atom+xml (should use default of json)
1010+ """
1011+ req = webob.Request.blank('/servers/2')
1012+ req.accept = "application/atom+xml"
1013+ res = req.get_response(fakes.wsgi_app())
1014+ self.assertEqual(res.status_int, 300)
1015+ self.assertEqual(res.content_type, "application/json")
1016+
1017+ def test_multi_choice_server(self):
1018+ req = webob.Request.blank('/servers/2')
1019+ req.accept = "application/json"
1020+ res = req.get_response(fakes.wsgi_app())
1021+ self.assertEqual(res.status_int, 300)
1022+ self.assertEqual(res.content_type, "application/json")
1023+
1024+ expected = {
1025+ "choices": [
1026+ {
1027+ "id": "v1.1",
1028+ "status": "CURRENT",
1029+ "links": [
1030+ {
1031+ "href": "http://localhost/v1.1/servers/2",
1032+ "rel": "self",
1033+ },
1034+ ],
1035+ "media-types": [
1036+ {
1037+ "base": "application/xml",
1038+ "type": "application/vnd.openstack.compute-v1.1+xml"
1039+ },
1040+ {
1041+ "base": "application/json",
1042+ "type": "application/vnd.openstack.compute-v1.1+json"
1043+ },
1044+ ],
1045+ },
1046+ {
1047+ "id": "v1.0",
1048+ "status": "DEPRECATED",
1049+ "links": [
1050+ {
1051+ "href": "http://localhost/v1.0/servers/2",
1052+ "rel": "self",
1053+ },
1054+ ],
1055+ "media-types": [
1056+ {
1057+ "base": "application/xml",
1058+ "type": "application/vnd.openstack.compute-v1.0+xml"
1059+ },
1060+ {
1061+ "base": "application/json",
1062+ "type": "application/vnd.openstack.compute-v1.0+json"
1063+ },
1064+ ],
1065+ },
1066+ ], }
1067+
1068+ self.assertDictMatch(expected, json.loads(res.body))
1069+
1070+
1071+class VersionsViewBuilderTests(test.TestCase):
1072 def test_view_builder(self):
1073 base_url = "http://example.org/"
1074
1075 version_data = {
1076- "id": "3.2.1",
1077- "status": "CURRENT",
1078- "updated": "2011-07-18T11:30:00Z"}
1079+ "v3.2.1": {
1080+ "id": "3.2.1",
1081+ "status": "CURRENT",
1082+ "updated": "2011-07-18T11:30:00Z",
1083+ }
1084+ }
1085
1086 expected = {
1087- "id": "3.2.1",
1088- "status": "CURRENT",
1089- "updated": "2011-07-18T11:30:00Z",
1090- "links": [
1091+ "versions": [
1092 {
1093- "rel": "self",
1094- "href": "http://example.org/3.2.1/",
1095- },
1096- ],
1097+ "id": "3.2.1",
1098+ "status": "CURRENT",
1099+ "updated": "2011-07-18T11:30:00Z",
1100+ "links": [
1101+ {
1102+ "rel": "self",
1103+ "href": "http://example.org/3.2.1/",
1104+ },
1105+ ],
1106+ }
1107+ ]
1108 }
1109
1110 builder = views.versions.ViewBuilder(base_url)
1111- output = builder.build(version_data)
1112+ output = builder.build_versions(version_data)
1113
1114 self.assertEqual(output, expected)
1115
1116@@ -163,7 +643,9 @@
1117
1118 self.assertEqual(actual, expected)
1119
1120- def test_xml_serializer(self):
1121+
1122+class VersionsSerializerTests(test.TestCase):
1123+ def test_versions_list_xml_serializer(self):
1124 versions_data = {
1125 'versions': [
1126 {
1127@@ -180,20 +662,137 @@
1128 ]
1129 }
1130
1131- expected = """
1132- <versions>
1133- <version id="2.7.1" status="DEPRECATED"
1134- updated="2011-07-18T11:30:00Z">
1135- <atom:link href="http://test/2.7.1" rel="self"/>
1136- </version>
1137- </versions>""".replace(" ", "").replace("\n", "")
1138-
1139- serializer = versions.VersionsXMLSerializer()
1140- response = serializer.default(versions_data)
1141- response = response.replace(" ", "").replace("\n", "")
1142- self.assertEqual(expected, response)
1143-
1144- def test_atom_serializer(self):
1145+ serializer = versions.VersionsXMLSerializer()
1146+ response = serializer.index(versions_data)
1147+
1148+ root = xml.etree.ElementTree.XML(response)
1149+ self.assertEqual(root.tag.split('}')[1], "versions")
1150+ self.assertEqual(root.tag.split('}')[0].strip('{'), wsgi.XMLNS_V11)
1151+ version = list(root)[0]
1152+ self.assertEqual(version.tag.split('}')[1], "version")
1153+ self.assertEqual(version.get('id'),
1154+ versions_data['versions'][0]['id'])
1155+ self.assertEqual(version.get('status'),
1156+ versions_data['versions'][0]['status'])
1157+
1158+ link = list(version)[0]
1159+
1160+ self.assertEqual(link.tag.split('}')[1], "link")
1161+ self.assertEqual(link.tag.split('}')[0].strip('{'), wsgi.XMLNS_ATOM)
1162+ for key, val in versions_data['versions'][0]['links'][0].items():
1163+ self.assertEqual(link.get(key), val)
1164+
1165+ def test_versions_multi_xml_serializer(self):
1166+ versions_data = {
1167+ 'choices': [
1168+ {
1169+ "id": "2.7.1",
1170+ "updated": "2011-07-18T11:30:00Z",
1171+ "status": "DEPRECATED",
1172+ "media-types": VERSIONS['v1.1']['media-types'],
1173+ "links": [
1174+ {
1175+ "rel": "self",
1176+ "href": "http://test/2.7.1/images",
1177+ },
1178+ ],
1179+ },
1180+ ]
1181+ }
1182+
1183+ serializer = versions.VersionsXMLSerializer()
1184+ response = serializer.multi(versions_data)
1185+
1186+ root = xml.etree.ElementTree.XML(response)
1187+ self.assertEqual(root.tag.split('}')[1], "choices")
1188+ self.assertEqual(root.tag.split('}')[0].strip('{'), wsgi.XMLNS_V11)
1189+ version = list(root)[0]
1190+ self.assertEqual(version.tag.split('}')[1], "version")
1191+ self.assertEqual(version.get('id'), versions_data['choices'][0]['id'])
1192+ self.assertEqual(version.get('status'),
1193+ versions_data['choices'][0]['status'])
1194+
1195+ media_types = list(version)[0]
1196+ media_type_nodes = list(media_types)
1197+ self.assertEqual(media_types.tag.split('}')[1], "media-types")
1198+
1199+ set_types = versions_data['choices'][0]['media-types']
1200+ for i, type in enumerate(set_types):
1201+ node = media_type_nodes[i]
1202+ self.assertEqual(node.tag.split('}')[1], "media-type")
1203+ for key, val in set_types[i].items():
1204+ self.assertEqual(node.get(key), val)
1205+
1206+ link = list(version)[1]
1207+
1208+ self.assertEqual(link.tag.split('}')[1], "link")
1209+ self.assertEqual(link.tag.split('}')[0].strip('{'), wsgi.XMLNS_ATOM)
1210+ for key, val in versions_data['choices'][0]['links'][0].items():
1211+ self.assertEqual(link.get(key), val)
1212+
1213+ def test_version_detail_xml_serializer(self):
1214+ version_data = {
1215+ "version": {
1216+ "id": "v1.0",
1217+ "status": "CURRENT",
1218+ "updated": "2011-01-21T11:33:21Z",
1219+ "links": [
1220+ {
1221+ "rel": "self",
1222+ "href": "http://localhost/v1.0/"
1223+ },
1224+ {
1225+ "rel": "describedby",
1226+ "type": "application/pdf",
1227+ "href": "http://docs.rackspacecloud.com/"
1228+ "servers/api/v1.0/cs-devguide-20110125.pdf"
1229+ },
1230+ {
1231+ "rel": "describedby",
1232+ "type": "application/vnd.sun.wadl+xml",
1233+ "href": "http://docs.rackspacecloud.com/"
1234+ "servers/api/v1.0/application.wadl"
1235+ },
1236+ ],
1237+ "media-types": [
1238+ {
1239+ "base": "application/xml",
1240+ "type": "application/vnd.openstack.compute-v1.0+xml"
1241+ },
1242+ {
1243+ "base": "application/json",
1244+ "type": "application/vnd.openstack.compute-v1.0+json"
1245+ }
1246+ ],
1247+ },
1248+ }
1249+
1250+ serializer = versions.VersionsXMLSerializer()
1251+ response = serializer.show(version_data)
1252+
1253+ root = xml.etree.ElementTree.XML(response)
1254+ self.assertEqual(root.tag.split('}')[1], "version")
1255+ self.assertEqual(root.tag.split('}')[0].strip('{'), wsgi.XMLNS_V11)
1256+
1257+ children = list(root)
1258+ media_types = children[0]
1259+ media_type_nodes = list(media_types)
1260+ links = (children[1], children[2], children[3])
1261+
1262+ self.assertEqual(media_types.tag.split('}')[1], 'media-types')
1263+ for i, media_node in enumerate(media_type_nodes):
1264+ self.assertEqual(media_node.tag.split('}')[1], 'media-type')
1265+ for key, val in version_data['version']['media-types'][i].items():
1266+ self.assertEqual(val, media_node.get(key))
1267+
1268+ for i, link in enumerate(links):
1269+ self.assertEqual(link.tag.split('}')[0].strip('{'),
1270+ 'http://www.w3.org/2005/Atom')
1271+ self.assertEqual(link.tag.split('}')[1], 'link')
1272+ for key, val in version_data['version']['links'][i].items():
1273+ self.assertEqual(val, link.get(key))
1274+
1275+ def test_versions_list_atom_serializer(self):
1276 versions_data = {
1277 'versions': [
1278 {
1279@@ -210,45 +809,158 @@
1280 ]
1281 }
1282
1283- expected = """
1284- <feed xmlns="http://www.w3.org/2005/Atom">
1285- <title type="text">
1286- Available API Versions
1287- </title>
1288- <updated>
1289- 2011-07-20T11:40:00Z
1290- </updated>
1291- <id>
1292- http://test/
1293- </id>
1294- <author>
1295- <name>
1296- Rackspace
1297- </name>
1298- <uri>
1299- http://www.rackspace.com/
1300- </uri>
1301- </author>
1302- <link href="http://test/" rel="self"/>
1303- <entry>
1304- <id>
1305- http://test/2.9.8
1306- </id>
1307- <title type="text">
1308- Version 2.9.8
1309- </title>
1310- <updated>
1311- 2011-07-20T11:40:00Z
1312- </updated>
1313- <link href="http://test/2.9.8" rel="self"/>
1314- <content type="text">
1315- Version 2.9.8 CURRENT (2011-07-20T11:40:00Z)
1316- </content>
1317- </entry>
1318- </feed>""".replace(" ", "").replace("\n", "")
1319-
1320- serializer = versions.VersionsAtomSerializer()
1321- response = serializer.default(versions_data)
1322- print response
1323- response = response.replace(" ", "").replace("\n", "")
1324- self.assertEqual(expected, response)
1325+ serializer = versions.VersionsAtomSerializer()
1326+ response = serializer.index(versions_data)
1327+
1328+ root = xml.etree.ElementTree.XML(response)
1329+ self.assertEqual(root.tag.split('}')[1], "feed")
1330+ self.assertEqual(root.tag.split('}')[0].strip('{'),
1331+ "http://www.w3.org/2005/Atom")
1332+
1333+ children = list(root)
1334+ title = children[0]
1335+ updated = children[1]
1336+ id = children[2]
1337+ author = children[3]
1338+ link = children[4]
1339+ entry = children[5]
1340+
1341+ self.assertEqual(title.tag.split('}')[1], 'title')
1342+ self.assertEqual(title.text, 'Available API Versions')
1343+ self.assertEqual(updated.tag.split('}')[1], 'updated')
1344+ self.assertEqual(updated.text, '2011-07-20T11:40:00Z')
1345+ self.assertEqual(id.tag.split('}')[1], 'id')
1346+ self.assertEqual(id.text, 'http://test/')
1347+
1348+ self.assertEqual(author.tag.split('}')[1], 'author')
1349+ author_name = list(author)[0]
1350+ author_uri = list(author)[1]
1351+ self.assertEqual(author_name.tag.split('}')[1], 'name')
1352+ self.assertEqual(author_name.text, 'Rackspace')
1353+ self.assertEqual(author_uri.tag.split('}')[1], 'uri')
1354+ self.assertEqual(author_uri.text, 'http://www.rackspace.com/')
1355+
1356+ self.assertEqual(link.get('href'), 'http://test/')
1357+ self.assertEqual(link.get('rel'), 'self')
1358+
1359+ self.assertEqual(entry.tag.split('}')[1], 'entry')
1360+ entry_children = list(entry)
1361+ entry_id = entry_children[0]
1362+ entry_title = entry_children[1]
1363+ entry_updated = entry_children[2]
1364+ entry_link = entry_children[3]
1365+ entry_content = entry_children[4]
1366+ self.assertEqual(entry_id.tag.split('}')[1], "id")
1367+ self.assertEqual(entry_id.text, "http://test/2.9.8")
1368+ self.assertEqual(entry_title.tag.split('}')[1], "title")
1369+ self.assertEqual(entry_title.get('type'), "text")
1370+ self.assertEqual(entry_title.text, "Version 2.9.8")
1371+ self.assertEqual(entry_updated.tag.split('}')[1], "updated")
1372+ self.assertEqual(entry_updated.text, "2011-07-20T11:40:00Z")
1373+ self.assertEqual(entry_link.tag.split('}')[1], "link")
1374+ self.assertEqual(entry_link.get('href'), "http://test/2.9.8")
1375+ self.assertEqual(entry_link.get('rel'), "self")
1376+ self.assertEqual(entry_content.tag.split('}')[1], "content")
1377+ self.assertEqual(entry_content.get('type'), "text")
1378+ self.assertEqual(entry_content.text,
1379+ "Version 2.9.8 CURRENT (2011-07-20T11:40:00Z)")
1380+
1381+ def test_version_detail_atom_serializer(self):
1382+ versions_data = {
1383+ "version": {
1384+ "id": "v1.1",
1385+ "status": "CURRENT",
1386+ "updated": "2011-01-21T11:33:21Z",
1387+ "links": [
1388+ {
1389+ "rel": "self",
1390+ "href": "http://localhost/v1.1/"
1391+ },
1392+ {
1393+ "rel": "describedby",
1394+ "type": "application/pdf",
1395+ "href": "http://docs.rackspacecloud.com/"
1396+ "servers/api/v1.1/cs-devguide-20110125.pdf"
1397+ },
1398+ {
1399+ "rel": "describedby",
1400+ "type": "application/vnd.sun.wadl+xml",
1401+ "href": "http://docs.rackspacecloud.com/"
1402+ "servers/api/v1.1/application.wadl"
1403+ },
1404+ ],
1405+ "media-types": [
1406+ {
1407+ "base": "application/xml",
1408+ "type": "application/vnd.openstack.compute-v1.1+xml"
1409+ },
1410+ {
1411+ "base": "application/json",
1412+ "type": "application/vnd.openstack.compute-v1.1+json"
1413+ }
1414+ ],
1415+ },
1416+ }
1417+
1418+ serializer = versions.VersionsAtomSerializer()
1419+ response = serializer.show(versions_data)
1420+
1421+ root = xml.etree.ElementTree.XML(response)
1422+ self.assertEqual(root.tag.split('}')[1], "feed")
1423+ self.assertEqual(root.tag.split('}')[0].strip('{'),
1424+ "http://www.w3.org/2005/Atom")
1425+
1426+ children = list(root)
1427+ title = children[0]
1428+ updated = children[1]
1429+ id = children[2]
1430+ author = children[3]
1431+ link = children[4]
1432+ entry = children[5]
1433+
1434+ self.assertEqual(root.tag.split('}')[1], 'feed')
1435+ self.assertEqual(title.tag.split('}')[1], 'title')
1436+ self.assertEqual(title.text, 'About This Version')
1437+ self.assertEqual(updated.tag.split('}')[1], 'updated')
1438+ self.assertEqual(updated.text, '2011-01-21T11:33:21Z')
1439+ self.assertEqual(id.tag.split('}')[1], 'id')
1440+ self.assertEqual(id.text, 'http://localhost/v1.1/')
1441+
1442+ self.assertEqual(author.tag.split('}')[1], 'author')
1443+ author_name = list(author)[0]
1444+ author_uri = list(author)[1]
1445+ self.assertEqual(author_name.tag.split('}')[1], 'name')
1446+ self.assertEqual(author_name.text, 'Rackspace')
1447+ self.assertEqual(author_uri.tag.split('}')[1], 'uri')
1448+ self.assertEqual(author_uri.text, 'http://www.rackspace.com/')
1449+
1450+ self.assertEqual(link.get('href'),
1451+ 'http://localhost/v1.1/')
1452+ self.assertEqual(link.get('rel'), 'self')
1453+
1454+ self.assertEqual(entry.tag.split('}')[1], 'entry')
1455+ entry_children = list(entry)
1456+ entry_id = entry_children[0]
1457+ entry_title = entry_children[1]
1458+ entry_updated = entry_children[2]
1459+ entry_links = (entry_children[3], entry_children[4], entry_children[5])
1460+ entry_content = entry_children[6]
1461+
1462+ self.assertEqual(entry_id.tag.split('}')[1], "id")
1463+ self.assertEqual(entry_id.text,
1464+ "http://localhost/v1.1/")
1465+ self.assertEqual(entry_title.tag.split('}')[1], "title")
1466+ self.assertEqual(entry_title.get('type'), "text")
1467+ self.assertEqual(entry_title.text, "Version v1.1")
1468+ self.assertEqual(entry_updated.tag.split('}')[1], "updated")
1469+ self.assertEqual(entry_updated.text, "2011-01-21T11:33:21Z")
1470+
1471+ for i, link in enumerate(versions_data["version"]["links"]):
1472+ self.assertEqual(entry_links[i].tag.split('}')[1], "link")
1473+ for key, val in versions_data["version"]["links"][i].items():
1474+ self.assertEqual(entry_links[i].get(key), val)
1475+
1476+ self.assertEqual(entry_content.tag.split('}')[1], "content")
1477+ self.assertEqual(entry_content.get('type'), "text")
1478+ self.assertEqual(entry_content.text,
1479+ "Version v1.1 CURRENT (2011-01-21T11:33:21Z)")