blob: a4cab58483b029a5e44bd0b8c009c526573f7925 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001#! /usr/bin/env python
2# ex:ts=4:sw=4:sts=4:et
3# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4#
5# BitBake Toaster Implementation
6#
7# Copyright (C) 2013-2015 Intel Corporation
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License version 2 as
11# published by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program; if not, write to the Free Software Foundation, Inc.,
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22"""Test cases for Toaster GUI and ReST."""
23
24from django.test import TestCase
Patrick Williamsf1e5d692016-03-30 15:21:19 -050025from django.test.client import RequestFactory
Patrick Williamsc124f4f2015-09-15 14:41:29 -050026from django.core.urlresolvers import reverse
27from django.utils import timezone
Patrick Williamsf1e5d692016-03-30 15:21:19 -050028
29from orm.models import Project, Release, BitbakeVersion, Package, LogMessage
Patrick Williamsd7e96312015-09-22 08:09:05 -050030from orm.models import ReleaseLayerSourcePriority, LayerSource, Layer, Build
Patrick Williamsf1e5d692016-03-30 15:21:19 -050031from orm.models import Layer_Version, Recipe, Machine, ProjectLayer, Target
32from orm.models import CustomImageRecipe, ProjectVariable
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050033from orm.models import Branch, CustomImagePackage
Patrick Williamsf1e5d692016-03-30 15:21:19 -050034
35import toastermain
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050036import inspect
37import toastergui
Patrick Williamsf1e5d692016-03-30 15:21:19 -050038
39from toastergui.tables import SoftwareRecipesTable
Patrick Williamsc124f4f2015-09-15 14:41:29 -050040import json
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050041from datetime import timedelta
Patrick Williamsd7e96312015-09-22 08:09:05 -050042from bs4 import BeautifulSoup
Patrick Williamsf1e5d692016-03-30 15:21:19 -050043import re
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050044import string
45import json
Patrick Williamsc124f4f2015-09-15 14:41:29 -050046
47PROJECT_NAME = "test project"
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050048PROJECT_NAME2 = "test project 2"
Patrick Williamsf1e5d692016-03-30 15:21:19 -050049CLI_BUILDS_PROJECT_NAME = 'Command line builds'
50
Patrick Williamsc124f4f2015-09-15 14:41:29 -050051class ViewTests(TestCase):
52 """Tests to verify view APIs."""
53
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050054 fixtures = ['toastergui-unittest-data']
55
Patrick Williamsc124f4f2015-09-15 14:41:29 -050056 def setUp(self):
Patrick Williamsf1e5d692016-03-30 15:21:19 -050057
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050058 self.project = Project.objects.first()
59 self.recipe1 = Recipe.objects.get(pk=2)
60 self.recipe2 = Recipe.objects.last()
61 self.customr = CustomImageRecipe.objects.first()
62 self.cust_package = CustomImagePackage.objects.first()
63 self.package = Package.objects.first()
64 self.lver = Layer_Version.objects.first()
Patrick Williamsc124f4f2015-09-15 14:41:29 -050065
66 def test_get_base_call_returns_html(self):
67 """Basic test for all-projects view"""
68 response = self.client.get(reverse('all-projects'), follow=True)
69 self.assertEqual(response.status_code, 200)
70 self.assertTrue(response['Content-Type'].startswith('text/html'))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050071 self.assertTemplateUsed(response, "projects-toastertable.html")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050072
73 def test_get_json_call_returns_json(self):
74 """Test for all projects output in json format"""
75 url = reverse('all-projects')
76 response = self.client.get(url, {"format": "json"}, follow=True)
77 self.assertEqual(response.status_code, 200)
78 self.assertTrue(response['Content-Type'].startswith('application/json'))
79
80 data = json.loads(response.content)
81
82 self.assertTrue("error" in data)
83 self.assertEqual(data["error"], "ok")
84 self.assertTrue("rows" in data)
85
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050086 self.assertTrue(self.project.name in [x["name"] for x in data["rows"]])
Patrick Williamsc124f4f2015-09-15 14:41:29 -050087 self.assertTrue("id" in data["rows"][0])
88
Patrick Williamsc124f4f2015-09-15 14:41:29 -050089 def test_typeaheads(self):
90 """Test typeahead ReST API"""
91 layers_url = reverse('xhr_layerstypeahead', args=(self.project.id,))
92 prj_url = reverse('xhr_projectstypeahead')
93
94 urls = [layers_url,
95 prj_url,
96 reverse('xhr_recipestypeahead', args=(self.project.id,)),
97 reverse('xhr_machinestypeahead', args=(self.project.id,)),
98 ]
99
100 def basic_reponse_check(response, url):
101 """Check data structure of http response."""
102 self.assertEqual(response.status_code, 200)
103 self.assertTrue(response['Content-Type'].startswith('application/json'))
104
105 data = json.loads(response.content)
106
107 self.assertTrue("error" in data)
108 self.assertEqual(data["error"], "ok")
109 self.assertTrue("results" in data)
110
111 # We got a result so now check the fields
112 if len(data['results']) > 0:
113 result = data['results'][0]
114
115 self.assertTrue(len(result['name']) > 0)
116 self.assertTrue("detail" in result)
117 self.assertTrue(result['id'] > 0)
118
119 # Special check for the layers typeahead's extra fields
120 if url == layers_url:
121 self.assertTrue(len(result['layerdetailurl']) > 0)
122 self.assertTrue(len(result['vcs_url']) > 0)
123 self.assertTrue(len(result['vcs_reference']) > 0)
124 # Special check for project typeahead extra fields
125 elif url == prj_url:
126 self.assertTrue(len(result['projectPageUrl']) > 0)
127
128 return True
129
130 return False
131
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500132
133 for url in urls:
134 results = False
135
136 for typeing in list(string.ascii_letters):
137 response = self.client.get(url, {'search': typeing})
138 results = basic_reponse_check(response, url)
139 if results:
140 break
141
142 # After "typeing" the alpabet we should have result true
143 # from each of the urls
144 self.assertTrue(results)
145
146 def test_xhr_import_layer(self):
147 """Test xhr_importlayer API"""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500148 LayerSource.objects.create(sourcetype=LayerSource.TYPE_IMPORTED)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500149 #Test for importing an already existing layer
150 args = {'vcs_url' : "git://git.example.com/test",
151 'name' : "base-layer",
152 'git_ref': "c12b9596afd236116b25ce26dbe0d793de9dc7ce",
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500153 'project_id': self.project.id,
154 'dir_path' : "/path/in/repository"}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500155 response = self.client.post(reverse('xhr_importlayer'), args)
156 data = json.loads(response.content)
157 self.assertEqual(response.status_code, 200)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500158 self.assertEqual(data["error"], "ok")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500159
160 #Test to verify import of a layer successful
161 args['name'] = "meta-oe"
162 response = self.client.post(reverse('xhr_importlayer'), args)
163 data = json.loads(response.content)
164 self.assertTrue(data["error"], "ok")
165
166 #Test for html tag in the data
167 args['<'] = "testing html tag"
168 response = self.client.post(reverse('xhr_importlayer'), args)
169 data = json.loads(response.content)
170 self.assertNotEqual(data["error"], "ok")
171
172 #Empty data passed
173 args = {}
174 response = self.client.post(reverse('xhr_importlayer'), args)
175 data = json.loads(response.content)
176 self.assertNotEqual(data["error"], "ok")
177
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500178 def test_custom_ok(self):
179 """Test successful return from ReST API xhr_customrecipe"""
180 url = reverse('xhr_customrecipe')
181 params = {'name': 'custom', 'project': self.project.id,
182 'base': self.recipe1.id}
183 response = self.client.post(url, params)
184 self.assertEqual(response.status_code, 200)
185 data = json.loads(response.content)
186 self.assertEqual(data['error'], 'ok')
187 self.assertTrue('url' in data)
188 # get recipe from the database
189 recipe = CustomImageRecipe.objects.get(project=self.project,
190 name=params['name'])
191 args = (self.project.id, recipe.id,)
192 self.assertEqual(reverse('customrecipe', args=args), data['url'])
193
194 def test_custom_incomplete_params(self):
195 """Test not passing all required parameters to xhr_customrecipe"""
196 url = reverse('xhr_customrecipe')
197 for params in [{}, {'name': 'custom'},
198 {'name': 'custom', 'project': self.project.id}]:
199 response = self.client.post(url, params)
200 self.assertEqual(response.status_code, 200)
201 data = json.loads(response.content)
202 self.assertNotEqual(data["error"], "ok")
203
204 def test_xhr_custom_wrong_project(self):
205 """Test passing wrong project id to xhr_customrecipe"""
206 url = reverse('xhr_customrecipe')
207 params = {'name': 'custom', 'project': 0, "base": self.recipe1.id}
208 response = self.client.post(url, params)
209 self.assertEqual(response.status_code, 200)
210 data = json.loads(response.content)
211 self.assertNotEqual(data["error"], "ok")
212
213 def test_xhr_custom_wrong_base(self):
214 """Test passing wrong base recipe id to xhr_customrecipe"""
215 url = reverse('xhr_customrecipe')
216 params = {'name': 'custom', 'project': self.project.id, "base": 0}
217 response = self.client.post(url, params)
218 self.assertEqual(response.status_code, 200)
219 data = json.loads(response.content)
220 self.assertNotEqual(data["error"], "ok")
221
222 def test_xhr_custom_details(self):
223 """Test getting custom recipe details"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500224 url = reverse('xhr_customrecipe_id', args=(self.customr.id,))
225 response = self.client.get(url)
226 self.assertEqual(response.status_code, 200)
227 expected = {"error": "ok",
228 "info": {'id': self.customr.id,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500229 'name': self.customr.name,
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500230 'base_recipe_id': self.recipe1.id,
231 'project_id': self.project.id,
232 }
233 }
234 self.assertEqual(json.loads(response.content), expected)
235
236 def test_xhr_custom_del(self):
237 """Test deleting custom recipe"""
238 name = "to be deleted"
239 recipe = CustomImageRecipe.objects.create(\
240 name=name, project=self.project,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500241 base_recipe=self.recipe1,
242 file_path="/tmp/testing",
243 layer_version=self.customr.layer_version)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500244 url = reverse('xhr_customrecipe_id', args=(recipe.id,))
245 response = self.client.delete(url)
246 self.assertEqual(response.status_code, 200)
247 self.assertEqual(json.loads(response.content), {"error": "ok"})
248 # try to delete not-existent recipe
249 url = reverse('xhr_customrecipe_id', args=(recipe.id,))
250 response = self.client.delete(url)
251 self.assertEqual(response.status_code, 200)
252 self.assertNotEqual(json.loads(response.content)["error"], "ok")
253
254 def test_xhr_custom_packages(self):
255 """Test adding and deleting package to a custom recipe"""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500256 # add self.package to recipe
257 response = self.client.put(reverse('xhr_customrecipe_packages',
258 args=(self.customr.id,
259 self.cust_package.id)))
260
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500261 self.assertEqual(response.status_code, 200)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500262 self.assertEqual(json.loads(response.content),
263 {"error": "ok"})
264 self.assertEqual(self.customr.appends_set.first().name,
265 self.cust_package.name)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500266 # delete it
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500267 to_delete = self.customr.appends_set.first().pk
268 del_url = reverse('xhr_customrecipe_packages',
269 args=(self.customr.id, to_delete))
270
271 response = self.client.delete(del_url)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500272 self.assertEqual(response.status_code, 200)
273 self.assertEqual(json.loads(response.content), {"error": "ok"})
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500274 all_packages = self.customr.get_all_packages().values_list('pk',
275 flat=True)
276
277 self.assertFalse(to_delete in all_packages)
278 # delete invalid package to test error condition
279 del_url = reverse('xhr_customrecipe_packages',
280 args=(self.customr.id,
281 99999))
282
283 response = self.client.delete(del_url)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500284 self.assertEqual(response.status_code, 200)
285 self.assertNotEqual(json.loads(response.content)["error"], "ok")
286
287 def test_xhr_custom_packages_err(self):
288 """Test error conditions of xhr_customrecipe_packages"""
289 # test calls with wrong recipe id and wrong package id
290 for args in [(0, self.package.id), (self.customr.id, 0)]:
291 url = reverse('xhr_customrecipe_packages', args=args)
292 # test put and delete methods
293 for method in (self.client.put, self.client.delete):
294 response = method(url)
295 self.assertEqual(response.status_code, 200)
296 self.assertNotEqual(json.loads(response.content),
297 {"error": "ok"})
298
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500299 def test_download_custom_recipe(self):
300 """Download the recipe file generated for the custom image"""
301
302 # Create a dummy recipe file for the custom image generation to read
303 open("/tmp/a_recipe.bb", 'wa').close()
304 response = self.client.get(reverse('customrecipedownload',
305 args=(self.project.id,
306 self.customr.id)))
307
308 self.assertEqual(response.status_code, 200)
309
310
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500311 def test_software_recipes_table(self):
312 """Test structure returned for Software RecipesTable"""
313 table = SoftwareRecipesTable()
314 request = RequestFactory().get('/foo/', {'format': 'json'})
315 response = table.get(request, pid=self.project.id)
316 data = json.loads(response.content)
317
318 rows = data['rows']
319 row1 = next(x for x in rows if x['name'] == self.recipe1.name)
320 row2 = next(x for x in rows if x['name'] == self.recipe2.name)
321
322 self.assertEqual(response.status_code, 200, 'should be 200 OK status')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500323
324 # check other columns have been populated correctly
325 self.assertEqual(row1['name'], self.recipe1.name)
326 self.assertEqual(row1['version'], self.recipe1.version)
327 self.assertEqual(row1['get_description_or_summary'],
328 self.recipe1.description)
329 self.assertEqual(row1['layer_version__layer__name'],
330 self.recipe1.layer_version.layer.name)
331 self.assertEqual(row2['name'], self.recipe2.name)
332 self.assertEqual(row2['version'], self.recipe2.version)
333 self.assertEqual(row2['get_description_or_summary'],
334 self.recipe2.description)
335 self.assertEqual(row2['layer_version__layer__name'],
336 self.recipe2.layer_version.layer.name)
337
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500338 def test_toaster_tables(self):
339 """Test all ToasterTables instances"""
340 current_recipes = self.project.get_available_recipes()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500341
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500342 def get_data(table, options={}):
343 """Send a request and parse the json response"""
344 options['format'] = "json"
345 options['nocache'] = "true"
346 request = RequestFactory().get('/', options)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500347
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500348 # This is the image recipe needed for a package list for
349 # PackagesTable do this here to throw a non exist exception
350 image_recipe = Recipe.objects.get(pk=4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500351
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500352 # Add any kwargs that are needed by any of the possible tables
353 args = {'pid': self.project.id,
354 'layerid': self.lver.pk,
355 'recipeid': self.recipe1.pk,
356 'recipe_id': image_recipe.pk,
357 'custrecipeid': self.customr.pk
358 }
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500359
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500360 response = table.get(request, **args)
361 return json.loads(response.content)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500362
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500363 # Get a list of classes in tables module
364 tables = inspect.getmembers(toastergui.tables, inspect.isclass)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500365
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500366 for name, table_cls in tables:
367 # Filter out the non ToasterTables from the tables module
368 if not issubclass(table_cls, toastergui.widgets.ToasterTable) or \
369 table_cls == toastergui.widgets.ToasterTable:
370 continue
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500371
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500372 # Get the table data without any options, this also does the
373 # initialisation of the table i.e. setup_columns,
374 # setup_filters and setup_queryset that we can use later
375 table = table_cls()
376 all_data = get_data(table)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500377
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500378 self.assertTrue(len(all_data['rows']) > 1,
379 "Cannot test on a %s table with < 1 row" % name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500380
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500381 if table.default_orderby:
382 row_one = all_data['rows'][0][table.default_orderby.strip("-")]
383 row_two = all_data['rows'][1][table.default_orderby.strip("-")]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500384
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500385 if '-' in table.default_orderby:
386 self.assertTrue(row_one >= row_two,
387 "Default ordering not working on %s"
388 " '%s' should be >= '%s'" %
389 (name, row_one, row_two))
390 else:
391 self.assertTrue(row_one <= row_two,
392 "Default ordering not working on %s"
393 " '%s' should be <= '%s'" %
394 (name, row_one, row_two))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500395
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500396 # Test the column ordering and filtering functionality
397 for column in table.columns:
398 if column['orderable']:
399 # If a column is orderable test it in both order
400 # directions ordering on the columns field_name
401 ascending = get_data(table_cls(),
402 {"orderby" : column['field_name']})
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500403
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500404 row_one = ascending['rows'][0][column['field_name']]
405 row_two = ascending['rows'][1][column['field_name']]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500406
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500407 self.assertTrue(row_one <= row_two,
408 "Ascending sort applied but row 0 is less "
409 "than row 1 %s %s " %
410 (column['field_name'], name))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500411
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500412
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500413 descending = get_data(table_cls(),
414 {"orderby" :
415 '-'+column['field_name']})
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500416
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500417 row_one = descending['rows'][0][column['field_name']]
418 row_two = descending['rows'][1][column['field_name']]
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500419
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500420 self.assertTrue(row_one >= row_two,
421 "Descending sort applied but row 0 is "
422 "greater than row 1 %s %s" %
423 (column['field_name'], name))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500424
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500425 # If the two start rows are the same we haven't actually
426 # changed the order
427 self.assertNotEqual(ascending['rows'][0],
428 descending['rows'][0],
429 "An orderby %s has not changed the "
430 "order of the data in table %s" %
431 (column['field_name'], name))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500432
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500433 if column['filter_name']:
434 # If a filter is available for the column get the filter
435 # info. This contains what filter actions are defined.
436 filter_info = get_data(table_cls(),
437 {"cmd": "filterinfo",
438 "name": column['filter_name']})
439 self.assertTrue(len(filter_info['filter_actions']) > 0,
440 "Filter %s was defined but no actions "
441 "added to it" % column['filter_name'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500442
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500443 for filter_action in filter_info['filter_actions']:
444 # filter string to pass as the option
445 # This is the name of the filter:action
446 # e.g. project_filter:not_in_project
447 filter_string = "%s:%s" % (column['filter_name'],
448 filter_action['action_name'])
449 # Now get the data with the filter applied
450 filtered_data = get_data(table_cls(),
451 {"filter" : filter_string})
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500452
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500453 # date range filter actions can't specify the
454 # number of results they return, so their count is 0
455 if filter_action['count'] != None:
456 self.assertEqual(len(filtered_data['rows']),
457 int(filter_action['count']),
458 "We added a table filter for %s but "
459 "the number of rows returned was not "
460 "what the filter info said there "
461 "would be" % name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500462
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500463
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500464 # Test search functionality on the table
465 something_found = False
466 for search in list(string.ascii_letters):
467 search_data = get_data(table_cls(), {'search' : search})
Patrick Williamsd7e96312015-09-22 08:09:05 -0500468
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500469 if len(search_data['rows']) > 0:
470 something_found = True
471 break
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500472
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500473 self.assertTrue(something_found,
474 "We went through the whole alphabet and nothing"
475 " was found for the search of table %s" % name)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500476
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500477 # Test the limit functionality on the table
478 limited_data = get_data(table_cls(), {'limit' : "1"})
479 self.assertEqual(len(limited_data['rows']),
480 1,
481 "Limit 1 set on table %s but not 1 row returned"
482 % name)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500483
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500484 # Test the pagination functionality on the table
485 page_one_data = get_data(table_cls(), {'limit' : "1",
486 "page": "1"})['rows'][0]
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500487
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500488 page_two_data = get_data(table_cls(), {'limit' : "1",
489 "page": "2"})['rows'][0]
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500490
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500491 self.assertNotEqual(page_one_data,
492 page_two_data,
493 "Changed page on table %s but first row is the "
494 "same as the previous page" % name)