blob: 4d1549b0a99fefa51a294da979a4ac99fbb42763 [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
25from django.core.urlresolvers import reverse
26from django.utils import timezone
Patrick Williamsd7e96312015-09-22 08:09:05 -050027from orm.models import Project, Release, BitbakeVersion, ProjectTarget
28from orm.models import ReleaseLayerSourcePriority, LayerSource, Layer, Build
Patrick Williamsc124f4f2015-09-15 14:41:29 -050029from orm.models import Layer_Version, Recipe, Machine, ProjectLayer
30import json
Patrick Williamsd7e96312015-09-22 08:09:05 -050031from bs4 import BeautifulSoup
Patrick Williamsc124f4f2015-09-15 14:41:29 -050032
33PROJECT_NAME = "test project"
34
35class ViewTests(TestCase):
36 """Tests to verify view APIs."""
37
38 def setUp(self):
39 bbv = BitbakeVersion.objects.create(name="test bbv", giturl="/tmp/",
40 branch="master", dirpath="")
41 release = Release.objects.create(name="test release",
42 bitbake_version=bbv)
43 self.project = Project.objects.create_project(name=PROJECT_NAME,
44 release=release)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050045 layersrc = LayerSource.objects.create(sourcetype=LayerSource.TYPE_IMPORTED)
46 self.priority = ReleaseLayerSourcePriority.objects.create(release=release,
47 layer_source=layersrc)
48 layer = Layer.objects.create(name="base-layer", layer_source=layersrc,
49 vcs_url="/tmp/")
50
51 lver = Layer_Version.objects.create(layer=layer, project=self.project,
52 layer_source=layersrc, commit="master")
53
54 Recipe.objects.create(layer_source=layersrc, name="base-recipe",
55 version="1.2", summary="one recipe",
56 description="recipe", layer_version=lver)
57
58 Machine.objects.create(layer_version=lver, name="wisk",
59 description="wisking machine")
60
61 ProjectLayer.objects.create(project=self.project, layercommit=lver)
62
63 self.assertTrue(lver in self.project.compatible_layerversions())
64
65 def test_get_base_call_returns_html(self):
66 """Basic test for all-projects view"""
67 response = self.client.get(reverse('all-projects'), follow=True)
68 self.assertEqual(response.status_code, 200)
69 self.assertTrue(response['Content-Type'].startswith('text/html'))
70 self.assertTemplateUsed(response, "projects.html")
71 self.assertTrue(PROJECT_NAME in response.content)
72
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
86 self.assertTrue(PROJECT_NAME in [x["name"] for x in data["rows"]])
87 self.assertTrue("id" in data["rows"][0])
88
89 self.assertEqual(sorted(data["rows"][0]),
90 ['bitbake_version_id', 'created', 'id',
91 'is_default', 'layersTypeAheadUrl', 'name',
92 'num_builds', 'projectBuildsUrl', 'projectPageUrl',
93 'recipesTypeAheadUrl', 'release_id',
94 'short_description', 'updated', 'user_id'])
95
96 def test_typeaheads(self):
97 """Test typeahead ReST API"""
98 layers_url = reverse('xhr_layerstypeahead', args=(self.project.id,))
99 prj_url = reverse('xhr_projectstypeahead')
100
101 urls = [layers_url,
102 prj_url,
103 reverse('xhr_recipestypeahead', args=(self.project.id,)),
104 reverse('xhr_machinestypeahead', args=(self.project.id,)),
105 ]
106
107 def basic_reponse_check(response, url):
108 """Check data structure of http response."""
109 self.assertEqual(response.status_code, 200)
110 self.assertTrue(response['Content-Type'].startswith('application/json'))
111
112 data = json.loads(response.content)
113
114 self.assertTrue("error" in data)
115 self.assertEqual(data["error"], "ok")
116 self.assertTrue("results" in data)
117
118 # We got a result so now check the fields
119 if len(data['results']) > 0:
120 result = data['results'][0]
121
122 self.assertTrue(len(result['name']) > 0)
123 self.assertTrue("detail" in result)
124 self.assertTrue(result['id'] > 0)
125
126 # Special check for the layers typeahead's extra fields
127 if url == layers_url:
128 self.assertTrue(len(result['layerdetailurl']) > 0)
129 self.assertTrue(len(result['vcs_url']) > 0)
130 self.assertTrue(len(result['vcs_reference']) > 0)
131 # Special check for project typeahead extra fields
132 elif url == prj_url:
133 self.assertTrue(len(result['projectPageUrl']) > 0)
134
135 return True
136
137 return False
138
139 import string
140
141 for url in urls:
142 results = False
143
144 for typeing in list(string.ascii_letters):
145 response = self.client.get(url, {'search': typeing})
146 results = basic_reponse_check(response, url)
147 if results:
148 break
149
150 # After "typeing" the alpabet we should have result true
151 # from each of the urls
152 self.assertTrue(results)
153
154 def test_xhr_import_layer(self):
155 """Test xhr_importlayer API"""
156 #Test for importing an already existing layer
157 args = {'vcs_url' : "git://git.example.com/test",
158 'name' : "base-layer",
159 'git_ref': "c12b9596afd236116b25ce26dbe0d793de9dc7ce",
160 'project_id': 1, 'dir_path' : "/path/in/repository"}
161 response = self.client.post(reverse('xhr_importlayer'), args)
162 data = json.loads(response.content)
163 self.assertEqual(response.status_code, 200)
164 self.assertNotEqual(data["error"], "ok")
165
166 #Test to verify import of a layer successful
167 args['name'] = "meta-oe"
168 response = self.client.post(reverse('xhr_importlayer'), args)
169 data = json.loads(response.content)
170 self.assertTrue(data["error"], "ok")
171
172 #Test for html tag in the data
173 args['<'] = "testing html tag"
174 response = self.client.post(reverse('xhr_importlayer'), args)
175 data = json.loads(response.content)
176 self.assertNotEqual(data["error"], "ok")
177
178 #Empty data passed
179 args = {}
180 response = self.client.post(reverse('xhr_importlayer'), args)
181 data = json.loads(response.content)
182 self.assertNotEqual(data["error"], "ok")
183
184class LandingPageTests(TestCase):
185 """ Tests for redirects on the landing page """
186 # disable bogus pylint message error:
187 # "Instance of 'WSGIRequest' has no 'url' member (no-member)"
188 # (see https://github.com/landscapeio/pylint-django/issues/42)
189 # pylint: disable=E1103
190
191 LANDING_PAGE_TITLE = 'This is Toaster'
192
193 def setUp(self):
194 """ Add default project manually """
195 self.project = Project.objects.create_project('foo', None)
196 self.project.is_default = True
197 self.project.save()
198
199 def test_only_default_project(self):
200 """
201 No projects except default
202 => get the landing page
203 """
204 response = self.client.get(reverse('landing'))
205 self.assertTrue(self.LANDING_PAGE_TITLE in response.content)
206
207 def test_default_project_has_build(self):
208 """
209 Default project has a build, no other projects
210 => get the builds page
211 """
212 now = timezone.now()
213 build = Build.objects.create(project=self.project,
214 started_on=now,
215 completed_on=now)
216 build.save()
217
218 response = self.client.get(reverse('landing'))
219 self.assertEqual(response.status_code, 302,
220 'response should be a redirect')
221 self.assertTrue('/builds' in response.url,
222 'should redirect to builds')
223
224 def test_user_project_exists(self):
225 """
226 User has added a project (without builds)
227 => get the projects page
228 """
229 user_project = Project.objects.create_project('foo', None)
230 user_project.save()
231
232 response = self.client.get(reverse('landing'))
233 self.assertEqual(response.status_code, 302,
234 'response should be a redirect')
235 self.assertTrue('/projects' in response.url,
236 'should redirect to projects')
237
238 def test_user_project_has_build(self):
239 """
240 User has added a project (with builds)
241 => get the builds page
242 """
243 user_project = Project.objects.create_project('foo', None)
244 user_project.save()
245
246 now = timezone.now()
247 build = Build.objects.create(project=user_project,
248 started_on=now,
249 completed_on=now)
250 build.save()
251
252 response = self.client.get(reverse('landing'))
253 self.assertEqual(response.status_code, 302,
254 'response should be a redirect')
255 self.assertTrue('/builds' in response.url,
256 'should redirect to builds')
257
258class ProjectsPageTests(TestCase):
259 """ Tests for projects page """
260
261 PROJECT_NAME = 'cli builds'
262
263 def setUp(self):
264 """ Add default project manually """
265 project = Project.objects.create_project(self.PROJECT_NAME, None)
266 self.default_project = project
267 self.default_project.is_default = True
268 self.default_project.save()
269
270 def test_default_project_hidden(self):
271 """ The default project should be hidden if it has no builds """
272 params = {"count": 10, "orderby": "updated:-", "page": 1}
273 response = self.client.get(reverse('all-projects'), params)
274
275 self.assertTrue(not('tr class="data"' in response.content),
276 'should be no project rows in the page')
277 self.assertTrue(not(self.PROJECT_NAME in response.content),
278 'default project "cli builds" should not be in page')
279
280 def test_default_project_has_build(self):
281 """ The default project should be shown if it has builds """
282 now = timezone.now()
283 build = Build.objects.create(project=self.default_project,
284 started_on=now,
285 completed_on=now)
286 build.save()
287
288 params = {"count": 10, "orderby": "updated:-", "page": 1}
289 response = self.client.get(reverse('all-projects'), params)
290
291 self.assertTrue('tr class="data"' in response.content,
292 'should be a project row in the page')
293 self.assertTrue(self.PROJECT_NAME in response.content,
294 'default project "cli builds" should be in page')
Patrick Williamsd7e96312015-09-22 08:09:05 -0500295
296class ProjectBuildsDisplayTest(TestCase):
297 """ Test data at /project/X/builds is displayed correctly """
298
299 def setUp(self):
300 bbv = BitbakeVersion.objects.create(name="bbv1", giturl="/tmp/",
301 branch="master", dirpath="")
302 release = Release.objects.create(name="release1",
303 bitbake_version=bbv)
304 self.project1 = Project.objects.create_project(name=PROJECT_NAME,
305 release=release)
306 self.project2 = Project.objects.create_project(name=PROJECT_NAME,
307 release=release)
308
309 # parameters for builds to associate with the projects
310 now = timezone.now()
311
312 self.project1_build_success = {
313 "project": self.project1,
314 "started_on": now,
315 "completed_on": now,
316 "outcome": Build.SUCCEEDED
317 }
318
319 self.project1_build_in_progress = {
320 "project": self.project1,
321 "started_on": now,
322 "completed_on": now,
323 "outcome": Build.IN_PROGRESS
324 }
325
326 self.project2_build_success = {
327 "project": self.project2,
328 "started_on": now,
329 "completed_on": now,
330 "outcome": Build.SUCCEEDED
331 }
332
333 self.project2_build_in_progress = {
334 "project": self.project2,
335 "started_on": now,
336 "completed_on": now,
337 "outcome": Build.IN_PROGRESS
338 }
339
340 def _get_rows_for_project(self, project_id):
341 url = reverse("projectbuilds", args=(project_id,))
342 response = self.client.get(url, follow=True)
343 soup = BeautifulSoup(response.content)
344 return soup.select('tr[class="data"]')
345
346 def test_show_builds_for_project(self):
347 """ Builds for a project should be displayed """
348 build1a = Build.objects.create(**self.project1_build_success)
349 build1b = Build.objects.create(**self.project1_build_success)
350 build_rows = self._get_rows_for_project(self.project1.id)
351 self.assertEqual(len(build_rows), 2)
352
353 def test_show_builds_for_project_only(self):
354 """ Builds for other projects should be excluded """
355 build1a = Build.objects.create(**self.project1_build_success)
356 build1b = Build.objects.create(**self.project1_build_success)
357 build1c = Build.objects.create(**self.project1_build_success)
358
359 # shouldn't see these two
360 build2a = Build.objects.create(**self.project2_build_success)
361 build2b = Build.objects.create(**self.project2_build_in_progress)
362
363 build_rows = self._get_rows_for_project(self.project1.id)
364 self.assertEqual(len(build_rows), 3)
365
366 def test_show_builds_exclude_in_progress(self):
367 """ "in progress" builds should not be shown """
368 build1a = Build.objects.create(**self.project1_build_success)
369 build1b = Build.objects.create(**self.project1_build_success)
370
371 # shouldn't see this one
372 build1c = Build.objects.create(**self.project1_build_in_progress)
373
374 # shouldn't see these two either, as they belong to a different project
375 build2a = Build.objects.create(**self.project2_build_success)
376 build2b = Build.objects.create(**self.project2_build_in_progress)
377
378 build_rows = self._get_rows_for_project(self.project1.id)
379 self.assertEqual(len(build_rows), 2)