summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/toaster/tests/views/test_views.py
diff options
context:
space:
mode:
authorMichael Wood <michael.g.wood@intel.com>2016-10-28 18:48:43 +0300
committerRichard Purdie <richard.purdie@linuxfoundation.org>2016-11-04 12:50:55 +0000
commiteffbf9e09e527a4bfb1e1ba5146ade2bad02e673 (patch)
tree1f5d9ef941276f16fcdeb5d89199d6b2936530e9 /bitbake/lib/toaster/tests/views/test_views.py
parentd350276a86089a89c149e10d1e50e44ec6cb24d0 (diff)
downloadpoky-effbf9e09e527a4bfb1e1ba5146ade2bad02e673.tar.gz
bitbake: toaster: Move views tests to main testing module
Consolidating all the tests to live in the same place to make them more discoverable and consistent as well as not cluttering up the django app directory. (Bitbake rev: 66076c006079237d97aaef4f242af5a4fa116d97) Signed-off-by: Michael Wood <michael.g.wood@intel.com> Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib/toaster/tests/views/test_views.py')
-rw-r--r--bitbake/lib/toaster/tests/views/test_views.py539
1 files changed, 539 insertions, 0 deletions
diff --git a/bitbake/lib/toaster/tests/views/test_views.py b/bitbake/lib/toaster/tests/views/test_views.py
new file mode 100644
index 0000000000..c20b3fe0b6
--- /dev/null
+++ b/bitbake/lib/toaster/tests/views/test_views.py
@@ -0,0 +1,539 @@
1#! /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
25from django.test.client import RequestFactory
26from django.core.urlresolvers import reverse
27from django.db.models import Q
28
29from orm.models import Project, Package
30from orm.models import Layer_Version, Recipe
31from orm.models import CustomImageRecipe
32from orm.models import CustomImagePackage
33
34import inspect
35import toastergui
36
37from toastergui.tables import SoftwareRecipesTable
38import json
39from bs4 import BeautifulSoup
40import string
41
42PROJECT_NAME = "test project"
43PROJECT_NAME2 = "test project 2"
44CLI_BUILDS_PROJECT_NAME = 'Command line builds'
45
46
47class ViewTests(TestCase):
48 """Tests to verify view APIs."""
49
50 fixtures = ['toastergui-unittest-data']
51
52 def setUp(self):
53
54 self.project = Project.objects.first()
55 self.recipe1 = Recipe.objects.get(pk=2)
56 self.customr = CustomImageRecipe.objects.first()
57 self.cust_package = CustomImagePackage.objects.first()
58 self.package = Package.objects.first()
59 self.lver = Layer_Version.objects.first()
60
61 def test_get_base_call_returns_html(self):
62 """Basic test for all-projects view"""
63 response = self.client.get(reverse('all-projects'), follow=True)
64 self.assertEqual(response.status_code, 200)
65 self.assertTrue(response['Content-Type'].startswith('text/html'))
66 self.assertTemplateUsed(response, "projects-toastertable.html")
67
68 def test_get_json_call_returns_json(self):
69 """Test for all projects output in json format"""
70 url = reverse('all-projects')
71 response = self.client.get(url, {"format": "json"}, follow=True)
72 self.assertEqual(response.status_code, 200)
73 self.assertTrue(response['Content-Type'].startswith(
74 'application/json'))
75
76 data = json.loads(response.content.decode('utf-8'))
77
78 self.assertTrue("error" in data)
79 self.assertEqual(data["error"], "ok")
80 self.assertTrue("rows" in data)
81
82 name_found = False
83 for row in data["rows"]:
84 name_found = row['name'].find(self.project.name)
85
86 self.assertTrue(name_found,
87 "project name not found in projects table")
88
89 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 def basic_reponse_check(response, url):
100 """Check data structure of http response."""
101 self.assertEqual(response.status_code, 200)
102 self.assertTrue(response['Content-Type'].startswith(
103 'application/json'))
104
105 data = json.loads(response.content.decode('utf-8'))
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
132 for url in urls:
133 results = False
134
135 for typeing in list(string.ascii_letters):
136 response = self.client.get(url, {'search': typeing})
137 results = basic_reponse_check(response, url)
138 if results:
139 break
140
141 # After "typeing" the alpabet we should have result true
142 # from each of the urls
143 self.assertTrue(results)
144
145 def test_xhr_import_layer(self):
146 """Test xhr_importlayer API"""
147 # Test for importing an already existing layer
148 args = {'vcs_url': "git://git.example.com/test",
149 'name': "base-layer",
150 'git_ref': "c12b9596afd236116b25ce26dbe0d793de9dc7ce",
151 'project_id': self.project.id,
152 'local_source_dir': "",
153 'dir_path': "/path/in/repository"}
154 response = self.client.post(reverse('xhr_importlayer'), args)
155 data = json.loads(response.content.decode('utf-8'))
156 self.assertEqual(response.status_code, 200)
157 self.assertEqual(data["error"], "ok")
158
159 # Test to verify import of a layer successful
160 args['name'] = "meta-oe"
161 response = self.client.post(reverse('xhr_importlayer'), args)
162 data = json.loads(response.content.decode('utf-8'))
163 self.assertTrue(data["error"], "ok")
164
165 # Test for html tag in the data
166 args['<'] = "testing html tag"
167 response = self.client.post(reverse('xhr_importlayer'), args)
168 data = json.loads(response.content.decode('utf-8'))
169 self.assertNotEqual(data["error"], "ok")
170
171 # Empty data passed
172 args = {}
173 response = self.client.post(reverse('xhr_importlayer'), args)
174 data = json.loads(response.content.decode('utf-8'))
175 self.assertNotEqual(data["error"], "ok")
176
177 def test_custom_ok(self):
178 """Test successful return from ReST API xhr_customrecipe"""
179 url = reverse('xhr_customrecipe')
180 params = {'name': 'custom', 'project': self.project.id,
181 'base': self.recipe1.id}
182 response = self.client.post(url, params)
183 self.assertEqual(response.status_code, 200)
184 data = json.loads(response.content.decode('utf-8'))
185 self.assertEqual(data['error'], 'ok')
186 self.assertTrue('url' in data)
187 # get recipe from the database
188 recipe = CustomImageRecipe.objects.get(project=self.project,
189 name=params['name'])
190 args = (self.project.id, recipe.id,)
191 self.assertEqual(reverse('customrecipe', args=args), data['url'])
192
193 def test_custom_incomplete_params(self):
194 """Test not passing all required parameters to xhr_customrecipe"""
195 url = reverse('xhr_customrecipe')
196 for params in [{}, {'name': 'custom'},
197 {'name': 'custom', 'project': self.project.id}]:
198 response = self.client.post(url, params)
199 self.assertEqual(response.status_code, 200)
200 data = json.loads(response.content.decode('utf-8'))
201 self.assertNotEqual(data["error"], "ok")
202
203 def test_xhr_custom_wrong_project(self):
204 """Test passing wrong project id to xhr_customrecipe"""
205 url = reverse('xhr_customrecipe')
206 params = {'name': 'custom', 'project': 0, "base": self.recipe1.id}
207 response = self.client.post(url, params)
208 self.assertEqual(response.status_code, 200)
209 data = json.loads(response.content.decode('utf-8'))
210 self.assertNotEqual(data["error"], "ok")
211
212 def test_xhr_custom_wrong_base(self):
213 """Test passing wrong base recipe id to xhr_customrecipe"""
214 url = reverse('xhr_customrecipe')
215 params = {'name': 'custom', 'project': self.project.id, "base": 0}
216 response = self.client.post(url, params)
217 self.assertEqual(response.status_code, 200)
218 data = json.loads(response.content.decode('utf-8'))
219 self.assertNotEqual(data["error"], "ok")
220
221 def test_xhr_custom_details(self):
222 """Test getting custom recipe details"""
223 url = reverse('xhr_customrecipe_id', args=(self.customr.id,))
224 response = self.client.get(url)
225 self.assertEqual(response.status_code, 200)
226 expected = {"error": "ok",
227 "info": {'id': self.customr.id,
228 'name': self.customr.name,
229 'base_recipe_id': self.recipe1.id,
230 'project_id': self.project.id}}
231 self.assertEqual(json.loads(response.content.decode('utf-8')),
232 expected)
233
234 def test_xhr_custom_del(self):
235 """Test deleting custom recipe"""
236 name = "to be deleted"
237 recipe = CustomImageRecipe.objects.create(
238 name=name, project=self.project,
239 base_recipe=self.recipe1,
240 file_path="/tmp/testing",
241 layer_version=self.customr.layer_version)
242 url = reverse('xhr_customrecipe_id', args=(recipe.id,))
243 response = self.client.delete(url)
244 self.assertEqual(response.status_code, 200)
245
246 gotoUrl = reverse('projectcustomimages', args=(self.project.pk,))
247
248 self.assertEqual(json.loads(response.content.decode('utf-8')),
249 {"error": "ok",
250 "gotoUrl": gotoUrl})
251
252 # try to delete not-existent recipe
253 url = reverse('xhr_customrecipe_id', args=(recipe.id,))
254 response = self.client.delete(url)
255 self.assertEqual(response.status_code, 200)
256 self.assertNotEqual(json.loads(
257 response.content.decode('utf-8'))["error"], "ok")
258
259 def test_xhr_custom_packages(self):
260 """Test adding and deleting package to a custom recipe"""
261 # add self.package to recipe
262 response = self.client.put(reverse('xhr_customrecipe_packages',
263 args=(self.customr.id,
264 self.cust_package.id)))
265
266 self.assertEqual(response.status_code, 200)
267 self.assertEqual(json.loads(response.content.decode('utf-8')),
268 {"error": "ok"})
269 self.assertEqual(self.customr.appends_set.first().name,
270 self.cust_package.name)
271 # delete it
272 to_delete = self.customr.appends_set.first().pk
273 del_url = reverse('xhr_customrecipe_packages',
274 args=(self.customr.id, to_delete))
275
276 response = self.client.delete(del_url)
277 self.assertEqual(response.status_code, 200)
278 self.assertEqual(json.loads(response.content.decode('utf-8')),
279 {"error": "ok"})
280 all_packages = self.customr.get_all_packages().values_list('pk',
281 flat=True)
282
283 self.assertFalse(to_delete in all_packages)
284 # delete invalid package to test error condition
285 del_url = reverse('xhr_customrecipe_packages',
286 args=(self.customr.id,
287 99999))
288
289 response = self.client.delete(del_url)
290 self.assertEqual(response.status_code, 200)
291 self.assertNotEqual(json.loads(
292 response.content.decode('utf-8'))["error"], "ok")
293
294 def test_xhr_custom_packages_err(self):
295 """Test error conditions of xhr_customrecipe_packages"""
296 # test calls with wrong recipe id and wrong package id
297 for args in [(0, self.package.id), (self.customr.id, 0)]:
298 url = reverse('xhr_customrecipe_packages', args=args)
299 # test put and delete methods
300 for method in (self.client.put, self.client.delete):
301 response = method(url)
302 self.assertEqual(response.status_code, 200)
303 self.assertNotEqual(json.loads(
304 response.content.decode('utf-8')),
305 {"error": "ok"})
306
307 def test_download_custom_recipe(self):
308 """Download the recipe file generated for the custom image"""
309
310 # Create a dummy recipe file for the custom image generation to read
311 open("/tmp/a_recipe.bb", 'a').close()
312 response = self.client.get(reverse('customrecipedownload',
313 args=(self.project.id,
314 self.customr.id)))
315
316 self.assertEqual(response.status_code, 200)
317
318 def test_software_recipes_table(self):
319 """Test structure returned for Software RecipesTable"""
320 table = SoftwareRecipesTable()
321 request = RequestFactory().get('/foo/', {'format': 'json'})
322 response = table.get(request, pid=self.project.id)
323 data = json.loads(response.content.decode('utf-8'))
324
325 recipes = Recipe.objects.filter(Q(is_image=False))
326 self.assertTrue(len(recipes) > 1,
327 "Need more than one software recipe to test "
328 "SoftwareRecipesTable")
329
330 recipe1 = recipes[0]
331 recipe2 = recipes[1]
332
333 rows = data['rows']
334 row1 = next(x for x in rows if x['name'] == recipe1.name)
335 row2 = next(x for x in rows if x['name'] == recipe2.name)
336
337 self.assertEqual(response.status_code, 200, 'should be 200 OK status')
338
339 # check other columns have been populated correctly
340 self.assertTrue(recipe1.name in row1['name'])
341 self.assertTrue(recipe1.version in row1['version'])
342 self.assertTrue(recipe1.description in
343 row1['get_description_or_summary'])
344
345 self.assertTrue(recipe1.layer_version.layer.name in
346 row1['layer_version__layer__name'])
347
348 self.assertTrue(recipe2.name in row2['name'])
349 self.assertTrue(recipe2.version in row2['version'])
350 self.assertTrue(recipe2.description in
351 row2['get_description_or_summary'])
352
353 self.assertTrue(recipe2.layer_version.layer.name in
354 row2['layer_version__layer__name'])
355
356 def test_toaster_tables(self):
357 """Test all ToasterTables instances"""
358
359 def get_data(table, options={}):
360 """Send a request and parse the json response"""
361 options['format'] = "json"
362 options['nocache'] = "true"
363 request = RequestFactory().get('/', options)
364
365 # This is the image recipe needed for a package list for
366 # PackagesTable do this here to throw a non exist exception
367 image_recipe = Recipe.objects.get(pk=4)
368
369 # Add any kwargs that are needed by any of the possible tables
370 args = {'pid': self.project.id,
371 'layerid': self.lver.pk,
372 'recipeid': self.recipe1.pk,
373 'recipe_id': image_recipe.pk,
374 'custrecipeid': self.customr.pk,
375 'build_id': 1,
376 'target_id': 1}
377
378 response = table.get(request, **args)
379 return json.loads(response.content.decode('utf-8'))
380
381 def get_text_from_td(td):
382 """If we have html in the td then extract the text portion"""
383 # just so we don't waste time parsing non html
384 if "<" not in td:
385 ret = td
386 else:
387 ret = BeautifulSoup(td, "html.parser").text
388
389 if len(ret):
390 return "0"
391 else:
392 return ret
393
394 # Get a list of classes in tables module
395 tables = inspect.getmembers(toastergui.tables, inspect.isclass)
396 tables.extend(inspect.getmembers(toastergui.buildtables,
397 inspect.isclass))
398
399 for name, table_cls in tables:
400 # Filter out the non ToasterTables from the tables module
401 if not issubclass(table_cls, toastergui.widgets.ToasterTable) or \
402 table_cls == toastergui.widgets.ToasterTable or \
403 'Mixin' in name:
404 continue
405
406 # Get the table data without any options, this also does the
407 # initialisation of the table i.e. setup_columns,
408 # setup_filters and setup_queryset that we can use later
409 table = table_cls()
410 all_data = get_data(table)
411
412 self.assertTrue(len(all_data['rows']) > 1,
413 "Cannot test on a %s table with < 1 row" % name)
414
415 if table.default_orderby:
416 row_one = get_text_from_td(
417 all_data['rows'][0][table.default_orderby.strip("-")])
418 row_two = get_text_from_td(
419 all_data['rows'][1][table.default_orderby.strip("-")])
420
421 if '-' in table.default_orderby:
422 self.assertTrue(row_one >= row_two,
423 "Default ordering not working on %s"
424 " '%s' should be >= '%s'" %
425 (name, row_one, row_two))
426 else:
427 self.assertTrue(row_one <= row_two,
428 "Default ordering not working on %s"
429 " '%s' should be <= '%s'" %
430 (name, row_one, row_two))
431
432 # Test the column ordering and filtering functionality
433 for column in table.columns:
434 if column['orderable']:
435 # If a column is orderable test it in both order
436 # directions ordering on the columns field_name
437 ascending = get_data(table_cls(),
438 {"orderby": column['field_name']})
439
440 row_one = get_text_from_td(
441 ascending['rows'][0][column['field_name']])
442 row_two = get_text_from_td(
443 ascending['rows'][1][column['field_name']])
444
445 self.assertTrue(row_one <= row_two,
446 "Ascending sort applied but row 0: \"%s\""
447 " is less than row 1: \"%s\" "
448 "%s %s " %
449 (row_one, row_two,
450 column['field_name'], name))
451
452 descending = get_data(table_cls(),
453 {"orderby":
454 '-'+column['field_name']})
455
456 row_one = get_text_from_td(
457 descending['rows'][0][column['field_name']])
458 row_two = get_text_from_td(
459 descending['rows'][1][column['field_name']])
460
461 self.assertTrue(row_one >= row_two,
462 "Descending sort applied but row 0: %s"
463 "is greater than row 1: %s"
464 "field %s table %s" %
465 (row_one,
466 row_two,
467 column['field_name'], name))
468
469 # If the two start rows are the same we haven't actually
470 # changed the order
471 self.assertNotEqual(ascending['rows'][0],
472 descending['rows'][0],
473 "An orderby %s has not changed the "
474 "order of the data in table %s" %
475 (column['field_name'], name))
476
477 if column['filter_name']:
478 # If a filter is available for the column get the filter
479 # info. This contains what filter actions are defined.
480 filter_info = get_data(table_cls(),
481 {"cmd": "filterinfo",
482 "name": column['filter_name']})
483 self.assertTrue(len(filter_info['filter_actions']) > 0,
484 "Filter %s was defined but no actions "
485 "added to it" % column['filter_name'])
486
487 for filter_action in filter_info['filter_actions']:
488 # filter string to pass as the option
489 # This is the name of the filter:action
490 # e.g. project_filter:not_in_project
491 filter_string = "%s:%s" % (
492 column['filter_name'],
493 filter_action['action_name'])
494 # Now get the data with the filter applied
495 filtered_data = get_data(table_cls(),
496 {"filter": filter_string})
497
498 # date range filter actions can't specify the
499 # number of results they return, so their count is 0
500 if filter_action['count'] is not None:
501 self.assertEqual(
502 len(filtered_data['rows']),
503 int(filter_action['count']),
504 "We added a table filter for %s but "
505 "the number of rows returned was not "
506 "what the filter info said there "
507 "would be" % name)
508
509 # Test search functionality on the table
510 something_found = False
511 for search in list(string.ascii_letters):
512 search_data = get_data(table_cls(), {'search': search})
513
514 if len(search_data['rows']) > 0:
515 something_found = True
516 break
517
518 self.assertTrue(something_found,
519 "We went through the whole alphabet and nothing"
520 " was found for the search of table %s" % name)
521
522 # Test the limit functionality on the table
523 limited_data = get_data(table_cls(), {'limit': "1"})
524 self.assertEqual(len(limited_data['rows']),
525 1,
526 "Limit 1 set on table %s but not 1 row returned"
527 % name)
528
529 # Test the pagination functionality on the table
530 page_one_data = get_data(table_cls(), {'limit': "1",
531 "page": "1"})['rows'][0]
532
533 page_two_data = get_data(table_cls(), {'limit': "1",
534 "page": "2"})['rows'][0]
535
536 self.assertNotEqual(page_one_data,
537 page_two_data,
538 "Changed page on table %s but first row is"
539 " the same as the previous page" % name)