~jcsackett/charmworld/bac-tag-constraints

« back to all changes in this revision

Viewing changes to charmworld/views/tests/test_api.py

  • Committer: Curtis Hovey
  • Date: 2013-08-20 22:09:45 UTC
  • mfrom: (352.1.17 api3-search)
  • Revision ID: curtis@canonical.com-20130820220945-8fjbm6afsp7qnymm
Introduce API3.search.

Show diffs side-by-side

added added

removed removed

Lines of Context:
10
10
import hashlib
11
11
import json
12
12
 
 
13
from mock import patch
 
14
 
13
15
from pyramid.httpexceptions import HTTPNotFound
14
16
from webob.etag import ETagMatcher
15
17
from webob.multidict import MultiDict
30
32
    json_response,
31
33
)
32
34
from charmworld.search import (
 
35
    BUNDLE,
 
36
    CHARM,
33
37
    charm_exact_fields,
34
38
    charm_free_text_fields,
35
39
)
167
171
    def related_weightless(cls, charm):
168
172
        return cls.weightless(cls.api_class._format_related(charm, 0))
169
173
 
 
174
    def make_indexed_bundle(self, *args, **kwargs):
 
175
        bundle, ignore = factory.makeBundle(self.db, *args, **kwargs)
 
176
        bundle._representation['_id'] = bundle.id
 
177
        self.index_client.index_bundle(bundle._representation)
 
178
        return bundle
 
179
 
 
180
    def make_indexed_charm(self, *args, **kwargs):
 
181
        ignore, charm = factory.makeCharm(self.db, *args, **kwargs)
 
182
        self.index_client.index_charm(charm)
 
183
        return charm
 
184
 
170
185
 
171
186
class API3Mixin(APITestBase):
172
187
 
173
188
    api_class = API3
 
189
    search_endpoint = 'search'
174
190
 
175
191
 
176
192
class API2Mixin(APITestBase):
177
193
 
178
194
    api_class = API2
 
195
    search_endpoint = 'charms'
179
196
 
180
197
 
181
198
class TestAPICharms:
195
212
    def test_remote_friendly_headers(self):
196
213
        """The Juju-Gui needs to be able to query the api via diff origin."""
197
214
        self.index_client.wait_for_startup()
198
 
        resp = self.get_response('charms')
 
215
        resp = self.get_response(self.search_endpoint)
199
216
        self.assertIn(("Access-Control-Allow-Origin", "*"), resp.headerlist)
200
217
        self.assertIn(
201
218
            ("Access-Control-Allow-Headers", "X-Requested-With"),
202
219
            resp.headerlist)
203
220
 
204
221
    def get_charms(self, *args, **kwargs):
205
 
        return self.get_response('charms', *args, **kwargs).json_body['result']
 
222
        return self.get_response(
 
223
            self.search_endpoint, *args, **kwargs).json_body['result']
206
224
 
207
225
    def _test_filter(self, filter_str, make_charm=None):
208
226
        if make_charm is None:
278
296
        self.assertEqual([charmers], [charm for charm in result])
279
297
        result = self.get_charms(type_='environment')
280
298
        self.assertEqual([], [charm['owner'] for charm in result])
281
 
        response = self.get_response('charms', type_='foo')
 
299
        response = self.get_response(
 
300
            self.search_endpoint, type_='foo')
282
301
        self.assertEqual(406, response.status_code)
283
302
        self.assertEqual({'type': 'unsupported_value', 'parameter': 'type',
284
303
                          'value': 'foo'}, response.json_body)
300
319
 
301
320
    def test_charms_accepts_provider(self):
302
321
        """Provider is accepted (but ignored)."""
303
 
        response = self.get_response('charms', provider='asdf')
 
322
        response = self.get_response(self.search_endpoint, provider='asdf')
304
323
        self.assertEqual(200, response.status_code)
305
324
 
306
325
    def test_charms_accepts_scope(self):
307
326
        """Scope is accepted (but ignored)."""
308
 
        response = self.get_response('charms', scope='asdf')
 
327
        response = self.get_response(self.search_endpoint, scope='asdf')
309
328
        self.assertEqual(200, response.status_code)
310
329
 
311
330
    def test_charms_text_search(self):
312
331
        abc_charm = self.makeCharm(summary='abc def')[1]
313
332
        self.makeCharm(summary='ghi jkl')[1]
314
 
        response = self.get_response('charms', text='def')
 
333
        response = self.get_response(self.search_endpoint, text='def')
315
334
        self.assertEqual(200, response.status_code)
316
335
        charms = response.json_body['result']
317
336
        self.assertEqual(1, len(charms))
330
349
 
331
350
    def test_charms_limit(self):
332
351
        """Limit provides a maximum for charm numbers."""
333
 
        response = self.get_response('charms', limit='6')
 
352
        response = self.get_response(self.search_endpoint, limit='6')
334
353
        self.assertEqual(0, len(response.json_body['result']))
335
354
        self.makeCharm()
336
355
        self.makeCharm(owner='jrandom')
337
 
        response = self.get_response('charms', limit='6')
 
356
        response = self.get_response(self.search_endpoint, limit='6')
338
357
        self.assertEqual(2, len(response.json_body['result']))
339
 
        response = self.get_response('charms', limit='1')
 
358
        response = self.get_response(self.search_endpoint, limit='1')
340
359
        self.assertEqual(1, len(response.json_body['result']))
341
 
        response = self.get_response('charms', limit='0')
 
360
        response = self.get_response(self.search_endpoint, limit='0')
342
361
        self.assertEqual(0, len(response.json_body['result']))
343
 
        response = self.get_response('charms', limit='-1')
 
362
        response = self.get_response(self.search_endpoint, limit='-1')
344
363
        self.assertEqual(406, response.status_code)
345
364
        self.assertEqual({
346
365
                         'type': 'negative_value',
362
381
            dt = datetime.utcfromtimestamp(0)
363
382
            last_featured_charm = self.makeCharm(
364
383
                name='featured%d' % num, date_created=dt, is_featured=True)[1]
365
 
        res = self.get_response('charms', remainder='/interesting')
 
384
        res = self.get_response(self.search_endpoint, remainder='/interesting')
366
385
        data = res.json_body
367
386
        self.assertIn('result', data)
368
387
        self.assertIn('new', data['result'])
389
408
 
390
409
    def test_charms_accepts_categories(self):
391
410
        """Categories are accepted (but ignored)."""
392
 
        response = self.get_response('charms', categories='asdf')
 
411
        response = self.get_response(self.search_endpoint, categories='asdf')
393
412
        self.assertEqual(200, response.status_code)
394
413
 
395
414
    provides2 = {'cookie-factory': {
410
429
 
411
430
    def test_charms_denies_category(self):
412
431
        """Category is not accepted."""
413
 
        response = self.get_response('charms', category='asdf')
 
432
        response = self.get_response(self.search_endpoint, category='asdf')
414
433
        self.assertEqual(406, response.status_code)
415
434
 
416
435
    def test_charms(self):
448
467
                                     date_created=datetime(1996, 9, 28,
449
468
                                                           11, 9, 8),
450
469
                                     categories=['cache-proxy'])
451
 
        response = self.get_response('charms')
 
470
        response = self.get_response(self.search_endpoint)
452
471
        self.assertEqual(200, response.status_code)
453
472
        self.assertEqual('application/json', response.content_type)
454
473
        result = response.json_body['result']
463
482
    def test_charms_path_notfound(self):
464
483
        """Charms endpoint raises HTTPNotFound for unsupported paths."""
465
484
        with self.assertRaises(HTTPNotFound):
466
 
            self.get_response('charms', 'precise/bong-2')
 
485
            self.get_response(self.search_endpoint, 'precise/bong-2')
467
486
 
468
487
    def test_autocomplete_matches_on_names(self):
469
488
        matching_charm = Charm(self.makeCharm(name='foo')[1])
477
496
            differing_charm[field] = 'foo'
478
497
        self.index_client.index_charm(differing_charm)
479
498
        response = self.get_response(
480
 
            'charms', autocomplete='true', text='foo')
 
499
            self.search_endpoint, autocomplete='true', text='foo')
481
500
        ids = [result['charm']['id'] for result in
482
501
               response.json_body['result']]
483
502
        self.assertEqual([matching_id], ids)
555
574
                'is_subordinate': False,
556
575
            },
557
576
            'metadata': {
 
577
                'doctype': CHARM
558
578
            }
559
579
        }
560
580
 
612
632
                'is_subordinate': True,
613
633
            },
614
634
            'metadata': {
 
635
                'doctype': CHARM
615
636
            }
616
637
        }
617
638
 
659
680
            date_created=older, name='provides-feat', provides={
660
681
                'asdf': {'interface': 'feat-r'}
661
682
            })[1]
662
 
        data = self.get_response('charms', remainder='/interesting').json_body
 
683
        data = self.get_response(
 
684
            self.search_endpoint, remainder='/interesting').json_body
663
685
        self.assertNotIn('related', data['result']['new'][0]['metadata'])
664
686
        self.assertNotIn('related', data['result']['popular'][0]['metadata'])
665
687
        self.assertNotIn('related', data['result']['featured'][0]['metadata'])
669
691
        # first_change and last_change. The view is nevertheless properly
670
692
        # rendered.
671
693
        self.makeCharm(empty_branch=True)
672
 
        result = json.loads(self.get_response('charms').body)['result'][0]
 
694
        result = json.loads(
 
695
            self.get_response(self.search_endpoint).body)['result'][0]
673
696
        # But the returned data does contain only dummy revision related
674
697
        # data.
675
698
        self.assertEqual('0', result['charm']['code_source']['revision'])
687
710
class TestAPIBundles:
688
711
 
689
712
    def makeBundle(self, *args, **kwargs):
690
 
        bundle, _ = factory.makeBundle(self.db, *args, **kwargs)
 
713
        bundle, ignore = factory.makeBundle(self.db, *args, **kwargs)
691
714
        return bundle
692
715
 
693
716
    def test_default_bundle(self):
802
825
 
803
826
    def test_unsupported_parameter(self):
804
827
        """Unsupported parameters cause a 406."""
805
 
        response = self.get_response('charms', kermit='6')
 
828
        response = self.get_response(self.search_endpoint, kermit='6')
806
829
        self.assertEqual(406, response.status_code)
807
830
        self.assertEqual({
808
831
                         'type': 'parameter_not_supported',
1219
1242
 
1220
1243
        # OPTIONS should return as fast as possible-- it should not search.
1221
1244
        self.index_client = FailIndexClient
1222
 
        request = self.make_request('charms', '')
 
1245
        request = self.make_request(self.search_endpoint, '')
1223
1246
        request.method = 'OPTIONS'
1224
1247
        response = self.api_class(request)()
1225
1248
        # Only the headers matter for a CORS preflight response.
1265
1288
        self.assertEqual(52, formatted['downloads_in_past_30_days'])
1266
1289
 
1267
1290
    def test_format_related_commits_in_past_30_days(self):
1268
 
        now = datetime(2013, 06, 17)
 
1291
        now = datetime(2013, 6, 17)
1269
1292
        charm = factory.get_charm_json(
1270
1293
            changes=[factory.makeChange(created=now.date())
1271
1294
                     for x in range(27)])
1272
1295
        formatted = self.api_class._format_related(charm, 0, _now=now)
1273
1296
        self.assertEqual(27, formatted['commits_in_past_30_days'])
1274
1297
        charm = factory.get_charm_json(
1275
 
            changes=[factory.makeChange(created=date(2013, 05, 18))
 
1298
            changes=[factory.makeChange(created=date(2013, 5, 18))
1276
1299
                     for x in range(32)] +
1277
 
                    [factory.makeChange(created=date(2013, 05, 17))])
 
1300
                    [factory.makeChange(created=date(2013, 5, 17))])
1278
1301
        formatted = self.api_class._format_related(
1279
1302
            charm, 0, _now=now)
1280
1303
        self.assertEqual(32, formatted['commits_in_past_30_days'])
1416
1439
class TestAPI3(TestAPI, API3Mixin):
1417
1440
    """Test API 3."""
1418
1441
 
 
1442
    def test_charms_path_notfound(self):
 
1443
        """The charms endpoint was removed."""
 
1444
        with self.assertRaises(HTTPNotFound):
 
1445
            self.get_response('charms', None)
 
1446
        with self.assertRaises(HTTPNotFound):
 
1447
            self.get_response('charms', 'precise/bong-2')
 
1448
 
 
1449
    def test_search_results_contain_charms_and_bundles(self):
 
1450
        self.use_index_client()
 
1451
        bundle = self.make_indexed_bundle(name='fan-stack-tic')
 
1452
        charm = self.make_indexed_charm()
 
1453
        result = self.get_response(self.search_endpoint).json_body['result']
 
1454
        self.assertEqual(2, len(result))
 
1455
        found_bundle = result[0]
 
1456
        self.assertEqual(BUNDLE, found_bundle['metadata']['doctype'])
 
1457
        self.assertEqual(bundle.name, found_bundle['bundle']['name'])
 
1458
        found_charm = result[1]
 
1459
        self.assertEqual(CHARM, found_charm['metadata']['doctype'])
 
1460
        self.assertEqual(charm['store_url'], found_charm['charm']['url'])
 
1461
 
1419
1462
 
1420
1463
class TestAPI2(TestAPI, API2Mixin):
1421
1464
    """Test API 2."""
 
1465
 
 
1466
    def test_charms_passes_doctype(self):
 
1467
        called = dict(kwargs=None)
 
1468
 
 
1469
        def callme():
 
1470
            def fake_search(*args, **kwargs):
 
1471
                called['kwargs'] = kwargs
 
1472
                return {'result': []}
 
1473
 
 
1474
            return fake_search
 
1475
 
 
1476
        with patch.object(self.api_class, '_search', new_callable=callme):
 
1477
            self.get_response(self.search_endpoint, limit=[5])
 
1478
            self.assertEqual(
 
1479
                {'doctype': CHARM, 'limit': [5]}, called['kwargs'])
 
1480
 
 
1481
    def test_charms_results_only_contain_charms(self):
 
1482
        self.use_index_client()
 
1483
        self.make_indexed_bundle()
 
1484
        charm = self.make_indexed_charm()
 
1485
        result = self.get_response(self.search_endpoint).json_body['result']
 
1486
        self.assertEqual(1, len(result))
 
1487
        self.assertEqual(charm['store_url'], result[0]['charm']['url'])